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 net.sourceforge.pmd.PMD;
23  import org.apache.maven.doxia.siterenderer.Renderer;
24  import org.apache.maven.model.ReportPlugin;
25  import org.apache.maven.plugins.annotations.Component;
26  import org.apache.maven.plugins.annotations.Parameter;
27  import org.apache.maven.project.MavenProject;
28  import org.apache.maven.reporting.AbstractMavenReport;
29  import org.codehaus.plexus.util.FileUtils;
30  import org.codehaus.plexus.util.PathTool;
31  import org.codehaus.plexus.util.ReaderFactory;
32  import org.codehaus.plexus.util.StringUtils;
33  
34  import java.io.File;
35  import java.io.IOException;
36  import java.util.ArrayList;
37  import java.util.Collection;
38  import java.util.Collections;
39  import java.util.HashSet;
40  import java.util.LinkedHashSet;
41  import java.util.List;
42  import java.util.Map;
43  import java.util.TreeMap;
44  
45  /**
46   * Base class for the PMD reports.
47   *
48   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
49   * @version $Id: AbstractPmdReport.html 901148 2014-03-11 20:12:51Z dennisl $
50   */
51  public abstract class AbstractPmdReport
52      extends AbstractMavenReport
53  {
54      /**
55       * The output directory for the intermediate XML report.
56       */
57      @Parameter( property = "project.build.directory", required = true )
58      protected File targetDirectory;
59  
60      /**
61       * The output directory for the final HTML report. Note that this parameter is only evaluated if the goal is run
62       * directly from the command line or during the default lifecycle. If the goal is run indirectly as part of a site
63       * generation, the output directory configured in the Maven Site Plugin is used instead.
64       */
65      @Parameter( property = "project.reporting.outputDirectory", required = true )
66      protected File outputDirectory;
67  
68      /**
69       * Site rendering component for generating the HTML report.
70       */
71      @Component
72      private Renderer siteRenderer;
73  
74      /**
75       * The project to analyse.
76       */
77      @Component
78      protected MavenProject project;
79  
80      /**
81       * Set the output format type, in addition to the HTML report.  Must be one of: "none",
82       * "csv", "xml", "txt" or the full class name of the PMD renderer to use.
83       * See the net.sourceforge.pmd.renderers package javadoc for available renderers.
84       * 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
91       * automatically if the jxr plugin is 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.
121      * Defaults to **\/*.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.<br/>
188      * <strong>Note:</strong> In versions 2.2 - 2.7.1 the default value for this
189      * was <code>true</code>. This was changed in version 3.0.
190      *
191      * @since 3.0
192      */
193     @Parameter( defaultValue = "false" )
194     protected boolean includeXmlInSite;
195 
196     /**
197      * Skip the PMD/CPD report generation if there are no violations or duplications found.
198      * Defaults to <code>true</code>.
199      *
200      * @since 3.1
201      */
202     @Parameter( defaultValue = "true" )
203     protected boolean skipEmptyReport;
204 
205     /** The files that are being analyzed. */
206     protected Map<File, PmdFileInfo> filesToProcess;
207 
208     /**
209      * {@inheritDoc}
210      */
211     protected MavenProject getProject()
212     {
213         return project;
214     }
215 
216     /**
217      * {@inheritDoc}
218      */
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" ) List<ReportPlugin> reportPlugins = project.getReportPlugins();
247                 for ( ReportPlugin plugin : reportPlugins )
248                 {
249                     String artifactId = plugin.getArtifactId();
250                     if ( "maven-jxr-plugin".equals( artifactId ) || "jxr-maven-plugin".equals( artifactId ) )
251                     {
252                         location = relativePath;
253                     }
254                 }
255             }
256 
257             if ( location == null )
258             {
259                 getLog().warn( "Unable to locate Source XRef to link to - DISABLED" );
260             }
261         }
262         return location;
263     }
264 
265     /**
266      * Convenience method to get the list of files where the PMD tool will be executed
267      *
268      * @return a List of the files where the PMD tool will be executed
269      * @throws java.io.IOException
270      */
271     protected Map<File, PmdFileInfo> getFilesToProcess()
272         throws IOException
273     {
274         if ( aggregate && !project.isExecutionRoot() )
275         {
276             return Collections.emptyMap();
277         }
278 
279         if ( excludeRoots == null )
280         {
281             excludeRoots = new File[0];
282         }
283 
284         Collection<File> excludeRootFiles = new HashSet<File>( excludeRoots.length );
285 
286         for ( File file : excludeRoots )
287         {
288             if ( file.isDirectory() )
289             {
290                 excludeRootFiles.add( file );
291             }
292         }
293 
294         List<PmdFileInfo> directories = new ArrayList<PmdFileInfo>();
295 
296         if ( compileSourceRoots != null )
297         {
298             for ( String root : compileSourceRoots )
299             {
300                 File sroot = new File( root );
301                 if ( sroot.exists() )
302                 {
303                     String sourceXref = constructXRefLocation( false );
304                     directories.add( new PmdFileInfo( project, sroot, sourceXref ) );
305                 }
306             }
307 
308         }
309         if ( includeTests )
310         {
311             if ( testSourceRoots != null )
312             {
313                 for ( String root : testSourceRoots )
314                 {
315                     File sroot = new File( root );
316                     if ( sroot.exists() )
317                     {
318                         String testXref = constructXRefLocation( true );
319                         directories.add( new PmdFileInfo( project, sroot, testXref ) );
320                     }
321                 }
322             }
323         }
324         if ( aggregate )
325         {
326             for ( MavenProject localProject : reactorProjects )
327             {
328                 @SuppressWarnings( "unchecked" ) List<String> localCompileSourceRoots =
329                     localProject.getCompileSourceRoots();
330                 for ( String root : localCompileSourceRoots )
331                 {
332                     File sroot = new File( root );
333                     if ( sroot.exists() )
334                     {
335                         String sourceXref = constructXRefLocation( false );
336                         directories.add( new PmdFileInfo( localProject, sroot, sourceXref ) );
337                     }
338                 }
339                 if ( includeTests )
340                 {
341                     @SuppressWarnings( "unchecked" ) List<String> localTestCompileSourceRoots =
342                         localProject.getTestCompileSourceRoots();
343                     for ( String root : localTestCompileSourceRoots )
344                     {
345                         File sroot = new File( root );
346                       if ( sroot.exists() )
347                       {
348                           String testXref = constructXRefLocation( true );
349                           directories.add( new PmdFileInfo( localProject, sroot, testXref ) );
350                       }
351                     }
352                 }
353             }
354 
355         }
356 
357         String excluding = getExcludes();
358         getLog().debug( "Exclusions: " + excluding );
359         String including = getIncludes();
360         getLog().debug( "Inclusions: " + including );
361 
362         Map<File, PmdFileInfo> files = new TreeMap<File, PmdFileInfo>();
363 
364         for ( PmdFileInfo finfo : directories )
365         {
366             getLog().debug( "Searching for files in directory " + finfo.getSourceDirectory().toString() );
367             File sourceDirectory = finfo.getSourceDirectory();
368             if ( sourceDirectory.isDirectory() && !excludeRootFiles.contains( sourceDirectory ) )
369             {
370                 @SuppressWarnings( "unchecked" ) List<File> newfiles =
371                     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         @SuppressWarnings( "unchecked" ) Collection<String> patterns =
409             new LinkedHashSet<String>( FileUtils.getDefaultExcludesAsList() );
410         if ( excludes != null )
411         {
412             patterns.addAll( excludes );
413         }
414         return StringUtils.join( patterns.iterator(), "," );
415     }
416 
417     protected boolean isHtml()
418     {
419         return "html".equals( format );
420     }
421     protected boolean isXml()
422     {
423         return "xml".equals( format );
424     }
425 
426     /**
427      * {@inheritDoc}
428      */
429     public boolean canGenerateReport()
430     {
431         if ( aggregate && !project.isExecutionRoot() )
432         {
433             return false;
434         }
435 
436         if ( "pom".equals( project.getPackaging() ) && !aggregate )
437         {
438             return false;
439         }
440 
441         // if format is XML, we need to output it even if the file list is empty
442         // so the "check" goals can check for failures
443         if ( isXml() )
444         {
445             return true;
446         }
447         try
448         {
449             filesToProcess = getFilesToProcess();
450             if ( filesToProcess.isEmpty() )
451             {
452                 return false;
453             }
454         }
455         catch ( IOException e )
456         {
457             getLog().error( e );
458         }
459         return true;
460     }
461 
462     /**
463      * {@inheritDoc}
464      */
465     protected String getOutputDirectory()
466     {
467         return outputDirectory.getAbsolutePath();
468     }
469 
470     protected String getSourceEncoding()
471     {
472         return sourceEncoding;
473     }
474 
475     /**
476      * Gets the effective reporting output files encoding.
477      *
478      * @return The effective reporting output file encoding, never <code>null</code>.
479      * @since 2.5
480      */
481     protected String getOutputEncoding()
482     {
483         return ( outputEncoding != null ) ? outputEncoding : ReaderFactory.UTF_8;
484     }
485 
486     static String getPmdVersion()
487     {
488         try
489         {
490             return (String) PMD.class.getField( "VERSION" ).get( null );
491         }
492         catch ( IllegalAccessException e )
493         {
494             throw new RuntimeException( "PMD VERSION field not accessible", e );
495         }
496         catch ( NoSuchFieldException e )
497         {
498             throw new RuntimeException( "PMD VERSION field not found", e );
499         }
500     }
501 }