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