1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.checkstyle;
20
21 import java.io.File;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.Locale;
27
28 import com.puppycrawl.tools.checkstyle.api.AuditEvent;
29 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
30 import com.puppycrawl.tools.checkstyle.api.Configuration;
31 import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
32 import org.apache.commons.lang3.StringUtils;
33 import org.apache.maven.doxia.sink.Sink;
34 import org.apache.maven.doxia.tools.SiteTool;
35 import org.apache.maven.doxia.util.DoxiaUtils;
36 import org.apache.maven.plugins.checkstyle.exec.CheckstyleResults;
37 import org.apache.maven.project.MavenProject;
38 import org.apache.maven.reporting.AbstractMavenReportRenderer;
39 import org.codehaus.plexus.i18n.I18N;
40
41
42
43
44
45
46 public class CheckstyleReportRenderer extends AbstractMavenReportRenderer {
47 private static final int NO_TEXT = 0;
48 private static final int TEXT_SIMPLE = 1;
49 private static final int TEXT_TITLE = 2;
50 private static final int TEXT_ABBREV = 3;
51
52 private final I18N i18n;
53
54 private final Locale locale;
55
56 private final MavenProject project;
57
58 private final Configuration checkstyleConfig;
59
60 private final boolean enableRulesSummary;
61
62 private final boolean enableSeveritySummary;
63
64 private final boolean enableFilesSummary;
65
66 private final SiteTool siteTool;
67
68 private String xrefLocation;
69
70 private String xrefTestLocation;
71
72 private List<File> testSourceDirectories = new ArrayList<>();
73
74 private List<String> treeWalkerNames = Collections.singletonList("TreeWalker");
75
76 private final String ruleset;
77
78 private final CheckstyleResults results;
79
80 public CheckstyleReportRenderer(
81 Sink sink,
82 I18N i18n,
83 Locale locale,
84 MavenProject project,
85 SiteTool siteTool,
86 String ruleset,
87 boolean enableRulesSummary,
88 boolean enableSeveritySummary,
89 boolean enableFilesSummary,
90 CheckstyleResults results) {
91 super(sink);
92 this.i18n = i18n;
93 this.locale = locale;
94 this.project = project;
95 this.siteTool = siteTool;
96 this.ruleset = ruleset;
97 this.enableRulesSummary = enableRulesSummary;
98 this.enableSeveritySummary = enableSeveritySummary;
99 this.enableFilesSummary = enableFilesSummary;
100 this.results = results;
101 this.checkstyleConfig = results.getConfiguration();
102 }
103
104 @Override
105 public String getTitle() {
106 return getI18nString("title");
107 }
108
109
110
111
112
113 private String getI18nString(String key) {
114 return i18n.getString("checkstyle-report", locale, "report.checkstyle." + key);
115 }
116
117 protected void renderBody() {
118 startSection(getTitle());
119
120 sink.paragraph();
121 sink.text(getI18nString("checkstylelink") + " ");
122 sink.link("https://checkstyle.org/");
123 sink.text("Checkstyle");
124 sink.link_();
125 String version = getCheckstyleVersion();
126 if (version != null) {
127 sink.text(" ");
128 sink.text(version);
129 }
130 sink.text(" ");
131 sink.text(String.format(getI18nString("ruleset"), ruleset));
132 sink.text(".");
133 sink.paragraph_();
134
135 renderSeveritySummarySection();
136
137 renderFilesSummarySection();
138
139 renderRulesSummarySection();
140
141 renderDetailsSection();
142
143 endSection();
144 }
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159 private String getConfigAttribute(
160 Configuration config,
161 ChainedItem<Configuration> parentConfiguration,
162 String attributeName,
163 String defaultValue) {
164 String ret;
165 try {
166 ret = config.getAttribute(attributeName);
167 } catch (CheckstyleException e) {
168
169 if (parentConfiguration != null) {
170 ret = getConfigAttribute(
171 parentConfiguration.value, parentConfiguration.parent, attributeName, defaultValue);
172 } else {
173 ret = defaultValue;
174 }
175 }
176 return ret;
177 }
178
179
180
181
182
183
184 private void renderRulesSummarySection() {
185 if (!enableRulesSummary) {
186 return;
187 }
188 if (checkstyleConfig == null) {
189 return;
190 }
191
192 startSection(getI18nString("rules"));
193
194 startTable();
195
196 tableHeader(new String[] {
197 getI18nString("rule.category"),
198 getI18nString("rule"),
199 getI18nString("violations"),
200 getI18nString("column.severity")
201 });
202
203
204 if ("checker".equalsIgnoreCase(checkstyleConfig.getName())) {
205 String category = null;
206 for (ConfReference ref : sortConfiguration(results)) {
207 renderRuleRow(ref, results, category);
208
209 category = ref.category;
210 }
211 } else {
212 tableRow(new String[] {getI18nString("norule")});
213 }
214
215 endTable();
216
217 endSection();
218 }
219
220
221
222
223
224
225
226
227 private void renderRuleRow(ConfReference ref, CheckstyleResults results, String previousCategory) {
228 Configuration checkerConfig = ref.configuration;
229 ChainedItem<Configuration> parentConfiguration = ref.parentConfiguration;
230 String ruleName = checkerConfig.getName();
231
232 sink.tableRow();
233
234
235 sink.tableCell();
236 String category = ref.category;
237 if (!category.equals(previousCategory)) {
238 sink.text(category);
239 }
240 sink.tableCell_();
241
242
243 sink.tableCell();
244 if (!"extension".equals(category)) {
245 sink.link("https://checkstyle.org/config_" + category + ".html#" + ruleName);
246 sink.text(ruleName);
247 sink.link_();
248 } else {
249 sink.text(ruleName);
250 }
251
252 List<String> attribnames = new ArrayList<>(Arrays.asList(checkerConfig.getAttributeNames()));
253 attribnames.remove("severity");
254 if (!attribnames.isEmpty()) {
255 sink.list();
256 for (String name : attribnames) {
257 sink.listItem();
258
259 sink.text(name);
260
261 String value = getConfigAttribute(checkerConfig, null, name, "");
262
263 if ("header".equals(name) && ("Header".equals(ruleName) || "RegexpHeader".equals(ruleName))) {
264 String[] lines = StringUtils.split(value, "\\n");
265 int linenum = 1;
266 for (String line : lines) {
267 sink.lineBreak();
268 sink.rawText("<span style=\"color: gray\">");
269 sink.text(linenum + ":");
270 sink.rawText("</span>");
271 sink.nonBreakingSpace();
272 sink.monospaced();
273 sink.text(line);
274 sink.monospaced_();
275 linenum++;
276 }
277 } else if ("headerFile".equals(name) && "RegexpHeader".equals(ruleName)) {
278 sink.text(": ");
279 sink.monospaced();
280 sink.text("\"");
281
282 String path =
283 siteTool.getRelativePath(value, project.getBasedir().getAbsolutePath());
284 sink.text(path.replace('\\', '/'));
285 sink.text(value);
286 sink.text("\"");
287 sink.monospaced_();
288 } else {
289 sink.text(": ");
290 sink.monospaced();
291 sink.text("\"");
292 sink.text(value);
293 sink.text("\"");
294 sink.monospaced_();
295 }
296 sink.listItem_();
297 }
298 sink.list_();
299 }
300
301 sink.tableCell_();
302
303
304 sink.tableCell();
305 sink.text(String.valueOf(ref.violations));
306 sink.tableCell_();
307
308
309 sink.tableCell();
310
311
312 String severity = getConfigAttribute(checkerConfig, parentConfiguration, "severity", "error");
313 iconSeverity(severity, TEXT_SIMPLE);
314 sink.tableCell_();
315
316 sink.tableRow_();
317 }
318
319
320
321
322
323
324
325
326
327
328 public boolean matchRule(AuditEvent event, String ruleName, String expectedMessage, String expectedSeverity) {
329 if (!ruleName.equals(RuleUtil.getName(event))) {
330 return false;
331 }
332
333
334
335 if (expectedMessage != null) {
336
337
338
339 String msgWithoutSingleQuote = StringUtils.replace(expectedMessage, "'", "");
340
341 if (!(expectedMessage.equals(event.getMessage()) || msgWithoutSingleQuote.equals(event.getMessage()))) {
342 return false;
343 }
344 }
345
346
347
348
349 if (expectedSeverity != null) {
350 if (!expectedSeverity.equals(event.getSeverityLevel().getName())) {
351 return false;
352 }
353 }
354 return true;
355 }
356
357 private void renderSeveritySummarySection() {
358 if (!enableSeveritySummary) {
359 return;
360 }
361
362 startSection(getI18nString("summary"));
363
364 startTable();
365
366 sink.tableRow();
367 sink.tableHeaderCell();
368 sink.text(getI18nString("files"));
369 sink.tableHeaderCell_();
370
371 sink.tableHeaderCell();
372 iconSeverity("info", TEXT_TITLE);
373 sink.tableHeaderCell_();
374
375 sink.tableHeaderCell();
376 iconSeverity("warning", TEXT_TITLE);
377 sink.tableHeaderCell_();
378
379 sink.tableHeaderCell();
380 iconSeverity("error", TEXT_TITLE);
381 sink.tableHeaderCell_();
382 sink.tableRow_();
383
384 tableRow(new String[] {
385 String.valueOf(results.getFileCount()),
386 String.valueOf(results.getSeverityCount(SeverityLevel.INFO)),
387 String.valueOf(results.getSeverityCount(SeverityLevel.WARNING)),
388 String.valueOf(results.getSeverityCount(SeverityLevel.ERROR))
389 });
390
391 endTable();
392
393 endSection();
394 }
395
396 private void renderFilesSummarySection() {
397 if (!enableFilesSummary) {
398 return;
399 }
400
401 startSection(getI18nString("files"));
402
403 startTable();
404
405 sink.tableRow();
406 sink.tableHeaderCell();
407 sink.text(getI18nString("file"));
408 sink.tableHeaderCell_();
409 sink.tableHeaderCell();
410 iconSeverity("info", TEXT_ABBREV);
411 sink.tableHeaderCell_();
412 sink.tableHeaderCell();
413 iconSeverity("warning", TEXT_ABBREV);
414 sink.tableHeaderCell_();
415 sink.tableHeaderCell();
416 iconSeverity("error", TEXT_ABBREV);
417 sink.tableHeaderCell_();
418 sink.tableRow_();
419
420
421 List<String> fileList = new ArrayList<>(results.getFiles().keySet());
422 Collections.sort(fileList);
423
424 for (String filename : fileList) {
425 List<AuditEvent> violations = results.getFileViolations(filename);
426 if (violations.isEmpty()) {
427
428 continue;
429 }
430
431 sink.tableRow();
432
433 sink.tableCell();
434 sink.link("#" + DoxiaUtils.encodeId(filename));
435 sink.text(filename);
436 sink.link_();
437 sink.tableCell_();
438
439 sink.tableCell();
440 sink.text(String.valueOf(results.getSeverityCount(violations, SeverityLevel.INFO)));
441 sink.tableCell_();
442
443 sink.tableCell();
444 sink.text(String.valueOf(results.getSeverityCount(violations, SeverityLevel.WARNING)));
445 sink.tableCell_();
446
447 sink.tableCell();
448 sink.text(String.valueOf(results.getSeverityCount(violations, SeverityLevel.ERROR)));
449 sink.tableCell_();
450
451 sink.tableRow_();
452 }
453
454 endTable();
455
456 endSection();
457 }
458
459 private void renderDetailsSection() {
460 startSection(getI18nString("details"));
461
462
463 List<String> fileList = new ArrayList<>(results.getFiles().keySet());
464 Collections.sort(fileList);
465
466 for (String file : fileList) {
467 List<AuditEvent> violations = results.getFileViolations(file);
468
469 if (violations.isEmpty()) {
470
471 continue;
472 }
473
474 startSection(file);
475
476 startTable();
477
478 tableHeader(new String[] {
479 getI18nString("column.severity"),
480 getI18nString("rule.category"),
481 getI18nString("rule"),
482 getI18nString("column.message"),
483 getI18nString("column.line")
484 });
485
486 renderFileEvents(violations, file);
487
488 endTable();
489
490 endSection();
491 }
492
493 endSection();
494 }
495
496 private void renderFileEvents(List<AuditEvent> eventList, String filename) {
497 for (AuditEvent event : eventList) {
498 SeverityLevel level = event.getSeverityLevel();
499
500 sink.tableRow();
501
502 sink.tableCell();
503 iconSeverity(level.getName(), TEXT_SIMPLE);
504 sink.tableCell_();
505
506 sink.tableCell();
507 String category = RuleUtil.getCategory(event);
508 if (category != null) {
509 sink.text(category);
510 }
511 sink.tableCell_();
512
513 sink.tableCell();
514 String ruleName = RuleUtil.getName(event);
515 if (ruleName != null) {
516 sink.text(ruleName);
517 }
518 sink.tableCell_();
519
520 sink.tableCell();
521 sink.text(event.getMessage());
522 sink.tableCell_();
523
524 sink.tableCell();
525
526 int line = event.getLine();
527 String effectiveXrefLocation = getEffectiveXrefLocation(eventList);
528 if (effectiveXrefLocation != null && line != 0) {
529 sink.link(effectiveXrefLocation + "/" + filename.replaceAll("\\.java$", ".html") + "#L" + line);
530 sink.text(String.valueOf(line));
531 sink.link_();
532 } else if (line != 0) {
533 sink.text(String.valueOf(line));
534 }
535 sink.tableCell_();
536
537 sink.tableRow_();
538 }
539 }
540
541 private String getEffectiveXrefLocation(List<AuditEvent> eventList) {
542 String absoluteFilename = eventList.get(0).getFileName();
543 if (isTestSource(absoluteFilename)) {
544 return getXrefTestLocation();
545 } else {
546 return getXrefLocation();
547 }
548 }
549
550 private boolean isTestSource(final String absoluteFilename) {
551 for (File testSourceDirectory : testSourceDirectories) {
552 if (absoluteFilename.startsWith(testSourceDirectory.getAbsolutePath())) {
553 return true;
554 }
555 }
556
557 return false;
558 }
559
560 public String getXrefLocation() {
561 return xrefLocation;
562 }
563
564 public void setXrefLocation(String xrefLocation) {
565 this.xrefLocation = xrefLocation;
566 }
567
568 public String getXrefTestLocation() {
569 return xrefTestLocation;
570 }
571
572 public void setXrefTestLocation(String xrefTestLocation) {
573 this.xrefTestLocation = xrefTestLocation;
574 }
575
576 public void setTestSourceDirectories(List<File> testSourceDirectories) {
577 this.testSourceDirectories = testSourceDirectories;
578 }
579
580 public void setTreeWalkerNames(List<String> treeWalkerNames) {
581 this.treeWalkerNames = treeWalkerNames;
582 }
583
584
585
586
587
588
589
590 private void iconSeverity(String level, int textType) {
591 sink.figureGraphics("images/icon_" + level + "_sml.gif");
592
593 if (textType > NO_TEXT) {
594 sink.nonBreakingSpace();
595 String suffix;
596 switch (textType) {
597 case TEXT_TITLE:
598 suffix = "s";
599 break;
600 case TEXT_ABBREV:
601 suffix = "s.abbrev";
602 break;
603 default:
604 suffix = "";
605 }
606 sink.text(getI18nString(level + suffix));
607 }
608 }
609
610
611
612
613
614 private String getCheckstyleVersion() {
615 Package checkstyleApiPackage = Configuration.class.getPackage();
616
617 return (checkstyleApiPackage == null) ? null : checkstyleApiPackage.getImplementationVersion();
618 }
619
620 public List<ConfReference> sortConfiguration(CheckstyleResults results) {
621 List<ConfReference> result = new ArrayList<>();
622
623 sortConfiguration(result, checkstyleConfig, null, results);
624
625 Collections.sort(result);
626
627 return result;
628 }
629
630 private void sortConfiguration(
631 List<ConfReference> result,
632 Configuration config,
633 ChainedItem<Configuration> parent,
634 CheckstyleResults results) {
635 for (Configuration childConfig : config.getChildren()) {
636 String ruleName = childConfig.getName();
637
638 if (treeWalkerNames.contains(ruleName)) {
639
640 sortConfiguration(result, childConfig, new ChainedItem<>(config, parent), results);
641 } else {
642 String fixedmessage = getConfigAttribute(childConfig, null, "message", null);
643
644
645
646 String configSeverity = getConfigAttribute(childConfig, null, "severity", null);
647
648
649 long violations = 0;
650 AuditEvent lastMatchedEvent = null;
651 for (List<AuditEvent> errors : results.getFiles().values()) {
652 for (AuditEvent event : errors) {
653 if (matchRule(event, ruleName, fixedmessage, configSeverity)) {
654 lastMatchedEvent = event;
655 violations++;
656 }
657 }
658 }
659
660 if (violations > 0)
661 {
662 String category = RuleUtil.getCategory(lastMatchedEvent);
663
664 result.add(new ConfReference(category, childConfig, parent, violations, result.size()));
665 }
666 }
667 }
668 }
669
670 private static class ConfReference implements Comparable<ConfReference> {
671 private final String category;
672 private final Configuration configuration;
673 private final ChainedItem<Configuration> parentConfiguration;
674 private final long violations;
675 private final int count;
676
677 ConfReference(
678 String category,
679 Configuration configuration,
680 ChainedItem<Configuration> parentConfiguration,
681 long violations,
682 int count) {
683 this.category = category;
684 this.configuration = configuration;
685 this.parentConfiguration = parentConfiguration;
686 this.violations = violations;
687 this.count = count;
688 }
689
690 public int compareTo(ConfReference o) {
691 int compare = category.compareTo(o.category);
692 if (compare == 0) {
693 compare = configuration.getName().compareTo(o.configuration.getName());
694 }
695 return (compare == 0) ? (o.count - count) : compare;
696 }
697 }
698
699 private static class ChainedItem<T> {
700 private final ChainedItem<T> parent;
701
702 private final T value;
703
704 ChainedItem(T value, ChainedItem<T> parent) {
705 this.parent = parent;
706 this.value = value;
707 }
708 }
709 }