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