View Javadoc
1   package org.apache.maven.plugins.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.FileNotFoundException;
24  import java.io.FileOutputStream;
25  import java.io.IOException;
26  import java.io.OutputStreamWriter;
27  import java.io.PrintStream;
28  import java.io.Writer;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Locale;
34  import java.util.Properties;
35  import java.util.ResourceBundle;
36  
37  import org.apache.maven.doxia.sink.Sink;
38  import org.apache.maven.execution.MavenSession;
39  import org.apache.maven.plugin.MojoExecutionException;
40  import org.apache.maven.plugins.annotations.Component;
41  import org.apache.maven.plugins.annotations.Mojo;
42  import org.apache.maven.plugins.annotations.Parameter;
43  import org.apache.maven.plugins.annotations.ResolutionScope;
44  import org.apache.maven.project.DefaultProjectBuildingRequest;
45  import org.apache.maven.project.MavenProject;
46  import org.apache.maven.project.ProjectBuildingRequest;
47  import org.apache.maven.reporting.MavenReportException;
48  import org.apache.maven.shared.artifact.filter.resolve.AndFilter;
49  import org.apache.maven.shared.artifact.filter.resolve.ExclusionsFilter;
50  import org.apache.maven.shared.artifact.filter.resolve.ScopeFilter;
51  import org.apache.maven.shared.artifact.filter.resolve.TransformableFilter;
52  import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResult;
53  import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver;
54  import org.codehaus.plexus.resource.ResourceManager;
55  import org.codehaus.plexus.resource.loader.FileResourceCreationException;
56  import org.codehaus.plexus.resource.loader.FileResourceLoader;
57  import org.codehaus.plexus.resource.loader.ResourceNotFoundException;
58  import org.codehaus.plexus.util.FileUtils;
59  import org.codehaus.plexus.util.ReaderFactory;
60  import org.codehaus.plexus.util.StringUtils;
61  
62  import net.sourceforge.pmd.PMD;
63  import net.sourceforge.pmd.PMDConfiguration;
64  import net.sourceforge.pmd.Report;
65  import net.sourceforge.pmd.RuleContext;
66  import net.sourceforge.pmd.RulePriority;
67  import net.sourceforge.pmd.RuleSetFactory;
68  import net.sourceforge.pmd.RuleSetNotFoundException;
69  import net.sourceforge.pmd.RuleSetReferenceId;
70  import net.sourceforge.pmd.RuleViolation;
71  import net.sourceforge.pmd.benchmark.Benchmarker;
72  import net.sourceforge.pmd.benchmark.TextReport;
73  import net.sourceforge.pmd.lang.LanguageRegistry;
74  import net.sourceforge.pmd.lang.LanguageVersion;
75  import net.sourceforge.pmd.renderers.CSVRenderer;
76  import net.sourceforge.pmd.renderers.HTMLRenderer;
77  import net.sourceforge.pmd.renderers.Renderer;
78  import net.sourceforge.pmd.renderers.TextRenderer;
79  import net.sourceforge.pmd.renderers.XMLRenderer;
80  import net.sourceforge.pmd.util.ResourceLoader;
81  import net.sourceforge.pmd.util.datasource.DataSource;
82  import net.sourceforge.pmd.util.datasource.FileDataSource;
83  
84  /**
85   * Creates a PMD report.
86   *
87   * @author Brett Porter
88   * @version $Id$
89   * @since 2.0
90   */
91  @Mojo( name = "pmd", threadSafe = true, requiresDependencyResolution = ResolutionScope.TEST )
92  public class PmdReport
93      extends AbstractPmdReport
94  {
95      /**
96       * The target JDK to analyze based on. Should match the source used in the compiler plugin. Valid values are
97       * currently <code>1.3</code>, <code>1.4</code>, <code>1.5</code>, <code>1.6</code>, <code>1.7</code>,
98       * <code>1.8</code>, <code>9</code>, <code>10</code>, <code>11</code>, and <code>12</code>.
99       * <p>
100      *   <b>Note:</b> this parameter is only used if the language parameter is set to <code>java</code>.
101      * </p>
102      */
103     @Parameter( property = "targetJdk", defaultValue = "${maven.compiler.source}" )
104     private String targetJdk;
105 
106     /**
107      * The programming language to be analyzed by PMD. Valid values are currently <code>java</code>,
108      * <code>javascript</code> and <code>jsp</code>.
109      *
110      * @since 3.0
111      */
112     @Parameter( defaultValue = "java" )
113     private String language;
114 
115     /**
116      * The rule priority threshold; rules with lower priority than this will not be evaluated.
117      *
118      * @since 2.1
119      */
120     @Parameter( property = "minimumPriority", defaultValue = "5" )
121     private int minimumPriority = 5;
122 
123     /**
124      * Skip the PMD report generation. Most useful on the command line via "-Dpmd.skip=true".
125      *
126      * @since 2.1
127      */
128     @Parameter( property = "pmd.skip", defaultValue = "false" )
129     private boolean skip;
130 
131     /**
132      * The PMD rulesets to use. See the
133      * <a href="https://pmd.github.io/latest/pmd_rules_java.html">Stock Java Rulesets</a> for a
134      * list of available rules.
135      * Defaults to a custom ruleset provided by this maven plugin
136      * (<code>/rulesets/java/maven-pmd-plugin-default.xml</code>).
137      */
138     @Parameter
139     private String[] rulesets = new String[] { "/rulesets/java/maven-pmd-plugin-default.xml" };
140 
141     /**
142      * Controls whether the project's compile/test classpath should be passed to PMD to enable its type resolution
143      * feature.
144      *
145      * @since 3.0
146      */
147     @Parameter( property = "pmd.typeResolution", defaultValue = "true" )
148     private boolean typeResolution;
149 
150     /**
151      * Controls whether PMD will track benchmark information.
152      *
153      * @since 3.1
154      */
155     @Parameter( property = "pmd.benchmark", defaultValue = "false" )
156     private boolean benchmark;
157 
158     /**
159      * Benchmark output filename.
160      *
161      * @since 3.1
162      */
163     @Parameter( property = "pmd.benchmarkOutputFilename",
164                     defaultValue = "${project.build.directory}/pmd-benchmark.txt" )
165     private String benchmarkOutputFilename;
166 
167     /**
168      * Source level marker used to indicate whether a RuleViolation should be suppressed. If it is not set, PMD's
169      * default will be used, which is <code>NOPMD</code>. See also <a
170      * href="https://pmd.github.io/latest/pmd_userdocs_suppressing_warnings.html">PMD &#x2013; Suppressing warnings</a>.
171      *
172      * @since 3.4
173      */
174     @Parameter( property = "pmd.suppressMarker" )
175     private String suppressMarker;
176 
177     /**
178      */
179     @Component
180     private ResourceManager locator;
181 
182     /** The PMD renderer for collecting violations. */
183     private PmdCollectingRenderer renderer;
184 
185     /** Helper to exclude violations given as a properties file. */
186     private final ExcludeViolationsFromFile excludeFromFile = new ExcludeViolationsFromFile();
187 
188     /**
189      * per default pmd executions error are ignored to not break the whole
190      *
191      * @since 3.1
192      */
193     @Parameter( property = "pmd.skipPmdError", defaultValue = "true" )
194     private boolean skipPmdError;
195 
196     /**
197      * Enables the analysis cache, which speeds up PMD. This
198      * requires a cache file, that contains the results of the last
199      * PMD run. Thus the cache is only effective, if this file is
200      * not cleaned between runs.
201      *
202      * @since 3.8
203      */
204     @Parameter( property = "pmd.analysisCache", defaultValue = "false" )
205     private boolean analysisCache;
206 
207     /**
208      * The location of the analysis cache, if it is enabled.
209      * This file contains the results of the last PMD run and must not be cleaned
210      * between consecutive PMD runs. Otherwise the cache is not in use.
211      * If the file doesn't exist, PMD executes as if there is no cache enabled and
212      * all files are analyzed. Otherwise only changed files will be analyzed again.
213      *
214      * @since 3.8
215      */
216     @Parameter( property = "pmd.analysisCacheLocation", defaultValue = "${project.build.directory}/pmd/pmd.cache" )
217     private String analysisCacheLocation;
218 
219     /**
220      * Also render processing errors into the HTML report.
221      * Processing errors are problems, that PMD encountered while executing the rules.
222      * It can be parsing errors or exceptions during rule execution.
223      * Processing errors indicate a bug in PMD and the information provided help in
224      * reporting and fixing bugs in PMD.
225      *
226      * @since 3.9.0
227      */
228     @Parameter( property = "pmd.renderProcessingErrors", defaultValue = "true" )
229     private boolean renderProcessingErrors = true;
230 
231     /**
232      * Also render the rule priority into the HTML report.
233      *
234      * @since 3.10.0
235      */
236     @Parameter( property = "pmd.renderRuleViolationPriority", defaultValue = "true" )
237     private boolean renderRuleViolationPriority = true;
238 
239     /**
240      * Add a section in the HTML report, that groups the found violations by rule priority
241      * in addition to grouping by file.
242      *
243      * @since 3.12.0
244      */
245     @Parameter( property = "pmd.renderViolationsByPriority", defaultValue = "true" )
246     private boolean renderViolationsByPriority = true;
247 
248     @Component
249     private DependencyResolver dependencyResolver;
250 
251     @Parameter( defaultValue = "${session}", required = true, readonly = true )
252     private MavenSession session;
253 
254     /**
255      * {@inheritDoc}
256      */
257     @Override
258     public String getName( Locale locale )
259     {
260         return getBundle( locale ).getString( "report.pmd.name" );
261     }
262 
263     /**
264      * {@inheritDoc}
265      */
266     @Override
267     public String getDescription( Locale locale )
268     {
269         return getBundle( locale ).getString( "report.pmd.description" );
270     }
271 
272     /**
273      * Configures the PMD rulesets to be used directly.
274      * Note: Usually the rulesets are configured via the property.
275      *
276      * @param rulesets the PMD rulesets to be used.
277      * @see #rulesets
278      */
279     public void setRulesets( String[] rulesets )
280     {
281         this.rulesets = Arrays.copyOf( rulesets, rulesets.length );
282     }
283 
284     /**
285      * {@inheritDoc}
286      */
287     @Override
288     public void executeReport( Locale locale )
289         throws MavenReportException
290     {
291         try
292         {
293             execute( locale );
294         }
295         finally
296         {
297             if ( getSink() != null )
298             {
299                 getSink().close();
300             }
301         }
302     }
303 
304     private void execute( Locale locale )
305         throws MavenReportException
306     {
307         if ( !skip && canGenerateReport() )
308         {
309             ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
310             try
311             {
312                 Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
313 
314                 Report report = generateReport( locale );
315 
316                 if ( !isHtml() && !isXml() )
317                 {
318                     writeNonHtml( report );
319                 }
320             }
321             finally
322             {
323                 Thread.currentThread().setContextClassLoader( origLoader );
324             }
325         }
326     }
327 
328     @Override
329     public boolean canGenerateReport()
330     {
331         if ( skip )
332         {
333             return false;
334         }
335 
336         boolean result = super.canGenerateReport();
337         if ( result )
338         {
339             try
340             {
341                 executePmdWithClassloader();
342                 if ( skipEmptyReport )
343                 {
344                     result = renderer.hasViolations();
345                     if ( result )
346                     {
347                         getLog().debug( "Skipping report since skipEmptyReport is true and"
348                                             + "there are no PMD violations." );
349                     }
350                 }
351             }
352             catch ( MavenReportException e )
353             {
354                 throw new RuntimeException( e );
355             }
356         }
357         return result;
358     }
359 
360     private void executePmdWithClassloader()
361         throws MavenReportException
362     {
363         ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
364         try
365         {
366             Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
367             executePmd();
368         }
369         finally
370         {
371             Thread.currentThread().setContextClassLoader( origLoader );
372         }
373     }
374 
375     private void executePmd()
376         throws MavenReportException
377     {
378         setupPmdLogging();
379 
380         if ( renderer != null )
381         {
382             // PMD has already been run
383             getLog().debug( "PMD has already been run - skipping redundant execution." );
384             return;
385         }
386 
387         try
388         {
389             excludeFromFile.loadExcludeFromFailuresData( excludeFromFailureFile );
390         }
391         catch ( MojoExecutionException e )
392         {
393             throw new MavenReportException( "Unable to load exclusions", e );
394         }
395 
396         // configure ResourceManager
397         locator.addSearchPath( FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath() );
398         locator.addSearchPath( "url", "" );
399         locator.setOutputDirectory( targetDirectory );
400 
401         renderer = new PmdCollectingRenderer();
402         PMDConfiguration pmdConfiguration = getPMDConfiguration();
403 
404         String[] sets = new String[rulesets.length];
405         try
406         {
407             for ( int idx = 0; idx < rulesets.length; idx++ )
408             {
409                 String set = rulesets[idx];
410                 getLog().debug( "Preparing ruleset: " + set );
411                 RuleSetReferenceId id = new RuleSetReferenceId( set );
412                 File ruleset = locator.getResourceAsFile( id.getRuleSetFileName(), getLocationTemp( set ) );
413                 if ( null == ruleset )
414                 {
415                     throw new MavenReportException( "Could not resolve " + set );
416                 }
417                 sets[idx] = ruleset.getAbsolutePath();
418             }
419         }
420         catch ( ResourceNotFoundException e )
421         {
422             throw new MavenReportException( e.getMessage(), e );
423         }
424         catch ( FileResourceCreationException e )
425         {
426             throw new MavenReportException( e.getMessage(), e );
427         }
428         pmdConfiguration.setRuleSets( StringUtils.join( sets, "," ) );
429 
430         try
431         {
432             if ( filesToProcess == null )
433             {
434                 filesToProcess = getFilesToProcess();
435             }
436 
437             if ( filesToProcess.isEmpty() && !"java".equals( language ) )
438             {
439                 getLog().warn( "No files found to process. Did you add your additional source folders like javascript?"
440                                    + " (see also build-helper-maven-plugin)" );
441             }
442         }
443         catch ( IOException e )
444         {
445             throw new MavenReportException( "Can't get file list", e );
446         }
447 
448         String encoding = getSourceEncoding();
449         if ( StringUtils.isEmpty( encoding ) )
450         {
451             encoding = ReaderFactory.FILE_ENCODING;
452             if ( !filesToProcess.isEmpty() )
453             {
454                 getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
455                                + ", i.e. build is platform dependent!" );
456             }
457         }
458         pmdConfiguration.setSourceEncoding( encoding );
459 
460         List<DataSource> dataSources = new ArrayList<>( filesToProcess.size() );
461         for ( File f : filesToProcess.keySet() )
462         {
463             dataSources.add( new FileDataSource( f ) );
464         }
465 
466         if ( sets.length > 0 )
467         {
468             processFilesWithPMD( pmdConfiguration, dataSources );
469         }
470         else
471         {
472             getLog().debug( "Skipping PMD execution as no rulesets are defined." );
473         }
474 
475         if ( renderer.hasErrors() )
476         {
477             if ( !skipPmdError )
478             {
479                 getLog().error( "PMD processing errors:" );
480                 getLog().error( renderer.getErrorsAsString( getLog().isDebugEnabled() ) );
481                 throw new MavenReportException( "Found " + renderer.getErrors().size() + " PMD processing errors" );
482             }
483             getLog().warn( "There are " + renderer.getErrors().size() + " PMD processing errors:" );
484             getLog().warn( renderer.getErrorsAsString( getLog().isDebugEnabled() ) );
485         }
486 
487         removeExcludedViolations( renderer.getViolations() );
488 
489         // if format is XML, we need to output it even if the file list is empty or we have no violations
490         // so the "check" goals can check for violations
491         if ( isXml() && renderer != null )
492         {
493             writeNonHtml( renderer.asReport() );
494         }
495 
496         if ( benchmark )
497         {
498             try ( PrintStream benchmarkFileStream = new PrintStream( benchmarkOutputFilename ) )
499             {
500                 ( new TextReport() ).generate( Benchmarker.values(), benchmarkFileStream );
501             }
502             catch ( FileNotFoundException fnfe )
503             {
504                 getLog().error( "Unable to generate benchmark file: " + benchmarkOutputFilename, fnfe );
505             }
506         }
507     }
508 
509     private void removeExcludedViolations( List<RuleViolation> violations )
510     {
511         getLog().debug( "Removing excluded violations. Using " + excludeFromFile.countExclusions()
512             + " configured exclusions." );
513         int violationsBefore = violations.size();
514 
515         Iterator<RuleViolation> iterator = violations.iterator();
516         while ( iterator.hasNext() )
517         {
518             RuleViolation rv = iterator.next();
519             if ( excludeFromFile.isExcludedFromFailure( rv ) )
520             {
521                 iterator.remove();
522             }
523         }
524 
525         int numberOfExcludedViolations = violationsBefore - violations.size();
526         getLog().debug( "Excluded " + numberOfExcludedViolations + " violations." );
527     }
528 
529     private void processFilesWithPMD( PMDConfiguration pmdConfiguration, List<DataSource> dataSources )
530             throws MavenReportException
531     {
532         RuleSetFactory ruleSetFactory = new RuleSetFactory( new ResourceLoader(),
533                 RulePriority.valueOf( this.minimumPriority ), true, true );
534         try
535         {
536             // load the ruleset once to log out any deprecated rules as warnings
537             ruleSetFactory.createRuleSets( pmdConfiguration.getRuleSets() );
538         }
539         catch ( RuleSetNotFoundException e1 )
540         {
541             throw new MavenReportException( "The ruleset could not be loaded", e1 );
542         }
543 
544         try
545         {
546             getLog().debug( "Executing PMD..." );
547             RuleContext ruleContext = new RuleContext();
548             PMD.processFiles( pmdConfiguration, ruleSetFactory, dataSources, ruleContext,
549                               Arrays.<Renderer>asList( renderer ) );
550 
551             if ( getLog().isDebugEnabled() )
552             {
553                 getLog().debug( "PMD finished. Found " + renderer.getViolations().size() + " violations." );
554             }
555         }
556         catch ( Exception e )
557         {
558             String message = "Failure executing PMD: " + e.getLocalizedMessage();
559             if ( !skipPmdError )
560             {
561                 throw new MavenReportException( message, e );
562             }
563             getLog().warn( message, e );
564         }
565     }
566 
567     private Report generateReport( Locale locale )
568         throws MavenReportException
569     {
570         Sink sink = getSink();
571         PmdReportGenerator doxiaRenderer = new PmdReportGenerator( getLog(), sink, getBundle( locale ), aggregate );
572         doxiaRenderer.setRenderRuleViolationPriority( renderRuleViolationPriority );
573         doxiaRenderer.setRenderViolationsByPriority( renderViolationsByPriority );
574         doxiaRenderer.setFiles( filesToProcess );
575         doxiaRenderer.setViolations( renderer.getViolations() );
576         if ( renderProcessingErrors )
577         {
578             doxiaRenderer.setProcessingErrors( renderer.getErrors() );
579         }
580 
581         try
582         {
583             doxiaRenderer.beginDocument();
584             doxiaRenderer.render();
585             doxiaRenderer.endDocument();
586         }
587         catch ( IOException e )
588         {
589             getLog().warn( "Failure creating the report: " + e.getLocalizedMessage(), e );
590         }
591 
592         return renderer.asReport();
593     }
594 
595     /**
596      * Convenience method to get the location of the specified file name.
597      *
598      * @param name the name of the file whose location is to be resolved
599      * @return a String that contains the absolute file name of the file
600      */
601     protected String getLocationTemp( String name )
602     {
603         String loc = name;
604         if ( loc.indexOf( '/' ) != -1 )
605         {
606             loc = loc.substring( loc.lastIndexOf( '/' ) + 1 );
607         }
608         if ( loc.indexOf( '\\' ) != -1 )
609         {
610             loc = loc.substring( loc.lastIndexOf( '\\' ) + 1 );
611         }
612 
613         // MPMD-127 in the case that the rules are defined externally on a url
614         // we need to replace some special url characters that cannot be
615         // used in filenames on disk or produce ackward filenames.
616         // replace all occurrences of the following characters: ? : & = %
617         loc = loc.replaceAll( "[\\?\\:\\&\\=\\%]", "_" );
618 
619         if ( !loc.endsWith( ".xml" ) )
620         {
621             loc = loc + ".xml";
622         }
623 
624         getLog().debug( "Before: " + name + " After: " + loc );
625         return loc;
626     }
627 
628     /**
629      * Use the PMD renderers to render in any format aside from HTML.
630      *
631      * @param report
632      * @throws MavenReportException
633      */
634     private void writeNonHtml( Report report )
635         throws MavenReportException
636     {
637         Renderer r = createRenderer();
638 
639         if ( r == null )
640         {
641             return;
642         }
643 
644         File targetFile = new File( targetDirectory, "pmd." + format );
645         try ( Writer writer = new OutputStreamWriter( new FileOutputStream( targetFile ), getOutputEncoding() ) )
646         {
647             targetDirectory.mkdirs();
648 
649             r.setWriter( writer );
650             r.start();
651             r.renderFileReport( report );
652             r.end();
653             r.flush();
654 
655             if ( includeXmlInSite )
656             {
657                 File siteDir = getReportOutputDirectory();
658                 siteDir.mkdirs();
659                 FileUtils.copyFile( targetFile, new File( siteDir, "pmd." + format ) );
660             }
661         }
662         catch ( IOException ioe )
663         {
664             throw new MavenReportException( ioe.getMessage(), ioe );
665         }
666     }
667 
668     /**
669      * Constructs the PMD configuration class, passing it an argument that configures the target JDK.
670      *
671      * @return the resulting PMD
672      * @throws org.apache.maven.reporting.MavenReportException if targetJdk is not supported
673      */
674     public PMDConfiguration getPMDConfiguration()
675         throws MavenReportException
676     {
677         PMDConfiguration configuration = new PMDConfiguration();
678         LanguageVersion languageVersion = null;
679 
680         if ( ( "java".equals( language ) || null == language ) && null != targetJdk )
681         {
682             languageVersion = LanguageRegistry.findLanguageVersionByTerseName( "java " + targetJdk );
683             if ( languageVersion == null )
684             {
685                 throw new MavenReportException( "Unsupported targetJdk value '" + targetJdk + "'." );
686             }
687         }
688         else if ( "javascript".equals( language ) || "ecmascript".equals( language ) )
689         {
690             languageVersion = LanguageRegistry.findLanguageVersionByTerseName( "ecmascript" );
691         }
692         else if ( "jsp".equals( language ) )
693         {
694             languageVersion = LanguageRegistry.findLanguageVersionByTerseName( "jsp" );
695         }
696 
697         if ( languageVersion != null )
698         {
699             getLog().debug( "Using language " + languageVersion );
700             configuration.setDefaultLanguageVersion( languageVersion );
701         }
702 
703         if ( typeResolution )
704         {
705             configureTypeResolution( configuration );
706         }
707 
708         if ( null != suppressMarker )
709         {
710             configuration.setSuppressMarker( suppressMarker );
711         }
712 
713         configuration.setBenchmark( benchmark );
714 
715         if ( analysisCache )
716         {
717             configuration.setAnalysisCacheLocation( analysisCacheLocation );
718             getLog().debug( "Using analysis cache location: " + analysisCacheLocation );
719         }
720         else
721         {
722             configuration.setIgnoreIncrementalAnalysis( true );
723         }
724 
725         return configuration;
726     }
727 
728     private void configureTypeResolution( PMDConfiguration configuration ) throws MavenReportException
729     {
730         try
731         {
732             List<String> classpath = new ArrayList<>();
733             if ( aggregate )
734             {
735                 List<String> dependencies = new ArrayList<>();
736 
737                 // collect exclusions for projects within the reactor
738                 // if module a depends on module b and both are in the reactor
739                 // then we don't want to resolve the dependency as an artifact.
740                 List<String> exclusionPatterns = new ArrayList<>();
741                 for ( MavenProject localProject : reactorProjects )
742                 {
743                     exclusionPatterns.add( localProject.getGroupId() + ":" + localProject.getArtifactId() );
744                 }
745                 TransformableFilter filter = new AndFilter( Arrays.asList(
746                         new ExclusionsFilter( exclusionPatterns ),
747                         includeTests ? ScopeFilter.including( "test" ) : ScopeFilter.including( "compile" )
748                 ) );
749 
750                 for ( MavenProject localProject : reactorProjects )
751                 {
752                     ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(
753                             session.getProjectBuildingRequest() );
754 
755                     Iterable<ArtifactResult> resolvedDependencies = dependencyResolver.resolveDependencies(
756                             buildingRequest, localProject.getModel(), filter );
757 
758                     for ( ArtifactResult resolvedArtifact : resolvedDependencies )
759                     {
760                         dependencies.add( resolvedArtifact.getArtifact().getFile().toString() );
761                     }
762 
763                     List<String> projectCompileClasspath = includeTests ? localProject.getTestClasspathElements()
764                             : localProject.getCompileClasspathElements();
765                     // Add the project's target folder first
766                     classpath.addAll( projectCompileClasspath );
767                     if ( !localProject.isExecutionRoot() )
768                     {
769                         for ( String path : projectCompileClasspath )
770                         {
771                             File pathFile = new File( path );
772                             if ( !pathFile.exists() || pathFile.list().length == 0 )
773                             {
774                                 getLog().warn( "The project " + localProject.getArtifactId()
775                                     + " does not seem to be compiled. PMD results might be inaccurate." );
776                             }
777                         }
778                     }
779 
780                 }
781 
782                 // Add the dependencies as last entries
783                 classpath.addAll( dependencies );
784 
785                 getLog().debug( "Using aggregated aux classpath: " + classpath );
786             }
787             else
788             {
789                 classpath.addAll( includeTests ? project.getTestClasspathElements()
790                         : project.getCompileClasspathElements() );
791                 getLog().debug( "Using aux classpath: " + classpath );
792             }
793             String path = StringUtils.join( classpath.iterator(), File.pathSeparator );
794             configuration.prependClasspath( path );
795         }
796         catch ( Exception e )
797         {
798             throw new MavenReportException( e.getMessage(), e );
799         }
800     }
801 
802     /**
803      * {@inheritDoc}
804      */
805     @Override
806     public String getOutputName()
807     {
808         return "pmd";
809     }
810 
811     private static ResourceBundle getBundle( Locale locale )
812     {
813         return ResourceBundle.getBundle( "pmd-report", locale, PmdReport.class.getClassLoader() );
814     }
815 
816     /**
817      * Create and return the correct renderer for the output type.
818      *
819      * @return the renderer based on the configured output
820      * @throws org.apache.maven.reporting.MavenReportException if no renderer found for the output type
821      */
822     public final Renderer createRenderer()
823         throws MavenReportException
824     {
825         Renderer result = null;
826         if ( "xml".equals( format ) )
827         {
828             result = new XMLRenderer( getOutputEncoding() );
829         }
830         else if ( "txt".equals( format ) )
831         {
832             result = new TextRenderer();
833         }
834         else if ( "csv".equals( format ) )
835         {
836             result = new CSVRenderer();
837         }
838         else if ( "html".equals( format ) )
839         {
840             result = new HTMLRenderer();
841         }
842         else if ( !"".equals( format ) && !"none".equals( format ) )
843         {
844             try
845             {
846                 result = (Renderer) Class.forName( format ).getConstructor( Properties.class ).
847                                 newInstance( new Properties() );
848             }
849             catch ( Exception e )
850             {
851                 throw new MavenReportException( "Can't find PMD custom format " + format + ": "
852                     + e.getClass().getName(), e );
853             }
854         }
855 
856         return result;
857     }
858 }