1   package org.apache.maven.plugins.pmd;
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
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.plugin.MojoExecutionException;
39  import org.apache.maven.plugins.annotations.Component;
40  import org.apache.maven.plugins.annotations.Mojo;
41  import org.apache.maven.plugins.annotations.Parameter;
42  import org.apache.maven.plugins.annotations.ResolutionScope;
43  import org.apache.maven.reporting.MavenReportException;
44  import org.codehaus.plexus.resource.ResourceManager;
45  import org.codehaus.plexus.resource.loader.FileResourceCreationException;
46  import org.codehaus.plexus.resource.loader.FileResourceLoader;
47  import org.codehaus.plexus.resource.loader.ResourceNotFoundException;
48  import org.codehaus.plexus.util.FileUtils;
49  import org.codehaus.plexus.util.ReaderFactory;
50  import org.codehaus.plexus.util.StringUtils;
51  
52  import net.sourceforge.pmd.PMD;
53  import net.sourceforge.pmd.PMDConfiguration;
54  import net.sourceforge.pmd.Report;
55  import net.sourceforge.pmd.RuleContext;
56  import net.sourceforge.pmd.RulePriority;
57  import net.sourceforge.pmd.RuleSetFactory;
58  import net.sourceforge.pmd.RuleSetReferenceId;
59  import net.sourceforge.pmd.RuleViolation;
60  import net.sourceforge.pmd.benchmark.Benchmarker;
61  import net.sourceforge.pmd.benchmark.TextReport;
62  import net.sourceforge.pmd.lang.LanguageRegistry;
63  import net.sourceforge.pmd.lang.LanguageVersion;
64  import net.sourceforge.pmd.renderers.CSVRenderer;
65  import net.sourceforge.pmd.renderers.HTMLRenderer;
66  import net.sourceforge.pmd.renderers.Renderer;
67  import net.sourceforge.pmd.renderers.TextRenderer;
68  import net.sourceforge.pmd.renderers.XMLRenderer;
69  import net.sourceforge.pmd.util.ResourceLoader;
70  import net.sourceforge.pmd.util.datasource.DataSource;
71  import net.sourceforge.pmd.util.datasource.FileDataSource;
72  
73  
74  
75  
76  
77  
78  
79  
80  @Mojo( name = "pmd", threadSafe = true, requiresDependencyResolution = ResolutionScope.TEST )
81  public class PmdReport
82      extends AbstractPmdReport
83  {
84      
85  
86  
87  
88  
89  
90  
91  
92      @Parameter( property = "targetJdk", defaultValue = "${maven.compiler.source}" )
93      private String targetJdk;
94  
95      
96  
97  
98  
99  
100 
101     @Parameter( defaultValue = "java" )
102     private String language;
103 
104     
105 
106 
107 
108 
109     @Parameter( property = "minimumPriority", defaultValue = "5" )
110     private int minimumPriority = 5;
111 
112     
113 
114 
115 
116 
117     @Parameter( property = "pmd.skip", defaultValue = "false" )
118     private boolean skip;
119 
120     
121 
122 
123 
124 
125 
126 
127     @Parameter
128     private String[] rulesets = new String[] { "/rulesets/java/maven-pmd-plugin-default.xml" };
129 
130     
131 
132 
133 
134 
135 
136     @Parameter( property = "pmd.typeResolution", defaultValue = "true" )
137     private boolean typeResolution;
138 
139     
140 
141 
142 
143 
144     @Parameter( property = "pmd.benchmark", defaultValue = "false" )
145     private boolean benchmark;
146 
147     
148 
149 
150 
151 
152     @Parameter( property = "pmd.benchmarkOutputFilename",
153                     defaultValue = "${project.build.directory}/pmd-benchmark.txt" )
154     private String benchmarkOutputFilename;
155 
156     
157 
158 
159 
160 
161 
162 
163     @Parameter( property = "pmd.suppressMarker" )
164     private String suppressMarker;
165 
166     
167 
168     @Component
169     private ResourceManager locator;
170 
171     
172     private PmdCollectingRenderer renderer;
173 
174     
175     private final ExcludeViolationsFromFile excludeFromFile = new ExcludeViolationsFromFile();
176 
177     
178 
179 
180 
181 
182     @Parameter( property = "pmd.skipPmdError", defaultValue = "true" )
183     private boolean skipPmdError;
184 
185     
186 
187 
188 
189 
190 
191 
192 
193     @Parameter( property = "pmd.analysisCache", defaultValue = "false" )
194     private boolean analysisCache;
195 
196     
197 
198 
199 
200 
201 
202 
203 
204 
205     @Parameter( property = "pmd.analysisCacheLocation", defaultValue = "${project.build.directory}/pmd/pmd.cache" )
206     private String analysisCacheLocation;
207 
208     
209 
210 
211 
212 
213 
214 
215 
216 
217     @Parameter( property = "pmd.renderProcessingErrors", defaultValue = "true" )
218     private boolean renderProcessingErrors = true;
219 
220     
221 
222 
223 
224 
225     @Parameter( property = "pmd.renderRuleViolationPriority", defaultValue = "true" )
226     private boolean renderRuleViolationPriority = true;
227 
228     
229 
230 
231     public String getName( Locale locale )
232     {
233         return getBundle( locale ).getString( "report.pmd.name" );
234     }
235 
236     
237 
238 
239     public String getDescription( Locale locale )
240     {
241         return getBundle( locale ).getString( "report.pmd.description" );
242     }
243 
244     
245 
246 
247 
248 
249 
250 
251     public void setRulesets( String[] rulesets )
252     {
253         this.rulesets = Arrays.copyOf( rulesets, rulesets.length );
254     }
255 
256     
257 
258 
259     @Override
260     public void executeReport( Locale locale )
261         throws MavenReportException
262     {
263         try
264         {
265             execute( locale );
266         }
267         finally
268         {
269             if ( getSink() != null )
270             {
271                 getSink().close();
272             }
273         }
274     }
275 
276     private void execute( Locale locale )
277         throws MavenReportException
278     {
279         if ( !skip && canGenerateReport() )
280         {
281             ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
282             try
283             {
284                 Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
285 
286                 Report report = generateReport( locale );
287 
288                 if ( !isHtml() && !isXml() )
289                 {
290                     writeNonHtml( report );
291                 }
292             }
293             finally
294             {
295                 Thread.currentThread().setContextClassLoader( origLoader );
296             }
297         }
298     }
299 
300     @Override
301     public boolean canGenerateReport()
302     {
303         if ( skip )
304         {
305             return false;
306         }
307 
308         boolean result = super.canGenerateReport();
309         if ( result )
310         {
311             try
312             {
313                 executePmdWithClassloader();
314                 if ( skipEmptyReport )
315                 {
316                     result = renderer.hasViolations();
317                     if ( result )
318                     {
319                         getLog().debug( "Skipping report since skipEmptyReport is true and"
320                                             + "there are no PMD violations." );
321                     }
322                 }
323             }
324             catch ( MavenReportException e )
325             {
326                 throw new RuntimeException( e );
327             }
328         }
329         return result;
330     }
331 
332     private void executePmdWithClassloader()
333         throws MavenReportException
334     {
335         ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
336         try
337         {
338             Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
339             executePmd();
340         }
341         finally
342         {
343             Thread.currentThread().setContextClassLoader( origLoader );
344         }
345     }
346 
347     private void executePmd()
348         throws MavenReportException
349     {
350         setupPmdLogging();
351 
352         if ( renderer != null )
353         {
354             
355             getLog().debug( "PMD has already been run - skipping redundant execution." );
356             return;
357         }
358 
359         try
360         {
361             excludeFromFile.loadExcludeFromFailuresData( excludeFromFailureFile );
362         }
363         catch ( MojoExecutionException e )
364         {
365             throw new MavenReportException( "Unable to load exclusions", e );
366         }
367 
368         
369         locator.addSearchPath( FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath() );
370         locator.addSearchPath( "url", "" );
371         locator.setOutputDirectory( targetDirectory );
372 
373         renderer = new PmdCollectingRenderer();
374         PMDConfiguration pmdConfiguration = getPMDConfiguration();
375 
376         String[] sets = new String[rulesets.length];
377         try
378         {
379             for ( int idx = 0; idx < rulesets.length; idx++ )
380             {
381                 String set = rulesets[idx];
382                 getLog().debug( "Preparing ruleset: " + set );
383                 RuleSetReferenceId id = new RuleSetReferenceId( set );
384                 File ruleset = locator.getResourceAsFile( id.getRuleSetFileName(), getLocationTemp( set ) );
385                 if ( null == ruleset )
386                 {
387                     throw new MavenReportException( "Could not resolve " + set );
388                 }
389                 sets[idx] = ruleset.getAbsolutePath();
390             }
391         }
392         catch ( ResourceNotFoundException e )
393         {
394             throw new MavenReportException( e.getMessage(), e );
395         }
396         catch ( FileResourceCreationException e )
397         {
398             throw new MavenReportException( e.getMessage(), e );
399         }
400         pmdConfiguration.setRuleSets( StringUtils.join( sets, "," ) );
401 
402         try
403         {
404             if ( filesToProcess == null )
405             {
406                 filesToProcess = getFilesToProcess();
407             }
408 
409             if ( filesToProcess.isEmpty() && !"java".equals( language ) )
410             {
411                 getLog().warn( "No files found to process. Did you add your additional source folders like javascript?"
412                                    + " (see also build-helper-maven-plugin)" );
413             }
414         }
415         catch ( IOException e )
416         {
417             throw new MavenReportException( "Can't get file list", e );
418         }
419 
420         String encoding = getSourceEncoding();
421         if ( StringUtils.isEmpty( encoding ) )
422         {
423             encoding = ReaderFactory.FILE_ENCODING;
424             if ( !filesToProcess.isEmpty() )
425             {
426                 getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
427                                + ", i.e. build is platform dependent!" );
428             }
429         }
430         pmdConfiguration.setSourceEncoding( encoding );
431 
432         List<DataSource> dataSources = new ArrayList<>( filesToProcess.size() );
433         for ( File f : filesToProcess.keySet() )
434         {
435             dataSources.add( new FileDataSource( f ) );
436         }
437 
438         if ( sets.length > 0 )
439         {
440             processFilesWithPMD( pmdConfiguration, dataSources );
441         }
442         else
443         {
444             getLog().debug( "Skipping PMD execution as no rulesets are defined." );
445         }
446 
447         if ( renderer.hasErrors() )
448         {
449             if ( !skipPmdError )
450             {
451                 getLog().error( "PMD processing errors:" );
452                 getLog().error( renderer.getErrorsAsString( getLog().isDebugEnabled() ) );
453                 throw new MavenReportException( "Found " + renderer.getErrors().size() + " PMD processing errors" );
454             }
455             getLog().warn( "There are " + renderer.getErrors().size() + " PMD processing errors:" );
456             getLog().warn( renderer.getErrorsAsString( getLog().isDebugEnabled() ) );
457         }
458 
459         removeExcludedViolations( renderer.getViolations() );
460 
461         
462         
463         if ( isXml() && renderer != null )
464         {
465             writeNonHtml( renderer.asReport() );
466         }
467 
468         if ( benchmark )
469         {
470             try ( PrintStream benchmarkFileStream = new PrintStream( benchmarkOutputFilename ) )
471             {
472                 ( new TextReport() ).generate( Benchmarker.values(), benchmarkFileStream );
473             }
474             catch ( FileNotFoundException fnfe )
475             {
476                 getLog().error( "Unable to generate benchmark file: " + benchmarkOutputFilename, fnfe );
477             }
478         }
479     }
480 
481     private void removeExcludedViolations( List<RuleViolation> violations )
482     {
483         getLog().debug( "Removing excluded violations. Using " + excludeFromFile.countExclusions()
484             + " configured exclusions." );
485         int violationsBefore = violations.size();
486 
487         Iterator<RuleViolation> iterator = violations.iterator();
488         while ( iterator.hasNext() )
489         {
490             RuleViolation rv = iterator.next();
491             if ( excludeFromFile.isExcludedFromFailure( rv ) )
492             {
493                 iterator.remove();
494             }
495         }
496 
497         int numberOfExcludedViolations = violationsBefore - violations.size();
498         getLog().debug( "Excluded " + numberOfExcludedViolations + " violations." );
499     }
500 
501     private void processFilesWithPMD( PMDConfiguration pmdConfiguration, List<DataSource> dataSources )
502             throws MavenReportException
503     {
504         RuleSetFactory ruleSetFactory = new RuleSetFactory( new ResourceLoader(),
505                 RulePriority.valueOf( this.minimumPriority ), false, true );
506         RuleContext ruleContext = new RuleContext();
507 
508         try
509         {
510             getLog().debug( "Executing PMD..." );
511             PMD.processFiles( pmdConfiguration, ruleSetFactory, dataSources, ruleContext,
512                               Arrays.<Renderer>asList( renderer ) );
513 
514             if ( getLog().isDebugEnabled() )
515             {
516                 getLog().debug( "PMD finished. Found " + renderer.getViolations().size() + " violations." );
517             }
518         }
519         catch ( Exception e )
520         {
521             String message = "Failure executing PMD: " + e.getLocalizedMessage();
522             if ( !skipPmdError )
523             {
524                 throw new MavenReportException( message, e );
525             }
526             getLog().warn( message, e );
527         }
528     }
529 
530     private Report generateReport( Locale locale )
531         throws MavenReportException
532     {
533         Sink sink = getSink();
534         PmdReportGenerator doxiaRenderer = new PmdReportGenerator( getLog(), sink, getBundle( locale ), aggregate );
535         doxiaRenderer.setRenderRuleViolationPriority( renderRuleViolationPriority );
536         doxiaRenderer.setFiles( filesToProcess );
537         doxiaRenderer.setViolations( renderer.getViolations() );
538         if ( renderProcessingErrors )
539         {
540             doxiaRenderer.setProcessingErrors( renderer.getErrors() );
541         }
542 
543         try
544         {
545             doxiaRenderer.beginDocument();
546             doxiaRenderer.render();
547             doxiaRenderer.endDocument();
548         }
549         catch ( IOException e )
550         {
551             getLog().warn( "Failure creating the report: " + e.getLocalizedMessage(), e );
552         }
553 
554         return renderer.asReport();
555     }
556 
557     
558 
559 
560 
561 
562 
563     protected String getLocationTemp( String name )
564     {
565         String loc = name;
566         if ( loc.indexOf( '/' ) != -1 )
567         {
568             loc = loc.substring( loc.lastIndexOf( '/' ) + 1 );
569         }
570         if ( loc.indexOf( '\\' ) != -1 )
571         {
572             loc = loc.substring( loc.lastIndexOf( '\\' ) + 1 );
573         }
574 
575         
576         
577         
578         
579         loc = loc.replaceAll( "[\\?\\:\\&\\=\\%]", "_" );
580 
581         if ( !loc.endsWith( ".xml" ) )
582         {
583             loc = loc + ".xml";
584         }
585 
586         getLog().debug( "Before: " + name + " After: " + loc );
587         return loc;
588     }
589 
590     
591 
592 
593 
594 
595 
596     private void writeNonHtml( Report report )
597         throws MavenReportException
598     {
599         Renderer r = createRenderer();
600 
601         if ( r == null )
602         {
603             return;
604         }
605 
606         File targetFile = new File( targetDirectory, "pmd." + format );
607         try ( Writer writer = new OutputStreamWriter( new FileOutputStream( targetFile ), getOutputEncoding() ) )
608         {
609             targetDirectory.mkdirs();
610 
611             r.setWriter( writer );
612             r.start();
613             r.renderFileReport( report );
614             r.end();
615             r.flush();
616 
617             if ( includeXmlInSite )
618             {
619                 File siteDir = getReportOutputDirectory();
620                 siteDir.mkdirs();
621                 FileUtils.copyFile( targetFile, new File( siteDir, "pmd." + format ) );
622             }
623         }
624         catch ( IOException ioe )
625         {
626             throw new MavenReportException( ioe.getMessage(), ioe );
627         }
628     }
629 
630     
631 
632 
633 
634 
635 
636     public PMDConfiguration getPMDConfiguration()
637         throws MavenReportException
638     {
639         PMDConfiguration configuration = new PMDConfiguration();
640         LanguageVersion languageVersion = null;
641 
642         if ( ( "java".equals( language ) || null == language ) && null != targetJdk )
643         {
644             languageVersion = LanguageRegistry.findLanguageVersionByTerseName( "java " + targetJdk );
645             if ( languageVersion == null )
646             {
647                 throw new MavenReportException( "Unsupported targetJdk value '" + targetJdk + "'." );
648             }
649         }
650         else if ( "javascript".equals( language ) || "ecmascript".equals( language ) )
651         {
652             languageVersion = LanguageRegistry.findLanguageVersionByTerseName( "ecmascript" );
653         }
654         else if ( "jsp".equals( language ) )
655         {
656             languageVersion = LanguageRegistry.findLanguageVersionByTerseName( "jsp" );
657         }
658 
659         if ( languageVersion != null )
660         {
661             getLog().debug( "Using language " + languageVersion );
662             configuration.setDefaultLanguageVersion( languageVersion );
663         }
664 
665         if ( typeResolution )
666         {
667             try
668             {
669                 List<String> classpath =
670                     includeTests ? project.getTestClasspathElements() : project.getCompileClasspathElements();
671                 getLog().debug( "Using aux classpath: " + classpath );
672                 configuration.prependClasspath( StringUtils.join( classpath.iterator(), File.pathSeparator ) );
673             }
674             catch ( Exception e )
675             {
676                 throw new MavenReportException( e.getMessage(), e );
677             }
678         }
679 
680         if ( null != suppressMarker )
681         {
682             configuration.setSuppressMarker( suppressMarker );
683         }
684 
685         configuration.setBenchmark( benchmark );
686 
687         if ( analysisCache )
688         {
689             configuration.setAnalysisCacheLocation( analysisCacheLocation );
690             getLog().debug( "Using analysis cache location: " + analysisCacheLocation );
691         }
692         else
693         {
694             configuration.setIgnoreIncrementalAnalysis( true );
695         }
696 
697         return configuration;
698     }
699 
700     
701 
702 
703     public String getOutputName()
704     {
705         return "pmd";
706     }
707 
708     private static ResourceBundle getBundle( Locale locale )
709     {
710         return ResourceBundle.getBundle( "pmd-report", locale, PmdReport.class.getClassLoader() );
711     }
712 
713     
714 
715 
716 
717 
718 
719     public final Renderer createRenderer()
720         throws MavenReportException
721     {
722         Renderer result = null;
723         if ( "xml".equals( format ) )
724         {
725             result = new XMLRenderer( getOutputEncoding() );
726         }
727         else if ( "txt".equals( format ) )
728         {
729             result = new TextRenderer();
730         }
731         else if ( "csv".equals( format ) )
732         {
733             result = new CSVRenderer();
734         }
735         else if ( "html".equals( format ) )
736         {
737             result = new HTMLRenderer();
738         }
739         else if ( !"".equals( format ) && !"none".equals( format ) )
740         {
741             try
742             {
743                 result = (Renderer) Class.forName( format ).getConstructor( Properties.class ).
744                                 newInstance( new Properties() );
745             }
746             catch ( Exception e )
747             {
748                 throw new MavenReportException( "Can't find PMD custom format " + format + ": "
749                     + e.getClass().getName(), e );
750             }
751         }
752 
753         return result;
754     }
755 }