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