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