1 package org.apache.maven.plugins.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.IOException;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.Comparator;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.ResourceBundle;
33 import java.util.Set;
34
35 import org.apache.maven.doxia.sink.Sink;
36 import org.apache.maven.plugin.logging.Log;
37 import org.apache.maven.plugins.pmd.model.ProcessingError;
38 import org.apache.maven.plugins.pmd.model.SuppressedViolation;
39 import org.apache.maven.plugins.pmd.model.Violation;
40 import org.codehaus.plexus.util.StringUtils;
41
42 import net.sourceforge.pmd.RulePriority;
43
44
45
46
47
48
49
50 public class PmdReportGenerator
51 {
52 private Log log;
53
54 private Sink sink;
55
56 private String currentFilename;
57
58 private ResourceBundle bundle;
59
60 private Set<Violation> violations = new HashSet<>();
61
62 private List<SuppressedViolation> suppressedViolations = new ArrayList<>();
63
64 private List<ProcessingError> processingErrors = new ArrayList<>();
65
66 private boolean aggregate;
67
68 private boolean renderRuleViolationPriority;
69
70 private boolean renderViolationsByPriority;
71
72 private Map<File, PmdFileInfo> files;
73
74
75
76 public PmdReportGenerator( Log log, Sink sink, ResourceBundle bundle, boolean aggregate )
77 {
78 this.log = log;
79 this.sink = sink;
80 this.bundle = bundle;
81 this.aggregate = aggregate;
82 }
83
84 private String getTitle()
85 {
86 return bundle.getString( "report.pmd.title" );
87 }
88
89 public void setViolations( Collection<Violation> violations )
90 {
91 this.violations = new HashSet<>( violations );
92 }
93
94 public List<Violation> getViolations()
95 {
96 return new ArrayList<>( violations );
97 }
98
99 public void setSuppressedViolations( Collection<SuppressedViolation> suppressedViolations )
100 {
101 this.suppressedViolations = new ArrayList<>( suppressedViolations );
102 }
103
104 public void setProcessingErrors( Collection<ProcessingError> errors )
105 {
106 this.processingErrors = new ArrayList<>( errors );
107 }
108
109 public List<ProcessingError> getProcessingErrors()
110 {
111 return processingErrors;
112 }
113
114
115
116
117
118
119
120
121
122
123
124 private String shortenFilename( String filename, PmdFileInfo fileInfo )
125 {
126 String result = filename;
127 if ( fileInfo != null && fileInfo.getSourceDirectory() != null )
128 {
129 result = StringUtils.substring( result, fileInfo.getSourceDirectory().getAbsolutePath().length() + 1 );
130 }
131 return StringUtils.replace( result, "\\", "/" );
132 }
133
134 private String makeFileSectionName( String filename, PmdFileInfo fileInfo )
135 {
136 if ( aggregate && fileInfo != null && fileInfo.getProject() != null )
137 {
138 return fileInfo.getProject().getName() + " - " + filename;
139 }
140 return filename;
141 }
142
143 private PmdFileInfo determineFileInfo( String filename )
144 throws IOException
145 {
146 File canonicalFilename = new File( filename ).getCanonicalFile();
147 PmdFileInfo fileInfo = files.get( canonicalFilename );
148 if ( fileInfo == null )
149 {
150 log.warn( "Couldn't determine PmdFileInfo for file " + filename + " (canonical: " + canonicalFilename
151 + "). XRef links won't be available." );
152 }
153
154 return fileInfo;
155 }
156
157 private void startFileSection( int level, String currentFilename, PmdFileInfo fileInfo )
158 {
159 sink.section( level, null );
160 sink.sectionTitle( level, null );
161
162
163 this.currentFilename = shortenFilename( currentFilename, fileInfo );
164
165 sink.text( makeFileSectionName( this.currentFilename, fileInfo ) );
166 sink.sectionTitle_( level );
167
168 sink.table();
169 sink.tableRow();
170 sink.tableHeaderCell();
171 sink.text( bundle.getString( "report.pmd.column.rule" ) );
172 sink.tableHeaderCell_();
173 sink.tableHeaderCell();
174 sink.text( bundle.getString( "report.pmd.column.violation" ) );
175 sink.tableHeaderCell_();
176 if ( this.renderRuleViolationPriority )
177 {
178 sink.tableHeaderCell();
179 sink.text( bundle.getString( "report.pmd.column.priority" ) );
180 sink.tableHeaderCell_();
181 }
182 sink.tableHeaderCell();
183 sink.text( bundle.getString( "report.pmd.column.line" ) );
184 sink.tableHeaderCell_();
185 sink.tableRow_();
186 }
187
188 private void endFileSection( int level )
189 {
190 sink.table_();
191 sink.section_( level );
192 }
193
194 private void addRuleName( Violation ruleViolation )
195 {
196 boolean hasUrl = StringUtils.isNotBlank( ruleViolation.getExternalInfoUrl() );
197
198 if ( hasUrl )
199 {
200 sink.link( ruleViolation.getExternalInfoUrl() );
201 }
202
203 sink.text( ruleViolation.getRule() );
204
205 if ( hasUrl )
206 {
207 sink.link_();
208 }
209 }
210
211 private void processSingleRuleViolation( Violation ruleViolation, PmdFileInfo fileInfo )
212 {
213 sink.tableRow();
214 sink.tableCell();
215 addRuleName( ruleViolation );
216 sink.tableCell_();
217 sink.tableCell();
218 sink.text( ruleViolation.getText() );
219 sink.tableCell_();
220
221 if ( this.renderRuleViolationPriority )
222 {
223 sink.tableCell();
224 sink.text( String.valueOf( RulePriority.valueOf( ruleViolation.getPriority() ).getPriority() ) );
225 sink.tableCell_();
226 }
227
228 sink.tableCell();
229
230 int beginLine = ruleViolation.getBeginline();
231 outputLineLink( beginLine, fileInfo );
232 int endLine = ruleViolation.getEndline();
233 if ( endLine != beginLine )
234 {
235 sink.text( "–" );
236 outputLineLink( endLine, fileInfo );
237 }
238
239 sink.tableCell_();
240 sink.tableRow_();
241 }
242
243
244
245
246 private void renderViolations()
247 throws IOException
248 {
249 sink.section1();
250 sink.sectionTitle1();
251 sink.text( bundle.getString( "report.pmd.files" ) );
252 sink.sectionTitle1_();
253
254
255
256 List<Violation> violations2 = new ArrayList<>( violations );
257 renderViolationsTable( 2, violations2 );
258
259 sink.section1_();
260 }
261
262 private void renderViolationsByPriority() throws IOException
263 {
264 if ( !renderViolationsByPriority )
265 {
266 return;
267 }
268
269 boolean oldPriorityColumn = this.renderRuleViolationPriority;
270 this.renderRuleViolationPriority = false;
271
272 sink.section1();
273 sink.sectionTitle1();
274 sink.text( bundle.getString( "report.pmd.violationsByPriority" ) );
275 sink.sectionTitle1_();
276
277 Map<RulePriority, List<Violation>> violationsByPriority = new HashMap<>();
278 for ( Violation violation : violations )
279 {
280 RulePriority priority = RulePriority.valueOf( violation.getPriority() );
281 List<Violation> violationSegment = violationsByPriority.get( priority );
282 if ( violationSegment == null )
283 {
284 violationSegment = new ArrayList<>();
285 violationsByPriority.put( priority, violationSegment );
286 }
287 violationSegment.add( violation );
288 }
289
290 for ( RulePriority priority : RulePriority.values() )
291 {
292 List<Violation> violationsWithPriority = violationsByPriority.get( priority );
293 if ( violationsWithPriority == null || violationsWithPriority.isEmpty() )
294 {
295 continue;
296 }
297
298 sink.section2();
299 sink.sectionTitle2();
300 sink.text( bundle.getString( "report.pmd.priority" ) + " " + priority.getPriority() );
301 sink.sectionTitle2_();
302
303 renderViolationsTable( 3, violationsWithPriority );
304
305 sink.section2_();
306 }
307
308 if ( violations.isEmpty() )
309 {
310 sink.paragraph();
311 sink.text( bundle.getString( "report.pmd.noProblems" ) );
312 sink.paragraph_();
313 }
314
315 sink.section1_();
316
317 this.renderRuleViolationPriority = oldPriorityColumn;
318 }
319
320 private void renderViolationsTable( int level, List<Violation> violationSegment )
321 throws IOException
322 {
323 Collections.sort( violationSegment, new Comparator<Violation>()
324 {
325
326 public int compare( Violation o1, Violation o2 )
327 {
328 int filenames = o1.getFileName().compareTo( o2.getFileName() );
329 if ( filenames == 0 )
330 {
331 return o1.getBeginline() - o2.getBeginline();
332 }
333 else
334 {
335 return filenames;
336 }
337 }
338 } );
339
340 boolean fileSectionStarted = false;
341 String previousFilename = null;
342 for ( Violation ruleViolation : violationSegment )
343 {
344 String currentFn = ruleViolation.getFileName();
345 PmdFileInfo fileInfo = determineFileInfo( currentFn );
346
347 if ( !currentFn.equalsIgnoreCase( previousFilename ) && fileSectionStarted )
348 {
349 endFileSection( level );
350 fileSectionStarted = false;
351 }
352 if ( !fileSectionStarted )
353 {
354 startFileSection( level, currentFn, fileInfo );
355 fileSectionStarted = true;
356 }
357
358 processSingleRuleViolation( ruleViolation, fileInfo );
359
360 previousFilename = currentFn;
361 }
362
363 if ( fileSectionStarted )
364 {
365 endFileSection( level );
366 }
367 }
368
369 private void outputLineLink( int line, PmdFileInfo fileInfo )
370 {
371 String xrefLocation = null;
372 if ( fileInfo != null )
373 {
374 xrefLocation = fileInfo.getXrefLocation();
375 }
376
377 if ( xrefLocation != null )
378 {
379 sink.link( xrefLocation + "/" + currentFilename.replaceAll( "\\.java$", ".html" ) + "#L" + line );
380 }
381 sink.text( String.valueOf( line ) );
382 if ( xrefLocation != null )
383 {
384 sink.link_();
385 }
386 }
387
388
389
390
391 private void renderSuppressedViolations()
392 throws IOException
393 {
394 sink.section1();
395 sink.sectionTitle1();
396 sink.text( bundle.getString( "report.pmd.suppressedViolations.title" ) );
397 sink.sectionTitle1_();
398
399 Collections.sort( suppressedViolations, new Comparator<SuppressedViolation>()
400 {
401 @Override
402 public int compare( SuppressedViolation o1, SuppressedViolation o2 )
403 {
404 return o1.getFilename().compareTo( o2.getFilename() );
405 }
406 } );
407
408 sink.table();
409 sink.tableRow();
410 sink.tableHeaderCell();
411 sink.text( bundle.getString( "report.pmd.suppressedViolations.column.filename" ) );
412 sink.tableHeaderCell_();
413 sink.tableHeaderCell();
414 sink.text( bundle.getString( "report.pmd.suppressedViolations.column.ruleMessage" ) );
415 sink.tableHeaderCell_();
416 sink.tableHeaderCell();
417 sink.text( bundle.getString( "report.pmd.suppressedViolations.column.suppressionType" ) );
418 sink.tableHeaderCell_();
419 sink.tableHeaderCell();
420 sink.text( bundle.getString( "report.pmd.suppressedViolations.column.userMessage" ) );
421 sink.tableHeaderCell_();
422 sink.tableRow_();
423
424 for ( SuppressedViolation suppressedViolation : suppressedViolations )
425 {
426 String filename = suppressedViolation.getFilename();
427 PmdFileInfo fileInfo = determineFileInfo( filename );
428 filename = shortenFilename( filename, fileInfo );
429
430 sink.tableRow();
431
432 sink.tableCell();
433 sink.text( filename );
434 sink.tableCell_();
435
436 sink.tableCell();
437 sink.text( suppressedViolation.getRuleMessage() );
438 sink.tableCell_();
439
440 sink.tableCell();
441 sink.text( suppressedViolation.getSuppressionType() );
442 sink.tableCell_();
443
444 sink.tableCell();
445 sink.text( suppressedViolation.getUserMessage() );
446 sink.tableCell_();
447
448 sink.tableRow_();
449 }
450
451 sink.table_();
452 sink.section1_();
453 }
454
455 private void processProcessingErrors() throws IOException
456 {
457
458
459 Collections.sort( processingErrors, new Comparator<ProcessingError>()
460 {
461 @Override
462 public int compare( ProcessingError e1, ProcessingError e2 )
463 {
464 return e1.getFilename().compareTo( e2.getFilename() );
465 }
466 } );
467
468 sink.section1();
469 sink.sectionTitle1();
470 sink.text( bundle.getString( "report.pmd.processingErrors.title" ) );
471 sink.sectionTitle1_();
472
473 sink.table();
474 sink.tableRow();
475 sink.tableHeaderCell();
476 sink.text( bundle.getString( "report.pmd.processingErrors.column.filename" ) );
477 sink.tableHeaderCell_();
478 sink.tableHeaderCell();
479 sink.text( bundle.getString( "report.pmd.processingErrors.column.problem" ) );
480 sink.tableHeaderCell_();
481 sink.tableRow_();
482
483 for ( ProcessingError error : processingErrors )
484 {
485 processSingleProcessingError( error );
486 }
487
488 sink.table_();
489
490 sink.section1_();
491 }
492
493 private void processSingleProcessingError( ProcessingError error ) throws IOException
494 {
495 String filename = error.getFilename();
496 PmdFileInfo fileInfo = determineFileInfo( filename );
497 filename = makeFileSectionName( shortenFilename( filename, fileInfo ), fileInfo );
498
499 sink.tableRow();
500 sink.tableCell();
501 sink.text( filename );
502 sink.tableCell_();
503 sink.tableCell();
504 sink.text( error.getMsg() );
505 sink.verbatim( null );
506 sink.rawText( error.getDetail() );
507 sink.verbatim_();
508 sink.tableCell_();
509 sink.tableRow_();
510 }
511
512 public void beginDocument()
513 {
514 sink.head();
515 sink.title();
516 sink.text( getTitle() );
517 sink.title_();
518 sink.head_();
519
520 sink.body();
521
522 sink.section1();
523 sink.sectionTitle1();
524 sink.text( getTitle() );
525 sink.sectionTitle1_();
526
527 sink.paragraph();
528 sink.text( bundle.getString( "report.pmd.pmdlink" ) + " " );
529 sink.link( "https://pmd.github.io" );
530 sink.text( "PMD" );
531 sink.link_();
532 sink.text( " " + AbstractPmdReport.getPmdVersion() + "." );
533 sink.paragraph_();
534
535 sink.section1_();
536
537
538 }
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554 public void render()
555 throws IOException
556 {
557 if ( !violations.isEmpty() )
558 {
559 renderViolationsByPriority();
560
561 renderViolations();
562 }
563 else
564 {
565 sink.paragraph();
566 sink.text( bundle.getString( "report.pmd.noProblems" ) );
567 sink.paragraph_();
568 }
569
570 if ( !suppressedViolations.isEmpty() )
571 {
572 renderSuppressedViolations();
573 }
574
575 if ( !processingErrors.isEmpty() )
576 {
577 processProcessingErrors();
578 }
579 }
580
581 public void endDocument()
582 throws IOException
583 {
584
585
586
587
588
589
590
591 sink.body_();
592
593 sink.flush();
594
595 sink.close();
596 }
597
598 public void setFiles( Map<File, PmdFileInfo> files )
599 {
600 this.files = files;
601 }
602
603 public void setRenderRuleViolationPriority( boolean renderRuleViolationPriority )
604 {
605 this.renderRuleViolationPriority = renderRuleViolationPriority;
606 }
607
608 public void setRenderViolationsByPriority( boolean renderViolationsByPriority )
609 {
610 this.renderViolationsByPriority = renderViolationsByPriority;
611 }
612 }