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