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.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
86
87
88
89
90
91 @Mojo( name = "pmd", threadSafe = true, requiresDependencyResolution = ResolutionScope.TEST )
92 public class PmdReport
93 extends AbstractPmdReport
94 {
95
96
97
98
99
100
101
102
103
104
105
106
107
108 @Parameter( property = "targetJdk", defaultValue = "${maven.compiler.source}" )
109 private String targetJdk;
110
111
112
113
114
115
116
117 @Parameter( defaultValue = "java" )
118 private String language;
119
120
121
122
123
124
125 @Parameter( property = "minimumPriority", defaultValue = "5" )
126 private int minimumPriority = 5;
127
128
129
130
131
132
133 @Parameter( property = "pmd.skip", defaultValue = "false" )
134 private boolean skip;
135
136
137
138
139
140
141
142
143 @Parameter
144 private String[] rulesets = new String[] { "/rulesets/java/maven-pmd-plugin-default.xml" };
145
146
147
148
149
150
151
152 @Parameter( property = "pmd.typeResolution", defaultValue = "true" )
153 private boolean typeResolution;
154
155
156
157
158
159
160 @Parameter( property = "pmd.benchmark", defaultValue = "false" )
161 private boolean benchmark;
162
163
164
165
166
167
168 @Parameter( property = "pmd.benchmarkOutputFilename",
169 defaultValue = "${project.build.directory}/pmd-benchmark.txt" )
170 private String benchmarkOutputFilename;
171
172
173
174
175
176
177
178
179 @Parameter( property = "pmd.suppressMarker" )
180 private String suppressMarker;
181
182
183
184 @Component
185 private ResourceManager locator;
186
187
188 private PmdCollectingRenderer renderer;
189
190
191 private final ExcludeViolationsFromFile excludeFromFile = new ExcludeViolationsFromFile();
192
193
194
195
196
197
198 @Parameter( property = "pmd.skipPmdError", defaultValue = "true" )
199 private boolean skipPmdError;
200
201
202
203
204
205
206
207
208
209 @Parameter( property = "pmd.analysisCache", defaultValue = "false" )
210 private boolean analysisCache;
211
212
213
214
215
216
217
218
219
220
221 @Parameter( property = "pmd.analysisCacheLocation", defaultValue = "${project.build.directory}/pmd/pmd.cache" )
222 private String analysisCacheLocation;
223
224
225
226
227
228
229
230
231
232
233 @Parameter( property = "pmd.renderProcessingErrors", defaultValue = "true" )
234 private boolean renderProcessingErrors = true;
235
236
237
238
239
240
241 @Parameter( property = "pmd.renderRuleViolationPriority", defaultValue = "true" )
242 private boolean renderRuleViolationPriority = true;
243
244
245
246
247
248
249
250 @Parameter( property = "pmd.renderViolationsByPriority", defaultValue = "true" )
251 private boolean renderViolationsByPriority = true;
252
253
254
255
256
257
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
270
271 @Override
272 public String getName( Locale locale )
273 {
274 return getBundle( locale ).getString( "report.pmd.name" );
275 }
276
277
278
279
280 @Override
281 public String getDescription( Locale locale )
282 {
283 return getBundle( locale ).getString( "report.pmd.description" );
284 }
285
286
287
288
289
290
291
292
293 public void setRulesets( String[] rulesets )
294 {
295 this.rulesets = Arrays.copyOf( rulesets, rulesets.length );
296 }
297
298
299
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
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
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
499
500
501 if ( renderer != null )
502 {
503 Report report = renderer.asReport();
504 writeXmlReport( report );
505
506
507
508
509
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
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
615
616
617
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
632
633
634
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
674
675
676
677
678 private void writeFormattedReport( Report report )
679 throws MavenReportException
680 {
681 Renderer r = createRenderer();
682 writeReport( report, r, format );
683 }
684
685
686
687
688
689
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
711
712
713
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
779
780
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
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
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
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
861
862
863
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 }