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