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 }