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