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 @Parameter( property = "targetJdk", defaultValue = "${maven.compiler.source}" )
104 private String targetJdk;
105
106
107
108
109
110
111
112 @Parameter( defaultValue = "java" )
113 private String language;
114
115
116
117
118
119
120 @Parameter( property = "minimumPriority", defaultValue = "5" )
121 private int minimumPriority = 5;
122
123
124
125
126
127
128 @Parameter( property = "pmd.skip", defaultValue = "false" )
129 private boolean skip;
130
131
132
133
134
135
136
137
138 @Parameter
139 private String[] rulesets = new String[] { "/rulesets/java/maven-pmd-plugin-default.xml" };
140
141
142
143
144
145
146
147 @Parameter( property = "pmd.typeResolution", defaultValue = "true" )
148 private boolean typeResolution;
149
150
151
152
153
154
155 @Parameter( property = "pmd.benchmark", defaultValue = "false" )
156 private boolean benchmark;
157
158
159
160
161
162
163 @Parameter( property = "pmd.benchmarkOutputFilename",
164 defaultValue = "${project.build.directory}/pmd-benchmark.txt" )
165 private String benchmarkOutputFilename;
166
167
168
169
170
171
172
173
174 @Parameter( property = "pmd.suppressMarker" )
175 private String suppressMarker;
176
177
178
179 @Component
180 private ResourceManager locator;
181
182
183 private PmdCollectingRenderer renderer;
184
185
186 private final ExcludeViolationsFromFile excludeFromFile = new ExcludeViolationsFromFile();
187
188
189
190
191
192
193 @Parameter( property = "pmd.skipPmdError", defaultValue = "true" )
194 private boolean skipPmdError;
195
196
197
198
199
200
201
202
203
204 @Parameter( property = "pmd.analysisCache", defaultValue = "false" )
205 private boolean analysisCache;
206
207
208
209
210
211
212
213
214
215
216 @Parameter( property = "pmd.analysisCacheLocation", defaultValue = "${project.build.directory}/pmd/pmd.cache" )
217 private String analysisCacheLocation;
218
219
220
221
222
223
224
225
226
227
228 @Parameter( property = "pmd.renderProcessingErrors", defaultValue = "true" )
229 private boolean renderProcessingErrors = true;
230
231
232
233
234
235
236 @Parameter( property = "pmd.renderRuleViolationPriority", defaultValue = "true" )
237 private boolean renderRuleViolationPriority = true;
238
239
240
241
242
243
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
256
257 @Override
258 public String getName( Locale locale )
259 {
260 return getBundle( locale ).getString( "report.pmd.name" );
261 }
262
263
264
265
266 @Override
267 public String getDescription( Locale locale )
268 {
269 return getBundle( locale ).getString( "report.pmd.description" );
270 }
271
272
273
274
275
276
277
278
279 public void setRulesets( String[] rulesets )
280 {
281 this.rulesets = Arrays.copyOf( rulesets, rulesets.length );
282 }
283
284
285
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
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
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
490
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
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
597
598
599
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
614
615
616
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
630
631
632
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
670
671
672
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
738
739
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
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
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
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
818
819
820
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 }