1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.pmd;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.UncheckedIOException;
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.List;
30 import java.util.Locale;
31 import java.util.Map;
32
33 import net.sourceforge.pmd.RulePriority;
34 import org.apache.maven.doxia.sink.Sink;
35 import org.apache.maven.plugin.logging.Log;
36 import org.apache.maven.plugins.pmd.model.ProcessingError;
37 import org.apache.maven.plugins.pmd.model.SuppressedViolation;
38 import org.apache.maven.plugins.pmd.model.Violation;
39 import org.apache.maven.reporting.AbstractMavenReportRenderer;
40 import org.codehaus.plexus.i18n.I18N;
41 import org.codehaus.plexus.util.StringUtils;
42
43
44
45
46
47
48
49 public class PmdReportRenderer extends AbstractMavenReportRenderer {
50 private final Log log;
51
52 private final I18N i18n;
53
54 private final Locale locale;
55
56 private final Map<File, PmdFileInfo> files;
57
58
59 private String currentFilename;
60
61 private final Collection<Violation> violations;
62
63 private boolean renderRuleViolationPriority;
64
65 private final boolean renderViolationsByPriority;
66
67 private final boolean aggregate;
68
69 private Collection<SuppressedViolation> suppressedViolations = new ArrayList<>();
70
71 private Collection<ProcessingError> processingErrors = new ArrayList<>();
72
73 public PmdReportRenderer(
74 Log log,
75 Sink sink,
76 I18N i18n,
77 Locale locale,
78 Map<File, PmdFileInfo> files,
79 Collection<Violation> violations,
80 boolean renderRuleViolationPriority,
81 boolean renderViolationsByPriority,
82 boolean aggregate) {
83 super(sink);
84 this.log = log;
85 this.i18n = i18n;
86 this.locale = locale;
87 this.files = files;
88 this.violations = violations;
89 this.renderRuleViolationPriority = renderRuleViolationPriority;
90 this.renderViolationsByPriority = renderViolationsByPriority;
91 this.aggregate = aggregate;
92 }
93
94 public void setSuppressedViolations(Collection<SuppressedViolation> suppressedViolations) {
95 this.suppressedViolations = suppressedViolations;
96 }
97
98 public void setProcessingErrors(Collection<ProcessingError> processingErrors) {
99 this.processingErrors = processingErrors;
100 }
101
102 @Override
103 public String getTitle() {
104 return getI18nString("title");
105 }
106
107
108
109
110
111 private String getI18nString(String key) {
112 return i18n.getString("pmd-report", locale, "report.pmd." + key);
113 }
114
115 public void renderBody() {
116 startSection(getTitle());
117
118 sink.paragraph();
119 sink.text(getI18nString("pmdlink") + " ");
120 link("https://pmd.github.io", "PMD");
121 sink.text(" " + AbstractPmdReport.getPmdVersion() + ".");
122 sink.paragraph_();
123
124 if (!violations.isEmpty()) {
125 renderViolationsByPriority();
126
127 renderViolations();
128 } else {
129 paragraph(getI18nString("noProblems"));
130 }
131
132 renderSuppressedViolations();
133
134 renderProcessingErrors();
135
136 endSection();
137 }
138
139 private void startFileSection(String currentFilename, PmdFileInfo fileInfo) {
140
141 this.currentFilename = shortenFilename(currentFilename, fileInfo);
142
143 startSection(makeFileSectionName(this.currentFilename, fileInfo));
144
145 startTable();
146 sink.tableRow();
147 tableHeaderCell(getI18nString("column.rule"));
148 tableHeaderCell(getI18nString("column.violation"));
149 if (this.renderRuleViolationPriority) {
150 tableHeaderCell(getI18nString("column.priority"));
151 }
152 tableHeaderCell(getI18nString("column.line"));
153 sink.tableRow_();
154 }
155
156 private void endFileSection() {
157 endTable();
158 endSection();
159 }
160
161 private void addRuleName(Violation ruleViolation) {
162 boolean hasUrl = StringUtils.isNotBlank(ruleViolation.getExternalInfoUrl());
163
164 if (hasUrl) {
165 sink.link(ruleViolation.getExternalInfoUrl());
166 }
167
168 sink.text(ruleViolation.getRule());
169
170 if (hasUrl) {
171 sink.link_();
172 }
173 }
174
175 private void renderSingleRuleViolation(Violation ruleViolation, PmdFileInfo fileInfo) {
176 sink.tableRow();
177 sink.tableCell();
178 addRuleName(ruleViolation);
179 sink.tableCell_();
180 tableCell(ruleViolation.getText());
181
182 if (this.renderRuleViolationPriority) {
183 tableCell(String.valueOf(
184 RulePriority.valueOf(ruleViolation.getPriority()).getPriority()));
185 }
186
187 sink.tableCell();
188
189 int beginLine = ruleViolation.getBeginline();
190 outputLineLink(beginLine, fileInfo);
191 int endLine = ruleViolation.getEndline();
192 if (endLine != beginLine) {
193 sink.text("–");
194 outputLineLink(endLine, fileInfo);
195 }
196
197 sink.tableCell_();
198 sink.tableRow_();
199 }
200
201
202
203
204 private void renderViolations() {
205 startSection(getI18nString("files"));
206
207
208 renderViolationsTable(violations);
209
210 endSection();
211 }
212
213 private void renderViolationsByPriority() {
214 if (!renderViolationsByPriority) {
215 return;
216 }
217
218 boolean oldPriorityColumn = this.renderRuleViolationPriority;
219 this.renderRuleViolationPriority = false;
220
221 startSection(getI18nString("violationsByPriority"));
222
223 Map<RulePriority, List<Violation>> violationsByPriority = new HashMap<>();
224 for (Violation violation : violations) {
225 RulePriority priority = RulePriority.valueOf(violation.getPriority());
226 List<Violation> violationSegment = violationsByPriority.get(priority);
227 if (violationSegment == null) {
228 violationSegment = new ArrayList<>();
229 violationsByPriority.put(priority, violationSegment);
230 }
231 violationSegment.add(violation);
232 }
233
234 for (RulePriority priority : RulePriority.values()) {
235 List<Violation> violationsWithPriority = violationsByPriority.get(priority);
236 if (violationsWithPriority == null || violationsWithPriority.isEmpty()) {
237 continue;
238 }
239
240 startSection(getI18nString("priority") + " " + priority.getPriority());
241
242 renderViolationsTable(violationsWithPriority);
243
244 endSection();
245 }
246
247 if (violations.isEmpty()) {
248 paragraph(getI18nString("noProblems"));
249 }
250
251 endSection();
252
253 this.renderRuleViolationPriority = oldPriorityColumn;
254 }
255
256 private void renderViolationsTable(Collection<Violation> violationSegment) {
257 List<Violation> violationSegmentCopy = new ArrayList<>(violationSegment);
258 Collections.sort(violationSegmentCopy, new Comparator<Violation>() {
259
260 public int compare(Violation o1, Violation o2) {
261 int filenames = o1.getFileName().compareTo(o2.getFileName());
262 if (filenames == 0) {
263 return o1.getBeginline() - o2.getBeginline();
264 } else {
265 return filenames;
266 }
267 }
268 });
269
270 boolean fileSectionStarted = false;
271 String previousFilename = null;
272 for (Violation ruleViolation : violationSegmentCopy) {
273 String currentFn = ruleViolation.getFileName();
274 PmdFileInfo fileInfo = determineFileInfo(currentFn);
275
276 if (!currentFn.equalsIgnoreCase(previousFilename) && fileSectionStarted) {
277 endFileSection();
278 fileSectionStarted = false;
279 }
280 if (!fileSectionStarted) {
281 startFileSection(currentFn, fileInfo);
282 fileSectionStarted = true;
283 }
284
285 renderSingleRuleViolation(ruleViolation, fileInfo);
286
287 previousFilename = currentFn;
288 }
289
290 if (fileSectionStarted) {
291 endFileSection();
292 }
293 }
294
295 private void outputLineLink(int line, PmdFileInfo fileInfo) {
296 String xrefLocation = null;
297 if (fileInfo != null) {
298 xrefLocation = fileInfo.getXrefLocation();
299 }
300
301 if (xrefLocation != null) {
302 sink.link(xrefLocation + "/" + currentFilename.replaceAll("\\.java$", ".html") + "#L" + line);
303 }
304 sink.text(String.valueOf(line));
305 if (xrefLocation != null) {
306 sink.link_();
307 }
308 }
309
310
311
312
313 private void renderSuppressedViolations() {
314 if (suppressedViolations.isEmpty()) {
315 return;
316 }
317
318 startSection(getI18nString("suppressedViolations.title"));
319
320 List<SuppressedViolation> suppressedViolationsCopy = new ArrayList<>(suppressedViolations);
321 Collections.sort(suppressedViolationsCopy, new Comparator<SuppressedViolation>() {
322 @Override
323 public int compare(SuppressedViolation o1, SuppressedViolation o2) {
324 return o1.getFilename().compareTo(o2.getFilename());
325 }
326 });
327
328 startTable();
329 tableHeader(new String[] {
330 getI18nString("suppressedViolations.column.filename"),
331 getI18nString("suppressedViolations.column.ruleMessage"),
332 getI18nString("suppressedViolations.column.suppressionType"),
333 getI18nString("suppressedViolations.column.userMessage")
334 });
335
336 for (SuppressedViolation suppressedViolation : suppressedViolationsCopy) {
337 String filename = suppressedViolation.getFilename();
338 PmdFileInfo fileInfo = determineFileInfo(filename);
339 filename = shortenFilename(filename, fileInfo);
340
341 tableRow(new String[] {
342 filename,
343 suppressedViolation.getRuleMessage(),
344 suppressedViolation.getSuppressionType(),
345 suppressedViolation.getUserMessage()
346 });
347 }
348
349 endTable();
350 endSection();
351 }
352
353 private void renderProcessingErrors() {
354 if (processingErrors.isEmpty()) {
355 return;
356 }
357
358
359
360 List<ProcessingError> processingErrorsCopy = new ArrayList<>(processingErrors);
361 Collections.sort(processingErrorsCopy, new Comparator<ProcessingError>() {
362 @Override
363 public int compare(ProcessingError e1, ProcessingError e2) {
364 return e1.getFilename().compareTo(e2.getFilename());
365 }
366 });
367
368 startSection(getI18nString("processingErrors.title"));
369
370 startTable();
371 tableHeader(new String[] {
372 getI18nString("processingErrors.column.filename"), getI18nString("processingErrors.column.problem")
373 });
374
375 for (ProcessingError error : processingErrorsCopy) {
376 renderSingleProcessingError(error);
377 }
378
379 endTable();
380 endSection();
381 }
382
383 private void renderSingleProcessingError(ProcessingError error) {
384 String filename = error.getFilename();
385 PmdFileInfo fileInfo = determineFileInfo(filename);
386 filename = makeFileSectionName(shortenFilename(filename, fileInfo), fileInfo);
387
388 sink.tableRow();
389 tableCell(filename);
390 sink.tableCell();
391 sink.text(error.getMsg());
392 sink.verbatim(null);
393 sink.rawText(error.getDetail());
394 sink.verbatim_();
395 sink.tableCell_();
396 sink.tableRow_();
397 }
398
399 private String shortenFilename(String filename, PmdFileInfo fileInfo) {
400 String result = filename;
401 if (fileInfo != null && fileInfo.getSourceDirectory() != null) {
402 result = StringUtils.substring(
403 result, fileInfo.getSourceDirectory().getAbsolutePath().length() + 1);
404 }
405 return StringUtils.replace(result, "\\", "/");
406 }
407
408 private String makeFileSectionName(String filename, PmdFileInfo fileInfo) {
409 if (aggregate && fileInfo != null && fileInfo.getProject() != null) {
410 return fileInfo.getProject().getName() + " - " + filename;
411 }
412 return filename;
413 }
414
415 private PmdFileInfo determineFileInfo(String filename) {
416 try {
417 File canonicalFilename = new File(filename).getCanonicalFile();
418 PmdFileInfo fileInfo = files.get(canonicalFilename);
419 if (fileInfo == null) {
420 log.warn("Couldn't determine PmdFileInfo for file " + filename + " (canonical: " + canonicalFilename
421 + "). XRef links won't be available.");
422 }
423 return fileInfo;
424 } catch (IOException e) {
425 throw new UncheckedIOException(e);
426 }
427 }
428 }