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.tableRows( null, false );
170 sink.tableRow();
171 sink.tableHeaderCell();
172 sink.text( bundle.getString( "report.pmd.column.rule" ) );
173 sink.tableHeaderCell_();
174 sink.tableHeaderCell();
175 sink.text( bundle.getString( "report.pmd.column.violation" ) );
176 sink.tableHeaderCell_();
177 if ( this.renderRuleViolationPriority )
178 {
179 sink.tableHeaderCell();
180 sink.text( bundle.getString( "report.pmd.column.priority" ) );
181 sink.tableHeaderCell_();
182 }
183 sink.tableHeaderCell();
184 sink.text( bundle.getString( "report.pmd.column.line" ) );
185 sink.tableHeaderCell_();
186 sink.tableRow_();
187 }
188
189 private void endFileSection( int level )
190 {
191 sink.tableRows_();
192 sink.table_();
193 sink.section_( level );
194 }
195
196 private void addRuleName( Violation ruleViolation )
197 {
198 boolean hasUrl = StringUtils.isNotBlank( ruleViolation.getExternalInfoUrl() );
199
200 if ( hasUrl )
201 {
202 sink.link( ruleViolation.getExternalInfoUrl() );
203 }
204
205 sink.text( ruleViolation.getRule() );
206
207 if ( hasUrl )
208 {
209 sink.link_();
210 }
211 }
212
213 private void processSingleRuleViolation( Violation ruleViolation, PmdFileInfo fileInfo )
214 {
215 sink.tableRow();
216 sink.tableCell();
217 addRuleName( ruleViolation );
218 sink.tableCell_();
219 sink.tableCell();
220 sink.text( ruleViolation.getText() );
221 sink.tableCell_();
222
223 if ( this.renderRuleViolationPriority )
224 {
225 sink.tableCell();
226 sink.text( String.valueOf( RulePriority.valueOf( ruleViolation.getPriority() ).getPriority() ) );
227 sink.tableCell_();
228 }
229
230 sink.tableCell();
231
232 int beginLine = ruleViolation.getBeginline();
233 outputLineLink( beginLine, fileInfo );
234 int endLine = ruleViolation.getEndline();
235 if ( endLine != beginLine )
236 {
237 sink.text( "–" );
238 outputLineLink( endLine, fileInfo );
239 }
240
241 sink.tableCell_();
242 sink.tableRow_();
243 }
244
245
246
247
248 private void renderViolations()
249 throws IOException
250 {
251 sink.section1();
252 sink.sectionTitle1();
253 sink.text( bundle.getString( "report.pmd.files" ) );
254 sink.sectionTitle1_();
255
256
257
258 List<Violation> violations2 = new ArrayList<>( violations );
259 renderViolationsTable( 2, violations2 );
260
261 sink.section1_();
262 }
263
264 private void renderViolationsByPriority() throws IOException
265 {
266 if ( !renderViolationsByPriority )
267 {
268 return;
269 }
270
271 boolean oldPriorityColumn = this.renderRuleViolationPriority;
272 this.renderRuleViolationPriority = false;
273
274 sink.section1();
275 sink.sectionTitle1();
276 sink.text( bundle.getString( "report.pmd.violationsByPriority" ) );
277 sink.sectionTitle1_();
278
279 Map<RulePriority, List<Violation>> violationsByPriority = new HashMap<>();
280 for ( Violation violation : violations )
281 {
282 RulePriority priority = RulePriority.valueOf( violation.getPriority() );
283 List<Violation> violationSegment = violationsByPriority.get( priority );
284 if ( violationSegment == null )
285 {
286 violationSegment = new ArrayList<>();
287 violationsByPriority.put( priority, violationSegment );
288 }
289 violationSegment.add( violation );
290 }
291
292 for ( RulePriority priority : RulePriority.values() )
293 {
294 List<Violation> violationsWithPriority = violationsByPriority.get( priority );
295 if ( violationsWithPriority == null || violationsWithPriority.isEmpty() )
296 {
297 continue;
298 }
299
300 sink.section2();
301 sink.sectionTitle2();
302 sink.text( bundle.getString( "report.pmd.priority" ) + " " + priority.getPriority() );
303 sink.sectionTitle2_();
304
305 renderViolationsTable( 3, violationsWithPriority );
306
307 sink.section2_();
308 }
309
310 if ( violations.isEmpty() )
311 {
312 sink.paragraph();
313 sink.text( bundle.getString( "report.pmd.noProblems" ) );
314 sink.paragraph_();
315 }
316
317 sink.section1_();
318
319 this.renderRuleViolationPriority = oldPriorityColumn;
320 }
321
322 private void renderViolationsTable( int level, List<Violation> violationSegment )
323 throws IOException
324 {
325 Collections.sort( violationSegment, new Comparator<Violation>()
326 {
327
328 public int compare( Violation o1, Violation o2 )
329 {
330 int filenames = o1.getFileName().compareTo( o2.getFileName() );
331 if ( filenames == 0 )
332 {
333 return o1.getBeginline() - o2.getBeginline();
334 }
335 else
336 {
337 return filenames;
338 }
339 }
340 } );
341
342 boolean fileSectionStarted = false;
343 String previousFilename = null;
344 for ( Violation ruleViolation : violationSegment )
345 {
346 String currentFn = ruleViolation.getFileName();
347 PmdFileInfo fileInfo = determineFileInfo( currentFn );
348
349 if ( !currentFn.equalsIgnoreCase( previousFilename ) && fileSectionStarted )
350 {
351 endFileSection( level );
352 fileSectionStarted = false;
353 }
354 if ( !fileSectionStarted )
355 {
356 startFileSection( level, currentFn, fileInfo );
357 fileSectionStarted = true;
358 }
359
360 processSingleRuleViolation( ruleViolation, fileInfo );
361
362 previousFilename = currentFn;
363 }
364
365 if ( fileSectionStarted )
366 {
367 endFileSection( level );
368 }
369 }
370
371 private void outputLineLink( int line, PmdFileInfo fileInfo )
372 {
373 String xrefLocation = null;
374 if ( fileInfo != null )
375 {
376 xrefLocation = fileInfo.getXrefLocation();
377 }
378
379 if ( xrefLocation != null )
380 {
381 sink.link( xrefLocation + "/" + currentFilename.replaceAll( "\\.java$", ".html" ) + "#L" + line );
382 }
383 sink.text( String.valueOf( line ) );
384 if ( xrefLocation != null )
385 {
386 sink.link_();
387 }
388 }
389
390
391
392
393 private void renderSuppressedViolations()
394 throws IOException
395 {
396 sink.section1();
397 sink.sectionTitle1();
398 sink.text( bundle.getString( "report.pmd.suppressedViolations.title" ) );
399 sink.sectionTitle1_();
400
401 Collections.sort( suppressedViolations, new Comparator<SuppressedViolation>()
402 {
403 @Override
404 public int compare( SuppressedViolation o1, SuppressedViolation o2 )
405 {
406 return o1.getFilename().compareTo( o2.getFilename() );
407 }
408 } );
409
410 sink.table();
411 sink.tableRows( null, false );
412 sink.tableRow();
413 sink.tableHeaderCell();
414 sink.text( bundle.getString( "report.pmd.suppressedViolations.column.filename" ) );
415 sink.tableHeaderCell_();
416 sink.tableHeaderCell();
417 sink.text( bundle.getString( "report.pmd.suppressedViolations.column.ruleMessage" ) );
418 sink.tableHeaderCell_();
419 sink.tableHeaderCell();
420 sink.text( bundle.getString( "report.pmd.suppressedViolations.column.suppressionType" ) );
421 sink.tableHeaderCell_();
422 sink.tableHeaderCell();
423 sink.text( bundle.getString( "report.pmd.suppressedViolations.column.userMessage" ) );
424 sink.tableHeaderCell_();
425 sink.tableRow_();
426
427 for ( SuppressedViolation suppressedViolation : suppressedViolations )
428 {
429 String filename = suppressedViolation.getFilename();
430 PmdFileInfo fileInfo = determineFileInfo( filename );
431 filename = shortenFilename( filename, fileInfo );
432
433 sink.tableRow();
434
435 sink.tableCell();
436 sink.text( filename );
437 sink.tableCell_();
438
439 sink.tableCell();
440 sink.text( suppressedViolation.getRuleMessage() );
441 sink.tableCell_();
442
443 sink.tableCell();
444 sink.text( suppressedViolation.getSuppressionType() );
445 sink.tableCell_();
446
447 sink.tableCell();
448 sink.text( suppressedViolation.getUserMessage() );
449 sink.tableCell_();
450
451 sink.tableRow_();
452 }
453
454 sink.tableRows_();
455 sink.table_();
456 sink.section1_();
457 }
458
459 private void processProcessingErrors() throws IOException
460 {
461
462
463 Collections.sort( processingErrors, new Comparator<ProcessingError>()
464 {
465 @Override
466 public int compare( ProcessingError e1, ProcessingError e2 )
467 {
468 return e1.getFilename().compareTo( e2.getFilename() );
469 }
470 } );
471
472 sink.section1();
473 sink.sectionTitle1();
474 sink.text( bundle.getString( "report.pmd.processingErrors.title" ) );
475 sink.sectionTitle1_();
476
477 sink.table();
478 sink.tableRows( null, false );
479 sink.tableRow();
480 sink.tableHeaderCell();
481 sink.text( bundle.getString( "report.pmd.processingErrors.column.filename" ) );
482 sink.tableHeaderCell_();
483 sink.tableHeaderCell();
484 sink.text( bundle.getString( "report.pmd.processingErrors.column.problem" ) );
485 sink.tableHeaderCell_();
486 sink.tableRow_();
487
488 for ( ProcessingError error : processingErrors )
489 {
490 processSingleProcessingError( error );
491 }
492
493 sink.tableRows_();
494 sink.table_();
495
496 sink.section1_();
497 }
498
499 private void processSingleProcessingError( ProcessingError error ) throws IOException
500 {
501 String filename = error.getFilename();
502 PmdFileInfo fileInfo = determineFileInfo( filename );
503 filename = makeFileSectionName( shortenFilename( filename, fileInfo ), fileInfo );
504
505 sink.tableRow();
506 sink.tableCell();
507 sink.text( filename );
508 sink.tableCell_();
509 sink.tableCell();
510 sink.text( error.getMsg() );
511 sink.verbatim( null );
512 sink.rawText( error.getDetail() );
513 sink.verbatim_();
514 sink.tableCell_();
515 sink.tableRow_();
516 }
517
518 public void beginDocument()
519 {
520 sink.head();
521 sink.title();
522 sink.text( getTitle() );
523 sink.title_();
524 sink.head_();
525
526 sink.body();
527
528 sink.section1();
529 sink.sectionTitle1();
530 sink.text( getTitle() );
531 sink.sectionTitle1_();
532
533 sink.paragraph();
534 sink.text( bundle.getString( "report.pmd.pmdlink" ) + " " );
535 sink.link( "https://pmd.github.io" );
536 sink.text( "PMD" );
537 sink.link_();
538 sink.text( " " + AbstractPmdReport.getPmdVersion() + "." );
539 sink.paragraph_();
540
541 sink.section1_();
542
543
544 }
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560 public void render()
561 throws IOException
562 {
563 if ( !violations.isEmpty() )
564 {
565 renderViolationsByPriority();
566
567 renderViolations();
568 }
569 else
570 {
571 sink.paragraph();
572 sink.text( bundle.getString( "report.pmd.noProblems" ) );
573 sink.paragraph_();
574 }
575
576 if ( !suppressedViolations.isEmpty() )
577 {
578 renderSuppressedViolations();
579 }
580
581 if ( !processingErrors.isEmpty() )
582 {
583 processProcessingErrors();
584 }
585 }
586
587 public void endDocument()
588 throws IOException
589 {
590
591
592
593
594
595
596
597 sink.body_();
598
599 sink.flush();
600
601 sink.close();
602 }
603
604 public void setFiles( Map<File, PmdFileInfo> files )
605 {
606 this.files = files;
607 }
608
609 public void setRenderRuleViolationPriority( boolean renderRuleViolationPriority )
610 {
611 this.renderRuleViolationPriority = renderRuleViolationPriority;
612 }
613
614 public void setRenderViolationsByPriority( boolean renderViolationsByPriority )
615 {
616 this.renderViolationsByPriority = renderViolationsByPriority;
617 }
618 }