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