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