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 public String getName( Locale locale )
224 {
225 return getBundle( locale ).getString( "report.pmd.name" );
226 }
227
228
229
230
231 public String getDescription( Locale locale )
232 {
233 return getBundle( locale ).getString( "report.pmd.description" );
234 }
235
236
237
238
239
240
241
242
243 public void setRulesets( String[] rulesets )
244 {
245 this.rulesets = Arrays.copyOf( rulesets, rulesets.length );
246 }
247
248
249
250
251 @Override
252 public void executeReport( Locale locale )
253 throws MavenReportException
254 {
255 try
256 {
257 execute( locale );
258 }
259 finally
260 {
261 if ( getSink() != null )
262 {
263 getSink().close();
264 }
265 }
266 }
267
268 private void execute( Locale locale )
269 throws MavenReportException
270 {
271 if ( !skip && canGenerateReport() )
272 {
273 ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
274 try
275 {
276 Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
277
278 Report report = generateReport( locale );
279
280 if ( !isHtml() && !isXml() )
281 {
282 writeNonHtml( report );
283 }
284 }
285 finally
286 {
287 Thread.currentThread().setContextClassLoader( origLoader );
288 }
289 }
290 }
291
292 @Override
293 public boolean canGenerateReport()
294 {
295 if ( skip )
296 {
297 return false;
298 }
299
300 boolean result = super.canGenerateReport();
301 if ( result )
302 {
303 try
304 {
305 executePmdWithClassloader();
306 if ( skipEmptyReport )
307 {
308 result = renderer.hasViolations();
309 if ( result )
310 {
311 getLog().debug( "Skipping report since skipEmptyReport is true and"
312 + "there are no PMD violations." );
313 }
314 }
315 }
316 catch ( MavenReportException e )
317 {
318 throw new RuntimeException( e );
319 }
320 }
321 return result;
322 }
323
324 private void executePmdWithClassloader()
325 throws MavenReportException
326 {
327 ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
328 try
329 {
330 Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
331 executePmd();
332 }
333 finally
334 {
335 Thread.currentThread().setContextClassLoader( origLoader );
336 }
337 }
338
339 private void executePmd()
340 throws MavenReportException
341 {
342 setupPmdLogging();
343
344 if ( renderer != null )
345 {
346
347 getLog().debug( "PMD has already been run - skipping redundant execution." );
348 return;
349 }
350
351 try
352 {
353 excludeFromFile.loadExcludeFromFailuresData( excludeFromFailureFile );
354 }
355 catch ( MojoExecutionException e )
356 {
357 throw new MavenReportException( "Unable to load exclusions", e );
358 }
359
360
361 locator.addSearchPath( FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath() );
362 locator.addSearchPath( "url", "" );
363 locator.setOutputDirectory( targetDirectory );
364
365 renderer = new PmdCollectingRenderer();
366 PMDConfiguration pmdConfiguration = getPMDConfiguration();
367
368 String[] sets = new String[rulesets.length];
369 try
370 {
371 for ( int idx = 0; idx < rulesets.length; idx++ )
372 {
373 String set = rulesets[idx];
374 getLog().debug( "Preparing ruleset: " + set );
375 RuleSetReferenceId id = new RuleSetReferenceId( set );
376 File ruleset = locator.getResourceAsFile( id.getRuleSetFileName(), getLocationTemp( set ) );
377 if ( null == ruleset )
378 {
379 throw new MavenReportException( "Could not resolve " + set );
380 }
381 sets[idx] = ruleset.getAbsolutePath();
382 }
383 }
384 catch ( ResourceNotFoundException e )
385 {
386 throw new MavenReportException( e.getMessage(), e );
387 }
388 catch ( FileResourceCreationException e )
389 {
390 throw new MavenReportException( e.getMessage(), e );
391 }
392 pmdConfiguration.setRuleSets( StringUtils.join( sets, "," ) );
393
394 try
395 {
396 if ( filesToProcess == null )
397 {
398 filesToProcess = getFilesToProcess();
399 }
400
401 if ( filesToProcess.isEmpty() && !"java".equals( language ) )
402 {
403 getLog().warn( "No files found to process. Did you add your additional source folders like javascript?"
404 + " (see also build-helper-maven-plugin)" );
405 }
406 }
407 catch ( IOException e )
408 {
409 throw new MavenReportException( "Can't get file list", e );
410 }
411
412 String encoding = getSourceEncoding();
413 if ( StringUtils.isEmpty( encoding ) )
414 {
415 encoding = ReaderFactory.FILE_ENCODING;
416 if ( !filesToProcess.isEmpty() )
417 {
418 getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
419 + ", i.e. build is platform dependent!" );
420 }
421 }
422 pmdConfiguration.setSourceEncoding( encoding );
423
424 List<DataSource> dataSources = new ArrayList<>( filesToProcess.size() );
425 for ( File f : filesToProcess.keySet() )
426 {
427 dataSources.add( new FileDataSource( f ) );
428 }
429
430 if ( sets.length > 0 )
431 {
432 processFilesWithPMD( pmdConfiguration, dataSources );
433 }
434 else
435 {
436 getLog().debug( "Skipping PMD execution as no rulesets are defined." );
437 }
438
439 if ( renderer.hasErrors() )
440 {
441 if ( !skipPmdError )
442 {
443 getLog().error( "PMD processing errors:" );
444 getLog().error( renderer.getErrorsAsString( getLog().isDebugEnabled() ) );
445 throw new MavenReportException( "Found " + renderer.getErrors().size() + " PMD processing errors" );
446 }
447 getLog().warn( "There are " + renderer.getErrors().size() + " PMD processing errors:" );
448 getLog().warn( renderer.getErrorsAsString( getLog().isDebugEnabled() ) );
449 }
450
451 removeExcludedViolations( renderer.getViolations() );
452
453
454
455 if ( isXml() && renderer != null )
456 {
457 writeNonHtml( renderer.asReport() );
458 }
459
460 if ( benchmark )
461 {
462 try ( PrintStream benchmarkFileStream = new PrintStream( benchmarkOutputFilename ) )
463 {
464 ( new TextReport() ).generate( Benchmarker.values(), benchmarkFileStream );
465 }
466 catch ( FileNotFoundException fnfe )
467 {
468 getLog().error( "Unable to generate benchmark file: " + benchmarkOutputFilename, fnfe );
469 }
470 }
471 }
472
473 private void removeExcludedViolations( List<RuleViolation> violations )
474 {
475 getLog().debug( "Removing excluded violations. Using " + excludeFromFile.countExclusions()
476 + " configured exclusions." );
477 int violationsBefore = violations.size();
478
479 Iterator<RuleViolation> iterator = violations.iterator();
480 while ( iterator.hasNext() )
481 {
482 RuleViolation rv = iterator.next();
483 if ( excludeFromFile.isExcludedFromFailure( rv ) )
484 {
485 iterator.remove();
486 }
487 }
488
489 int numberOfExcludedViolations = violationsBefore - violations.size();
490 getLog().debug( "Excluded " + numberOfExcludedViolations + " violations." );
491 }
492
493 private void processFilesWithPMD( PMDConfiguration pmdConfiguration, List<DataSource> dataSources )
494 throws MavenReportException
495 {
496 RuleSetFactory ruleSetFactory = new RuleSetFactory( new ResourceLoader(),
497 RulePriority.valueOf( this.minimumPriority ), false, true );
498 RuleContext ruleContext = new RuleContext();
499
500 try
501 {
502 getLog().debug( "Executing PMD..." );
503 PMD.processFiles( pmdConfiguration, ruleSetFactory, dataSources, ruleContext,
504 Arrays.<Renderer>asList( renderer ) );
505
506 if ( getLog().isDebugEnabled() )
507 {
508 getLog().debug( "PMD finished. Found " + renderer.getViolations().size() + " violations." );
509 }
510 }
511 catch ( Exception e )
512 {
513 String message = "Failure executing PMD: " + e.getLocalizedMessage();
514 if ( !skipPmdError )
515 {
516 throw new MavenReportException( message, e );
517 }
518 getLog().warn( message, e );
519 }
520 }
521
522 private Report generateReport( Locale locale )
523 throws MavenReportException
524 {
525 Sink sink = getSink();
526 PmdReportGenerator doxiaRenderer = new PmdReportGenerator( getLog(), sink, getBundle( locale ), aggregate );
527 doxiaRenderer.setFiles( filesToProcess );
528 doxiaRenderer.setViolations( renderer.getViolations() );
529 if ( renderProcessingErrors )
530 {
531 doxiaRenderer.setProcessingErrors( renderer.getErrors() );
532 }
533
534 try
535 {
536 doxiaRenderer.beginDocument();
537 doxiaRenderer.render();
538 doxiaRenderer.endDocument();
539 }
540 catch ( IOException e )
541 {
542 getLog().warn( "Failure creating the report: " + e.getLocalizedMessage(), e );
543 }
544
545 return renderer.asReport();
546 }
547
548
549
550
551
552
553
554 protected String getLocationTemp( String name )
555 {
556 String loc = name;
557 if ( loc.indexOf( '/' ) != -1 )
558 {
559 loc = loc.substring( loc.lastIndexOf( '/' ) + 1 );
560 }
561 if ( loc.indexOf( '\\' ) != -1 )
562 {
563 loc = loc.substring( loc.lastIndexOf( '\\' ) + 1 );
564 }
565
566
567
568
569
570 loc = loc.replaceAll( "[\\?\\:\\&\\=\\%]", "_" );
571
572 if ( !loc.endsWith( ".xml" ) )
573 {
574 loc = loc + ".xml";
575 }
576
577 getLog().debug( "Before: " + name + " After: " + loc );
578 return loc;
579 }
580
581
582
583
584
585
586
587 private void writeNonHtml( Report report )
588 throws MavenReportException
589 {
590 Renderer r = createRenderer();
591
592 if ( r == null )
593 {
594 return;
595 }
596
597 File targetFile = new File( targetDirectory, "pmd." + format );
598 try ( Writer writer = new OutputStreamWriter( new FileOutputStream( targetFile ), getOutputEncoding() ) )
599 {
600 targetDirectory.mkdirs();
601
602 r.setWriter( writer );
603 r.start();
604 r.renderFileReport( report );
605 r.end();
606 r.flush();
607
608 if ( includeXmlInSite )
609 {
610 File siteDir = getReportOutputDirectory();
611 siteDir.mkdirs();
612 FileUtils.copyFile( targetFile, new File( siteDir, "pmd." + format ) );
613 }
614 }
615 catch ( IOException ioe )
616 {
617 throw new MavenReportException( ioe.getMessage(), ioe );
618 }
619 }
620
621
622
623
624
625
626
627 public PMDConfiguration getPMDConfiguration()
628 throws MavenReportException
629 {
630 PMDConfiguration configuration = new PMDConfiguration();
631 LanguageVersion languageVersion = null;
632
633 if ( ( "java".equals( language ) || null == language ) && null != targetJdk )
634 {
635 languageVersion = LanguageRegistry.findLanguageVersionByTerseName( "java " + targetJdk );
636 if ( languageVersion == null )
637 {
638 throw new MavenReportException( "Unsupported targetJdk value '" + targetJdk + "'." );
639 }
640 }
641 else if ( "javascript".equals( language ) || "ecmascript".equals( language ) )
642 {
643 languageVersion = LanguageRegistry.findLanguageVersionByTerseName( "ecmascript" );
644 }
645 else if ( "jsp".equals( language ) )
646 {
647 languageVersion = LanguageRegistry.findLanguageVersionByTerseName( "jsp" );
648 }
649
650 if ( languageVersion != null )
651 {
652 getLog().debug( "Using language " + languageVersion );
653 configuration.setDefaultLanguageVersion( languageVersion );
654 }
655
656 if ( typeResolution )
657 {
658 try
659 {
660 List<String> classpath =
661 includeTests ? project.getTestClasspathElements() : project.getCompileClasspathElements();
662 getLog().debug( "Using aux classpath: " + classpath );
663 configuration.prependClasspath( StringUtils.join( classpath.iterator(), File.pathSeparator ) );
664 }
665 catch ( Exception e )
666 {
667 throw new MavenReportException( e.getMessage(), e );
668 }
669 }
670
671 if ( null != suppressMarker )
672 {
673 configuration.setSuppressMarker( suppressMarker );
674 }
675
676 configuration.setBenchmark( benchmark );
677
678 if ( analysisCache )
679 {
680 configuration.setAnalysisCacheLocation( analysisCacheLocation );
681 getLog().debug( "Using analysis cache location: " + analysisCacheLocation );
682 }
683
684 return configuration;
685 }
686
687
688
689
690 public String getOutputName()
691 {
692 return "pmd";
693 }
694
695 private static ResourceBundle getBundle( Locale locale )
696 {
697 return ResourceBundle.getBundle( "pmd-report", locale, PmdReport.class.getClassLoader() );
698 }
699
700
701
702
703
704
705
706 public final Renderer createRenderer()
707 throws MavenReportException
708 {
709 Renderer result = null;
710 if ( "xml".equals( format ) )
711 {
712 result = new XMLRenderer( getOutputEncoding() );
713 }
714 else if ( "txt".equals( format ) )
715 {
716 result = new TextRenderer();
717 }
718 else if ( "csv".equals( format ) )
719 {
720 result = new CSVRenderer();
721 }
722 else if ( "html".equals( format ) )
723 {
724 result = new HTMLRenderer();
725 }
726 else if ( !"".equals( format ) && !"none".equals( format ) )
727 {
728 try
729 {
730 result = (Renderer) Class.forName( format ).getConstructor( Properties.class ).
731 newInstance( new Properties() );
732 }
733 catch ( Exception e )
734 {
735 throw new MavenReportException( "Can't find PMD custom format " + format + ": "
736 + e.getClass().getName(), e );
737 }
738 }
739
740 return result;
741 }
742 }