View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugins.pmd;
20  
21  import javax.inject.Inject;
22  
23  import java.io.IOException;
24  import java.io.UnsupportedEncodingException;
25  import java.util.Locale;
26  
27  import org.apache.maven.plugins.annotations.Mojo;
28  import org.apache.maven.plugins.annotations.Parameter;
29  import org.apache.maven.plugins.pmd.exec.CpdRequest;
30  import org.apache.maven.plugins.pmd.exec.CpdResult;
31  import org.apache.maven.plugins.pmd.exec.CpdServiceExecutor;
32  import org.apache.maven.reporting.MavenReportException;
33  import org.codehaus.plexus.i18n.I18N;
34  
35  /**
36   * Creates a report for PMD's Copy/Paste Detector (CPD) tool.
37   * It can also generate a cpd results file in any of these formats: xml, csv or txt.
38   *
39   * <p>See <a href="https://pmd.github.io/latest/pmd_userdocs_cpd.html">Finding duplicated code</a>
40   * for more details.
41   *
42   * @author Mike Perham
43   * @version $Id$
44   * @since 2.0
45   */
46  @Mojo(name = "cpd", threadSafe = true)
47  public class CpdReport extends AbstractPmdReport {
48      /**
49       * The programming language to be analyzed by CPD. Valid values are currently <code>java</code>,
50       * <code>javascript</code> or <code>jsp</code>.
51       *
52       * @since 3.5
53       */
54      @Parameter(defaultValue = "java")
55      private String language;
56  
57      /**
58       * The minimum number of tokens that need to be duplicated before it causes a violation.
59       */
60      @Parameter(property = "minimumTokens", defaultValue = "100")
61      private int minimumTokens;
62  
63      /**
64       * Skip the CPD report generation. Most useful on the command line via "-Dcpd.skip=true".
65       *
66       * @since 2.1
67       */
68      @Parameter(property = "cpd.skip", defaultValue = "false")
69      private boolean skip;
70  
71      /**
72       * If true, CPD ignores literal value differences when evaluating a duplicate block. This means that
73       * <code>foo=42;</code> and <code>foo=43;</code> will be seen as equivalent. You may want to run PMD with this
74       * option off to start with and then switch it on to see what it turns up.
75       *
76       * @since 2.5
77       */
78      @Parameter(property = "cpd.ignoreLiterals", defaultValue = "false")
79      private boolean ignoreLiterals;
80  
81      /**
82       * Similar to <code>ignoreLiterals</code> but for identifiers; i.e., variable names, methods names, and so forth.
83       *
84       * @since 2.5
85       */
86      @Parameter(property = "cpd.ignoreIdentifiers", defaultValue = "false")
87      private boolean ignoreIdentifiers;
88  
89      /**
90       * If true, CPD ignores annotations.
91       *
92       * @since 3.11.0
93       */
94      @Parameter(property = "cpd.ignoreAnnotations", defaultValue = "false")
95      private boolean ignoreAnnotations;
96  
97      /**
98       * Internationalization component
99       */
100     private final I18N i18n;
101 
102     private final CpdServiceExecutor serviceExecutor;
103 
104     /**
105      * Contains the result of the last CPD execution.
106      * It might be <code>null</code> which means, that CPD
107      * has not been executed yet.
108      */
109     private CpdResult cpdResult;
110 
111     @Inject
112     public CpdReport(I18N i18n, CpdServiceExecutor serviceExecutor) {
113         this.i18n = i18n;
114         this.serviceExecutor = serviceExecutor;
115     }
116 
117     /** {@inheritDoc} */
118     public String getName(Locale locale) {
119         return getI18nString(locale, "name");
120     }
121 
122     /** {@inheritDoc} */
123     public String getDescription(Locale locale) {
124         return getI18nString(locale, "description");
125     }
126 
127     /**
128      * @param locale The locale
129      * @param key The key to search for
130      * @return The text appropriate for the locale.
131      */
132     protected String getI18nString(Locale locale, String key) {
133         return i18n.getString("cpd-report", locale, "report.cpd." + key);
134     }
135 
136     /**
137      * {@inheritDoc}
138      */
139     @Override
140     public void executeReport(Locale locale) throws MavenReportException {
141         ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
142         try {
143             Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
144 
145             CpdReportRenderer renderer = new CpdReportRenderer(
146                     getSink(), i18n, locale, filesToProcess, cpdResult.getDuplications(), isAggregator());
147             renderer.render();
148         } finally {
149             Thread.currentThread().setContextClassLoader(origLoader);
150         }
151     }
152 
153     @Override
154     public boolean canGenerateReport() throws MavenReportException {
155         if (skip) {
156             return false;
157         }
158 
159         boolean result = canGenerateReportInternal();
160         if (result) {
161             executeCpd();
162             if (skipEmptyReport) {
163                 result = cpdResult.hasDuplications();
164             }
165         }
166         return result;
167     }
168 
169     private void executeCpd() throws MavenReportException {
170         if (cpdResult != null) {
171             // CPD has already been run
172             getLog().debug("CPD has already been run - skipping redundant execution.");
173             return;
174         }
175 
176         try {
177             filesToProcess = getFilesToProcess();
178 
179             CpdRequest request = new CpdRequest();
180             request.setMinimumTokens(minimumTokens);
181             request.setLanguage(language);
182             request.setIgnoreAnnotations(ignoreAnnotations);
183             request.setIgnoreIdentifiers(ignoreIdentifiers);
184             request.setIgnoreLiterals(ignoreLiterals);
185             request.setSourceEncoding(getInputEncoding());
186             request.addFiles(filesToProcess.keySet());
187             request.setExcludeFromFailureFile(excludeFromFailureFile);
188             request.setTargetDirectory(targetDirectory.getAbsolutePath());
189             request.setOutputEncoding(getOutputEncoding());
190             request.setFormat(format);
191             request.setIncludeXmlInReports(includeXmlInReports);
192             request.setReportOutputDirectory(getReportOutputDirectory().getAbsolutePath());
193             request.setJdkToolchain(getJdkToolchain());
194 
195             cpdResult = serviceExecutor.execute(request);
196         } catch (UnsupportedEncodingException e) {
197             throw new MavenReportException("Encoding '" + getInputEncoding() + "' is not supported.", e);
198         } catch (IOException e) {
199             throw new MavenReportException(e.getMessage(), e);
200         }
201     }
202 
203     /**
204      * {@inheritDoc}
205      */
206     @Override
207     @Deprecated
208     public String getOutputName() {
209         return "cpd";
210     }
211 
212     @Override
213     public String getOutputPath() {
214         return "cpd";
215     }
216 }