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