View Javadoc
1   package org.apache.maven.plugin.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.File;
23  import java.io.IOException;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.HashSet;
28  import java.util.LinkedHashSet;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.TreeMap;
32  
33  import net.sourceforge.pmd.PMD;
34  
35  import org.apache.maven.doxia.siterenderer.Renderer;
36  import org.apache.maven.model.ReportPlugin;
37  import org.apache.maven.plugins.annotations.Component;
38  import org.apache.maven.plugins.annotations.Parameter;
39  import org.apache.maven.project.MavenProject;
40  import org.apache.maven.reporting.AbstractMavenReport;
41  import org.codehaus.plexus.util.FileUtils;
42  import org.codehaus.plexus.util.PathTool;
43  import org.codehaus.plexus.util.ReaderFactory;
44  import org.codehaus.plexus.util.StringUtils;
45  
46  /**
47   * Base class for the PMD reports.
48   *
49   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
50   * @version $Id: AbstractPmdReport.html 938498 2015-01-31 17:43:24Z michaelo $
51   */
52  public abstract class AbstractPmdReport
53      extends AbstractMavenReport
54  {
55      /**
56       * The output directory for the intermediate XML report.
57       */
58      @Parameter( property = "project.build.directory", required = true )
59      protected File targetDirectory;
60  
61      /**
62       * The output directory for the final HTML report. Note that this parameter is only evaluated if the goal is run
63       * directly from the command line or during the default lifecycle. If the goal is run indirectly as part of a site
64       * generation, the output directory configured in the Maven Site Plugin is used instead.
65       */
66      @Parameter( property = "project.reporting.outputDirectory", required = true )
67      protected File outputDirectory;
68  
69      /**
70       * Site rendering component for generating the HTML report.
71       */
72      @Component
73      private Renderer siteRenderer;
74  
75      /**
76       * The project to analyse.
77       */
78      @Parameter( defaultValue = "${project}", readonly = true, required = true )
79      protected MavenProject project;
80  
81      /**
82       * Set the output format type, in addition to the HTML report. Must be one of: "none", "csv", "xml", "txt" or the
83       * full class name of the PMD renderer to use. See the net.sourceforge.pmd.renderers package javadoc for available
84       * renderers. XML is required if the pmd:check goal is being used.
85       */
86      @Parameter( property = "format", defaultValue = "xml" )
87      protected String format = "xml";
88  
89      /**
90       * Link the violation line numbers to the source xref. Links will be created automatically if the jxr plugin is
91       * being used.
92       */
93      @Parameter( property = "linkXRef", defaultValue = "true" )
94      private boolean linkXRef;
95  
96      /**
97       * Location of the Xrefs to link to.
98       */
99      @Parameter( defaultValue = "${project.reporting.outputDirectory}/xref" )
100     private File xrefLocation;
101 
102     /**
103      * Location of the Test Xrefs to link to.
104      */
105     @Parameter( defaultValue = "${project.reporting.outputDirectory}/xref-test" )
106     private File xrefTestLocation;
107 
108     /**
109      * A list of files to exclude from checking. Can contain Ant-style wildcards and double wildcards. Note that these
110      * exclusion patterns only operate on the path of a source file relative to its source root directory. In other
111      * words, files are excluded based on their package and/or class name. If you want to exclude entire source root
112      * directories, use the parameter <code>excludeRoots</code> instead.
113      *
114      * @since 2.2
115      */
116     @Parameter
117     private List<String> excludes;
118 
119     /**
120      * A list of files to include from checking. Can contain Ant-style wildcards and double wildcards. Defaults to
121      * **\/*.java.
122      *
123      * @since 2.2
124      */
125     @Parameter
126     private List<String> includes;
127 
128     /**
129      * The directories containing the sources to be compiled.
130      */
131     @Parameter( property = "project.compileSourceRoots", required = true, readonly = true )
132     private List<String> compileSourceRoots;
133 
134     /**
135      * The directories containing the test-sources to be compiled.
136      */
137     @Parameter( property = "project.testCompileSourceRoots", required = true, readonly = true )
138     private List<String> testSourceRoots;
139 
140     /**
141      * The project source directories that should be excluded.
142      *
143      * @since 2.2
144      */
145     @Parameter
146     private File[] excludeRoots;
147 
148     /**
149      * Run PMD on the tests.
150      *
151      * @since 2.2
152      */
153     @Parameter( defaultValue = "false" )
154     protected boolean includeTests;
155 
156     /**
157      * Whether to build an aggregated report at the root, or build individual reports.
158      *
159      * @since 2.2
160      */
161     @Parameter( property = "aggregate", defaultValue = "false" )
162     protected boolean aggregate;
163 
164     /**
165      * The file encoding to use when reading the Java sources.
166      *
167      * @since 2.3
168      */
169     @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
170     private String sourceEncoding;
171 
172     /**
173      * The file encoding when writing non-HTML reports.
174      *
175      * @since 2.5
176      */
177     @Parameter( property = "outputEncoding", defaultValue = "${project.reporting.outputEncoding}" )
178     private String outputEncoding;
179 
180     /**
181      * The projects in the reactor for aggregation report.
182      */
183     @Parameter( property = "reactorProjects", readonly = true )
184     protected List<MavenProject> reactorProjects;
185 
186     /**
187      * Whether to include the xml files generated by PMD/CPD in the site.
188      *
189      * @since 3.0
190      */
191     @Parameter( defaultValue = "false" )
192     protected boolean includeXmlInSite;
193 
194     /**
195      * Skip the PMD/CPD report generation if there are no violations or duplications found. Defaults to
196      * <code>true</code>.
197      *
198      * @since 3.1
199      */
200     @Parameter( defaultValue = "true" )
201     protected boolean skipEmptyReport;
202 
203     /** The files that are being analyzed. */
204     protected Map<File, PmdFileInfo> filesToProcess;
205 
206     /**
207      * {@inheritDoc}
208      */
209     @Override
210     protected MavenProject getProject()
211     {
212         return project;
213     }
214 
215     /**
216      * {@inheritDoc}
217      */
218     @Override
219     protected Renderer getSiteRenderer()
220     {
221         return siteRenderer;
222     }
223 
224     protected String constructXRefLocation( boolean test )
225     {
226         String location = null;
227         if ( linkXRef )
228         {
229             File xrefLoc = test ? xrefTestLocation : xrefLocation;
230 
231             String relativePath =
232                 PathTool.getRelativePath( outputDirectory.getAbsolutePath(), xrefLoc.getAbsolutePath() );
233             if ( StringUtils.isEmpty( relativePath ) )
234             {
235                 relativePath = ".";
236             }
237             relativePath = relativePath + "/" + xrefLoc.getName();
238             if ( xrefLoc.exists() )
239             {
240                 // XRef was already generated by manual execution of a lifecycle binding
241                 location = relativePath;
242             }
243             else
244             {
245                 // Not yet generated - check if the report is on its way
246                 @SuppressWarnings( "unchecked" )
247                 List<ReportPlugin> reportPlugins = project.getReportPlugins();
248                 for ( ReportPlugin plugin : reportPlugins )
249                 {
250                     String artifactId = plugin.getArtifactId();
251                     if ( "maven-jxr-plugin".equals( artifactId ) || "jxr-maven-plugin".equals( artifactId ) )
252                     {
253                         location = relativePath;
254                     }
255                 }
256             }
257 
258             if ( location == null )
259             {
260                 getLog().warn( "Unable to locate Source XRef to link to - DISABLED" );
261             }
262         }
263         return location;
264     }
265 
266     /**
267      * Convenience method to get the list of files where the PMD tool will be executed
268      *
269      * @return a List of the files where the PMD tool will be executed
270      * @throws java.io.IOException
271      */
272     protected Map<File, PmdFileInfo> getFilesToProcess()
273         throws IOException
274     {
275         if ( aggregate && !project.isExecutionRoot() )
276         {
277             return Collections.emptyMap();
278         }
279 
280         if ( excludeRoots == null )
281         {
282             excludeRoots = new File[0];
283         }
284 
285         Collection<File> excludeRootFiles = new HashSet<File>( excludeRoots.length );
286 
287         for ( File file : excludeRoots )
288         {
289             if ( file.isDirectory() )
290             {
291                 excludeRootFiles.add( file );
292             }
293         }
294 
295         List<PmdFileInfo> directories = new ArrayList<PmdFileInfo>();
296 
297         if ( compileSourceRoots != null )
298         {
299             for ( String root : compileSourceRoots )
300             {
301                 File sroot = new File( root );
302                 if ( sroot.exists() )
303                 {
304                     String sourceXref = constructXRefLocation( false );
305                     directories.add( new PmdFileInfo( project, sroot, sourceXref ) );
306                 }
307             }
308 
309         }
310         if ( includeTests )
311         {
312             if ( testSourceRoots != null )
313             {
314                 for ( String root : testSourceRoots )
315                 {
316                     File sroot = new File( root );
317                     if ( sroot.exists() )
318                     {
319                         String testXref = constructXRefLocation( true );
320                         directories.add( new PmdFileInfo( project, sroot, testXref ) );
321                     }
322                 }
323             }
324         }
325         if ( aggregate )
326         {
327             for ( MavenProject localProject : reactorProjects )
328             {
329                 @SuppressWarnings( "unchecked" )
330                 List<String> localCompileSourceRoots = localProject.getCompileSourceRoots();
331                 for ( String root : localCompileSourceRoots )
332                 {
333                     File sroot = new File( root );
334                     if ( sroot.exists() )
335                     {
336                         String sourceXref = constructXRefLocation( false );
337                         directories.add( new PmdFileInfo( localProject, sroot, sourceXref ) );
338                     }
339                 }
340                 if ( includeTests )
341                 {
342                     @SuppressWarnings( "unchecked" )
343                     List<String> localTestCompileSourceRoots = localProject.getTestCompileSourceRoots();
344                     for ( String root : localTestCompileSourceRoots )
345                     {
346                         File sroot = new File( root );
347                         if ( sroot.exists() )
348                         {
349                             String testXref = constructXRefLocation( true );
350                             directories.add( new PmdFileInfo( localProject, sroot, testXref ) );
351                         }
352                     }
353                 }
354             }
355 
356         }
357 
358         String excluding = getExcludes();
359         getLog().debug( "Exclusions: " + excluding );
360         String including = getIncludes();
361         getLog().debug( "Inclusions: " + including );
362 
363         Map<File, PmdFileInfo> files = new TreeMap<File, PmdFileInfo>();
364 
365         for ( PmdFileInfo finfo : directories )
366         {
367             getLog().debug( "Searching for files in directory " + finfo.getSourceDirectory().toString() );
368             File sourceDirectory = finfo.getSourceDirectory();
369             if ( sourceDirectory.isDirectory() && !excludeRootFiles.contains( sourceDirectory ) )
370             {
371                 List<File> newfiles = FileUtils.getFiles( sourceDirectory, including, excluding );
372                 for ( File newfile : newfiles )
373                 {
374                     files.put( newfile.getCanonicalFile(), finfo );
375                 }
376             }
377         }
378 
379         return files;
380     }
381 
382     /**
383      * Gets the comma separated list of effective include patterns.
384      *
385      * @return The comma separated list of effective include patterns, never <code>null</code>.
386      */
387     private String getIncludes()
388     {
389         Collection<String> patterns = new LinkedHashSet<String>();
390         if ( includes != null )
391         {
392             patterns.addAll( includes );
393         }
394         if ( patterns.isEmpty() )
395         {
396             patterns.add( "**/*.java" );
397         }
398         return StringUtils.join( patterns.iterator(), "," );
399     }
400 
401     /**
402      * Gets the comma separated list of effective exclude patterns.
403      *
404      * @return The comma separated list of effective exclude patterns, never <code>null</code>.
405      */
406     private String getExcludes()
407     {
408         Collection<String> patterns = new LinkedHashSet<String>( FileUtils.getDefaultExcludesAsList() );
409         if ( excludes != null )
410         {
411             patterns.addAll( excludes );
412         }
413         return StringUtils.join( patterns.iterator(), "," );
414     }
415 
416     protected boolean isHtml()
417     {
418         return "html".equals( format );
419     }
420 
421     protected boolean isXml()
422     {
423         return "xml".equals( format );
424     }
425 
426     /**
427      * {@inheritDoc}
428      */
429     @Override
430     public boolean canGenerateReport()
431     {
432         if ( aggregate && !project.isExecutionRoot() )
433         {
434             return false;
435         }
436 
437         if ( "pom".equals( project.getPackaging() ) && !aggregate )
438         {
439             return false;
440         }
441 
442         // if format is XML, we need to output it even if the file list is empty
443         // so the "check" goals can check for failures
444         if ( isXml() )
445         {
446             return true;
447         }
448         try
449         {
450             filesToProcess = getFilesToProcess();
451             if ( filesToProcess.isEmpty() )
452             {
453                 return false;
454             }
455         }
456         catch ( IOException e )
457         {
458             getLog().error( e );
459         }
460         return true;
461     }
462 
463     /**
464      * {@inheritDoc}
465      */
466     @Override
467     protected String getOutputDirectory()
468     {
469         return outputDirectory.getAbsolutePath();
470     }
471 
472     protected String getSourceEncoding()
473     {
474         return sourceEncoding;
475     }
476 
477     /**
478      * Gets the effective reporting output files encoding.
479      *
480      * @return The effective reporting output file encoding, never <code>null</code>.
481      * @since 2.5
482      */
483     protected String getOutputEncoding()
484     {
485         return ( outputEncoding != null ) ? outputEncoding : ReaderFactory.UTF_8;
486     }
487 
488     static String getPmdVersion()
489     {
490         try
491         {
492             return (String) PMD.class.getField( "VERSION" ).get( null );
493         }
494         catch ( IllegalAccessException e )
495         {
496             throw new RuntimeException( "PMD VERSION field not accessible", e );
497         }
498         catch ( NoSuchFieldException e )
499         {
500             throw new RuntimeException( "PMD VERSION field not found", e );
501         }
502     }
503 }