View Javadoc
1   package org.apache.maven.plugins.pmd;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.IOException;
23  import java.io.UnsupportedEncodingException;
24  import java.util.Locale;
25  import java.util.Properties;
26  import java.util.ResourceBundle;
27  
28  import org.apache.maven.plugins.annotations.Mojo;
29  import org.apache.maven.plugins.annotations.Parameter;
30  import org.apache.maven.plugins.pmd.exec.CpdExecutor;
31  import org.apache.maven.plugins.pmd.exec.CpdRequest;
32  import org.apache.maven.plugins.pmd.exec.CpdResult;
33  import org.apache.maven.reporting.MavenReportException;
34  import org.apache.maven.toolchain.Toolchain;
35  
36  import net.sourceforge.pmd.cpd.JavaTokenizer;
37  import net.sourceforge.pmd.cpd.renderer.CPDRenderer;
38  
39  /**
40   * Creates a report for PMD's Copy/Paste Detector (CPD) tool.
41   * It can also generate a cpd results file in any of these formats: xml, csv or txt.
42   *
43   * <p>See <a href="https://pmd.github.io/latest/pmd_userdocs_cpd.html">Finding duplicated code</a>
44   * for more details.
45   *
46   * @author Mike Perham
47   * @version $Id$
48   * @since 2.0
49   */
50  @Mojo( name = "cpd", threadSafe = true )
51  public class CpdReport
52      extends AbstractPmdReport
53  {
54      /**
55       * The programming language to be analyzed by CPD. Valid values are currently <code>java</code>,
56       * <code>javascript</code> or <code>jsp</code>.
57       *
58       * @since 3.5
59       */
60      @Parameter( defaultValue = "java" )
61      private String language;
62  
63      /**
64       * The minimum number of tokens that need to be duplicated before it causes a violation.
65       */
66      @Parameter( property = "minimumTokens", defaultValue = "100" )
67      private int minimumTokens;
68  
69      /**
70       * Skip the CPD report generation. Most useful on the command line via "-Dcpd.skip=true".
71       *
72       * @since 2.1
73       */
74      @Parameter( property = "cpd.skip", defaultValue = "false" )
75      private boolean skip;
76  
77      /**
78       * If true, CPD ignores literal value differences when evaluating a duplicate block. This means that
79       * <code>foo=42;</code> and <code>foo=43;</code> will be seen as equivalent. You may want to run PMD with this
80       * option off to start with and then switch it on to see what it turns up.
81       *
82       * @since 2.5
83       */
84      @Parameter( property = "cpd.ignoreLiterals", defaultValue = "false" )
85      private boolean ignoreLiterals;
86  
87      /**
88       * Similar to <code>ignoreLiterals</code> but for identifiers; i.e., variable names, methods names, and so forth.
89       *
90       * @since 2.5
91       */
92      @Parameter( property = "cpd.ignoreIdentifiers", defaultValue = "false" )
93      private boolean ignoreIdentifiers;
94  
95      /**
96       * If true, CPD ignores annotations.
97       *
98       * @since 3.11.0
99       */
100     @Parameter( property = "cpd.ignoreAnnotations", defaultValue = "false" )
101     private boolean ignoreAnnotations;
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     /**
111      * {@inheritDoc}
112      */
113     public String getName( Locale locale )
114     {
115         return getBundle( locale ).getString( "report.cpd.name" );
116     }
117 
118     /**
119      * {@inheritDoc}
120      */
121     public String getDescription( Locale locale )
122     {
123         return getBundle( locale ).getString( "report.cpd.description" );
124     }
125 
126     /**
127      * {@inheritDoc}
128      */
129     @Override
130     public void executeReport( Locale locale )
131         throws MavenReportException
132     {
133         ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
134         try
135         {
136             Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
137 
138             generateMavenSiteReport( locale );
139         }
140         finally
141         {
142             Thread.currentThread().setContextClassLoader( origLoader );
143         }
144     }
145 
146     @Override
147     public boolean canGenerateReport()
148     {
149         if ( skip )
150         {
151             getLog().info( "Skipping CPD execution" );
152             return false;
153         }
154 
155         boolean result = super.canGenerateReport();
156         if ( result )
157         {
158             try
159             {
160                 executeCpd();
161                 if ( skipEmptyReport )
162                 {
163                     result = cpdResult.hasDuplications();
164                     if ( !result )
165                     {
166                         getLog().debug( "Skipping report since skipEmptyReport is true and there are no CPD issues." );
167                     }
168                 }
169             }
170             catch ( MavenReportException e )
171             {
172                 throw new RuntimeException( e );
173             }
174         }
175         return result;
176     }
177 
178     private void executeCpd()
179         throws MavenReportException
180     {
181         if ( cpdResult != null )
182         {
183             // CPD has already been run
184             getLog().debug( "CPD has already been run - skipping redundant execution." );
185             return;
186         }
187 
188         Properties languageProperties = new Properties();
189         if ( ignoreLiterals )
190         {
191             languageProperties.setProperty( JavaTokenizer.IGNORE_LITERALS, "true" );
192         }
193         if ( ignoreIdentifiers )
194         {
195             languageProperties.setProperty( JavaTokenizer.IGNORE_IDENTIFIERS, "true" );
196         }
197         if ( ignoreAnnotations )
198         {
199             languageProperties.setProperty( JavaTokenizer.IGNORE_ANNOTATIONS, "true" );
200         }
201         try
202         {
203             filesToProcess = getFilesToProcess();
204 
205             CpdRequest request = new CpdRequest();
206             request.setMinimumTokens( minimumTokens );
207             request.setLanguage( language );
208             request.setLanguageProperties( languageProperties );
209             request.setSourceEncoding( getInputEncoding() );
210             request.addFiles( filesToProcess.keySet() );
211 
212             request.setShowPmdLog( showPmdLog );
213             request.setLogLevel( determineCurrentRootLogLevel() );
214 
215             request.setExcludeFromFailureFile( excludeFromFailureFile );
216             request.setTargetDirectory( targetDirectory.getAbsolutePath() );
217             request.setOutputEncoding( getOutputEncoding() );
218             request.setFormat( format );
219             request.setIncludeXmlInSite( includeXmlInSite );
220             request.setReportOutputDirectory( getReportOutputDirectory().getAbsolutePath() );
221 
222             Toolchain tc = getToolchain();
223             if ( tc != null )
224             {
225                 getLog().info( "Toolchain in maven-pmd-plugin: " + tc );
226                 String javaExecutable = tc.findTool( "java" ); //NOI18N
227                 request.setJavaExecutable( javaExecutable );
228             }
229 
230             getLog().info( "PMD version: " + AbstractPmdReport.getPmdVersion() );
231             cpdResult = CpdExecutor.execute( request );
232         }
233         catch ( UnsupportedEncodingException e )
234         {
235             throw new MavenReportException( "Encoding '" + getInputEncoding() + "' is not supported.", e );
236         }
237         catch ( IOException e )
238         {
239             throw new MavenReportException( e.getMessage(), e );
240         }
241     }
242 
243     private void generateMavenSiteReport( Locale locale )
244     {
245         CpdReportGenerator gen = new CpdReportGenerator( getSink(), filesToProcess, getBundle( locale ),
246                 isAggregator() );
247         gen.generate( cpdResult.getDuplications() );
248     }
249 
250     /**
251      * {@inheritDoc}
252      */
253     public String getOutputName()
254     {
255         return "cpd";
256     }
257 
258     private static ResourceBundle getBundle( Locale locale )
259     {
260         return ResourceBundle.getBundle( "cpd-report", locale, CpdReport.class.getClassLoader() );
261     }
262 
263     /**
264      * Create and return the correct renderer for the output type.
265      *
266      * @return the renderer based on the configured output
267      * @throws org.apache.maven.reporting.MavenReportException if no renderer found for the output type
268      * @deprecated Use {@link CpdExecutor#createRenderer(String, String)} instead.
269      */
270     @Deprecated
271     public CPDRenderer createRenderer() throws MavenReportException
272     {
273         return CpdExecutor.createRenderer( format, getOutputEncoding() );
274     }
275 }