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.lang.rule.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 protected 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
181 sink.tableCell();
182 sink.text(ruleViolation.getText());
183 sink.tableCell_();
184
185 if (this.renderRuleViolationPriority) {
186 tableCell(String.valueOf(
187 RulePriority.valueOf(ruleViolation.getPriority()).getPriority()));
188 }
189
190 sink.tableCell();
191
192 int beginLine = ruleViolation.getBeginline();
193 outputLineLink(beginLine, fileInfo);
194 int endLine = ruleViolation.getEndline();
195 if (endLine != beginLine) {
196 sink.text("–");
197 outputLineLink(endLine, fileInfo);
198 }
199
200 sink.tableCell_();
201 sink.tableRow_();
202 }
203
204
205
206
207 private void renderViolations() {
208 startSection(getI18nString("files"));
209
210
211 renderViolationsTable(violations);
212
213 endSection();
214 }
215
216 private void renderViolationsByPriority() {
217 if (!renderViolationsByPriority) {
218 return;
219 }
220
221 boolean oldPriorityColumn = this.renderRuleViolationPriority;
222 this.renderRuleViolationPriority = false;
223
224 startSection(getI18nString("violationsByPriority"));
225
226 Map<RulePriority, List<Violation>> violationsByPriority = new HashMap<>();
227 for (Violation violation : violations) {
228 RulePriority priority = RulePriority.valueOf(violation.getPriority());
229 List<Violation> violationSegment = violationsByPriority.get(priority);
230 if (violationSegment == null) {
231 violationSegment = new ArrayList<>();
232 violationsByPriority.put(priority, violationSegment);
233 }
234 violationSegment.add(violation);
235 }
236
237 for (RulePriority priority : RulePriority.values()) {
238 List<Violation> violationsWithPriority = violationsByPriority.get(priority);
239 if (violationsWithPriority == null || violationsWithPriority.isEmpty()) {
240 continue;
241 }
242
243 startSection(getI18nString("priority") + " " + priority.getPriority());
244
245 renderViolationsTable(violationsWithPriority);
246
247 endSection();
248 }
249
250 if (violations.isEmpty()) {
251 paragraph(getI18nString("noProblems"));
252 }
253
254 endSection();
255
256 this.renderRuleViolationPriority = oldPriorityColumn;
257 }
258
259 private void renderViolationsTable(Collection<Violation> violationSegment) {
260 List<Violation> violationSegmentCopy = new ArrayList<>(violationSegment);
261 Collections.sort(violationSegmentCopy, new Comparator<Violation>() {
262
263 public int compare(Violation o1, Violation o2) {
264 int filenames = o1.getFileName().compareTo(o2.getFileName());
265 if (filenames == 0) {
266 return o1.getBeginline() - o2.getBeginline();
267 } else {
268 return filenames;
269 }
270 }
271 });
272
273 boolean fileSectionStarted = false;
274 String previousFilename = null;
275 for (Violation ruleViolation : violationSegmentCopy) {
276 String currentFn = ruleViolation.getFileName();
277 PmdFileInfo fileInfo = determineFileInfo(currentFn);
278
279 if (!currentFn.equalsIgnoreCase(previousFilename) && fileSectionStarted) {
280 endFileSection();
281 fileSectionStarted = false;
282 }
283 if (!fileSectionStarted) {
284 startFileSection(currentFn, fileInfo);
285 fileSectionStarted = true;
286 }
287
288 renderSingleRuleViolation(ruleViolation, fileInfo);
289
290 previousFilename = currentFn;
291 }
292
293 if (fileSectionStarted) {
294 endFileSection();
295 }
296 }
297
298 private void outputLineLink(int line, PmdFileInfo fileInfo) {
299 String xrefLocation = null;
300 if (fileInfo != null) {
301 xrefLocation = fileInfo.getXrefLocation();
302 }
303
304 if (xrefLocation != null) {
305 sink.link(xrefLocation + "/" + currentFilename.replaceAll("\\.java$", ".html") + "#L" + line);
306 }
307 sink.text(String.valueOf(line));
308 if (xrefLocation != null) {
309 sink.link_();
310 }
311 }
312
313
314
315
316 private void renderSuppressedViolations() {
317 if (suppressedViolations.isEmpty()) {
318 return;
319 }
320
321 startSection(getI18nString("suppressedViolations.title"));
322
323 List<SuppressedViolation> suppressedViolationsCopy = new ArrayList<>(suppressedViolations);
324 Collections.sort(suppressedViolationsCopy, new Comparator<SuppressedViolation>() {
325 @Override
326 public int compare(SuppressedViolation o1, SuppressedViolation o2) {
327 return o1.getFilename().compareTo(o2.getFilename());
328 }
329 });
330
331 startTable();
332 tableHeader(new String[] {
333 getI18nString("suppressedViolations.column.filename"),
334 getI18nString("suppressedViolations.column.ruleMessage"),
335 getI18nString("suppressedViolations.column.suppressionType"),
336 getI18nString("suppressedViolations.column.userMessage")
337 });
338
339 for (SuppressedViolation suppressedViolation : suppressedViolationsCopy) {
340 String filename = suppressedViolation.getFilename();
341 PmdFileInfo fileInfo = determineFileInfo(filename);
342 filename = shortenFilename(filename, fileInfo);
343
344
345 sink.tableRow();
346 tableCell(filename);
347 sink.tableCell();
348 sink.text(suppressedViolation.getRuleMessage());
349 sink.tableCell_();
350 tableCell(suppressedViolation.getSuppressionType());
351 sink.tableCell();
352 sink.text(suppressedViolation.getUserMessage());
353 sink.tableCell_();
354 sink.tableRow_();
355 }
356
357 endTable();
358 endSection();
359 }
360
361 private void renderProcessingErrors() {
362 if (processingErrors.isEmpty()) {
363 return;
364 }
365
366
367
368 List<ProcessingError> processingErrorsCopy = new ArrayList<>(processingErrors);
369 Collections.sort(processingErrorsCopy, new Comparator<ProcessingError>() {
370 @Override
371 public int compare(ProcessingError e1, ProcessingError e2) {
372 return e1.getFilename().compareTo(e2.getFilename());
373 }
374 });
375
376 startSection(getI18nString("processingErrors.title"));
377
378 startTable();
379 tableHeader(new String[] {
380 getI18nString("processingErrors.column.filename"), getI18nString("processingErrors.column.problem")
381 });
382
383 for (ProcessingError error : processingErrorsCopy) {
384 renderSingleProcessingError(error);
385 }
386
387 endTable();
388 endSection();
389 }
390
391 private void renderSingleProcessingError(ProcessingError error) {
392 String filename = error.getFilename();
393 PmdFileInfo fileInfo = determineFileInfo(filename);
394 filename = makeFileSectionName(shortenFilename(filename, fileInfo), fileInfo);
395
396 sink.tableRow();
397 tableCell(filename);
398 sink.tableCell();
399 sink.text(error.getMsg());
400 sink.verbatim(null);
401 sink.rawText(error.getDetail());
402 sink.verbatim_();
403 sink.tableCell_();
404 sink.tableRow_();
405 }
406
407 private String shortenFilename(String filename, PmdFileInfo fileInfo) {
408 String result = filename;
409 if (fileInfo != null && fileInfo.getSourceDirectory() != null) {
410 result = StringUtils.substring(
411 result, fileInfo.getSourceDirectory().getAbsolutePath().length() + 1);
412 }
413 return StringUtils.replace(result, "\\", "/");
414 }
415
416 private String makeFileSectionName(String filename, PmdFileInfo fileInfo) {
417 if (aggregate && fileInfo != null && fileInfo.getProject() != null) {
418 return fileInfo.getProject().getName() + " - " + filename;
419 }
420 return filename;
421 }
422
423 private PmdFileInfo determineFileInfo(String filename) {
424 try {
425 File canonicalFilename = new File(filename).getCanonicalFile();
426 PmdFileInfo fileInfo = files.get(canonicalFilename);
427 if (fileInfo == null) {
428 log.warn("Couldn't determine PmdFileInfo for file " + filename + " (canonical: " + canonicalFilename
429 + "). XRef links won't be available.");
430 }
431 return fileInfo;
432 } catch (IOException e) {
433 throw new UncheckedIOException(e);
434 }
435 }
436 }