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