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 }