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