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