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 java.io.IOException;
22  import java.io.UnsupportedEncodingException;
23  import java.util.Locale;
24  
25  import org.apache.maven.plugins.annotations.Component;
26  import org.apache.maven.plugins.annotations.Mojo;
27  import org.apache.maven.plugins.annotations.Parameter;
28  import org.apache.maven.plugins.pmd.exec.CpdExecutor;
29  import org.apache.maven.plugins.pmd.exec.CpdRequest;
30  import org.apache.maven.plugins.pmd.exec.CpdResult;
31  import org.apache.maven.reporting.MavenReportException;
32  import org.apache.maven.toolchain.Toolchain;
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     @Component
101     private I18N i18n;
102 
103     /**
104      * Contains the result of the last CPD execution.
105      * It might be <code>null</code> which means, that CPD
106      * has not been executed yet.
107      */
108     private CpdResult cpdResult;
109 
110     /** {@inheritDoc} */
111     public String getName(Locale locale) {
112         return getI18nString(locale, "name");
113     }
114 
115     /** {@inheritDoc} */
116     public String getDescription(Locale locale) {
117         return getI18nString(locale, "description");
118     }
119 
120     /**
121      * @param locale The locale
122      * @param key The key to search for
123      * @return The text appropriate for the locale.
124      */
125     protected String getI18nString(Locale locale, String key) {
126         return i18n.getString("cpd-report", locale, "report.cpd." + key);
127     }
128 
129     /**
130      * {@inheritDoc}
131      */
132     @Override
133     public void executeReport(Locale locale) throws MavenReportException {
134         ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
135         try {
136             Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
137 
138             CpdReportRenderer r = new CpdReportRenderer(
139                     getSink(), i18n, locale, filesToProcess, cpdResult.getDuplications(), isAggregator());
140             r.render();
141         } finally {
142             Thread.currentThread().setContextClassLoader(origLoader);
143         }
144     }
145 
146     @Override
147     public boolean canGenerateReport() {
148         if (skip) {
149             getLog().info("Skipping CPD execution");
150             return false;
151         }
152 
153         boolean result = super.canGenerateReport();
154         if (result) {
155             try {
156                 executeCpd();
157                 if (skipEmptyReport) {
158                     result = cpdResult.hasDuplications();
159                     if (!result) {
160                         getLog().debug("Skipping report since skipEmptyReport is true and there are no CPD issues.");
161                     }
162                 }
163             } catch (MavenReportException e) {
164                 throw new RuntimeException(e);
165             }
166         }
167         return result;
168     }
169 
170     private void executeCpd() throws MavenReportException {
171         if (cpdResult != null) {
172             // CPD has already been run
173             getLog().debug("CPD has already been run - skipping redundant execution.");
174             return;
175         }
176 
177         try {
178             filesToProcess = getFilesToProcess();
179 
180             CpdRequest request = new CpdRequest();
181             request.setMinimumTokens(minimumTokens);
182             request.setLanguage(language);
183             request.setIgnoreAnnotations(ignoreAnnotations);
184             request.setIgnoreIdentifiers(ignoreIdentifiers);
185             request.setIgnoreLiterals(ignoreLiterals);
186             request.setSourceEncoding(getInputEncoding());
187             request.addFiles(filesToProcess.keySet());
188             request.setLogLevel(determineCurrentRootLogLevel());
189             request.setExcludeFromFailureFile(excludeFromFailureFile);
190             request.setTargetDirectory(targetDirectory.getAbsolutePath());
191             request.setOutputEncoding(getOutputEncoding());
192             request.setFormat(format);
193             request.setIncludeXmlInSite(includeXmlInSite);
194             request.setReportOutputDirectory(getReportOutputDirectory().getAbsolutePath());
195 
196             Toolchain tc = getToolchain();
197             if (tc != null) {
198                 getLog().info("Toolchain in maven-pmd-plugin: " + tc);
199                 String javaExecutable = tc.findTool("java"); // NOI18N
200                 request.setJavaExecutable(javaExecutable);
201             }
202 
203             getLog().info("PMD version: " + AbstractPmdReport.getPmdVersion());
204             cpdResult = CpdExecutor.execute(request);
205         } catch (UnsupportedEncodingException e) {
206             throw new MavenReportException("Encoding '" + getInputEncoding() + "' is not supported.", e);
207         } catch (IOException e) {
208             throw new MavenReportException(e.getMessage(), e);
209         }
210     }
211 
212     /**
213      * {@inheritDoc}
214      */
215     public String getOutputName() {
216         return "cpd";
217     }
218 }