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