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