1 package org.apache.maven.plugins.checkstyle;
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.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.List;
27 import java.util.ResourceBundle;
28
29 import org.apache.maven.doxia.sink.Sink;
30 import org.apache.maven.doxia.sink.SinkEventAttributes;
31 import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
32 import org.apache.maven.doxia.tools.SiteTool;
33 import org.apache.maven.plugin.logging.Log;
34 import org.apache.maven.plugin.logging.SystemStreamLog;
35 import org.apache.maven.plugins.checkstyle.exec.CheckstyleResults;
36 import org.codehaus.plexus.util.StringUtils;
37
38 import com.puppycrawl.tools.checkstyle.api.AuditEvent;
39 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
40 import com.puppycrawl.tools.checkstyle.api.Configuration;
41 import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
42
43
44
45
46
47
48 public class CheckstyleReportGenerator
49 {
50 private Log log;
51
52 private final File basedir;
53
54 private final ResourceBundle bundle;
55
56 private final Sink sink;
57
58 private SeverityLevel severityLevel;
59
60 private Configuration checkstyleConfig;
61
62 private boolean enableRulesSummary;
63
64 private boolean enableSeveritySummary;
65
66 private boolean enableFilesSummary;
67
68 @Deprecated
69 private boolean enableRSS;
70
71 private final SiteTool siteTool;
72
73 private String xrefLocation;
74
75 private String xrefTestLocation;
76
77 private List<File> testSourceDirectories = new ArrayList<>();
78
79 private List<String> treeWalkerNames = Collections.singletonList( "TreeWalker" );
80
81 private final IconTool iconTool;
82
83 private final String ruleset;
84
85 public CheckstyleReportGenerator( Sink sink, ResourceBundle bundle, File basedir, SiteTool siteTool,
86 String ruleset )
87 {
88 this.bundle = bundle;
89
90 this.sink = sink;
91
92 this.basedir = basedir;
93
94 this.siteTool = siteTool;
95
96 this.ruleset = ruleset;
97
98 this.enableRulesSummary = true;
99 this.enableSeveritySummary = true;
100 this.enableFilesSummary = true;
101 this.enableRSS = false;
102 this.iconTool = new IconTool( sink, bundle );
103 }
104
105 public Log getLog()
106 {
107 if ( this.log == null )
108 {
109 this.log = new SystemStreamLog();
110 }
111 return this.log;
112 }
113
114 public void setLog( Log log )
115 {
116 this.log = log;
117 }
118
119 private String getTitle()
120 {
121 String title;
122
123 if ( getSeverityLevel() == null )
124 {
125 title = bundle.getString( "report.checkstyle.title" );
126 }
127 else
128 {
129 title = bundle.getString( "report.checkstyle.severity_title" ) + severityLevel.getName();
130 }
131
132 return title;
133 }
134
135 public void generateReport( CheckstyleResults results )
136 {
137 doHeading();
138
139 if ( getSeverityLevel() == null )
140 {
141 if ( enableSeveritySummary )
142 {
143 doSeveritySummary( results );
144 }
145
146 if ( enableFilesSummary )
147 {
148 doFilesSummary( results );
149 }
150
151 if ( enableRulesSummary )
152 {
153 doRulesSummary( results );
154 }
155 }
156
157 doDetails( results );
158 sink.body_();
159 sink.flush();
160 sink.close();
161 }
162
163 private void doHeading()
164 {
165 sink.head();
166 sink.title();
167 sink.text( getTitle() );
168 sink.title_();
169 sink.head_();
170
171 sink.body();
172
173 sink.section1();
174 sink.sectionTitle1();
175 sink.text( getTitle() );
176 sink.sectionTitle1_();
177
178 sink.paragraph();
179 sink.text( bundle.getString( "report.checkstyle.checkstylelink" ) + " " );
180 sink.link( "https://checkstyle.org/" );
181 sink.text( "Checkstyle" );
182 sink.link_();
183 String version = getCheckstyleVersion();
184 if ( version != null )
185 {
186 sink.text( " " );
187 sink.text( version );
188 }
189 sink.text( " " );
190 sink.text( String.format( bundle.getString( "report.checkstyle.ruleset" ), ruleset ) );
191 sink.text( "." );
192
193 if ( enableRSS )
194 {
195 sink.nonBreakingSpace();
196 sink.link( "checkstyle.rss" );
197 sink.figure();
198 sink.figureCaption();
199 sink.text( "rss feed" );
200 sink.figureCaption_();
201 sink.figureGraphics( "images/rss.png" );
202 sink.figure_();
203 sink.link_();
204 }
205
206 sink.paragraph_();
207 sink.section1_();
208 }
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223 private String getConfigAttribute( Configuration config, ChainedItem<Configuration> parentConfiguration,
224 String attributeName, String defaultValue )
225 {
226 String ret;
227 try
228 {
229 ret = config.getAttribute( attributeName );
230 }
231 catch ( CheckstyleException e )
232 {
233
234 if ( parentConfiguration != null )
235 {
236 ret =
237 getConfigAttribute( parentConfiguration.value, parentConfiguration.parent, attributeName,
238 defaultValue );
239 }
240 else
241 {
242 ret = defaultValue;
243 }
244 }
245 return ret;
246 }
247
248
249
250
251
252
253 private void doRulesSummary( CheckstyleResults results )
254 {
255 if ( checkstyleConfig == null )
256 {
257 return;
258 }
259
260 sink.section1();
261 sink.sectionTitle1();
262 sink.text( bundle.getString( "report.checkstyle.rules" ) );
263 sink.sectionTitle1_();
264
265 sink.table();
266
267 sink.tableRow();
268 sink.tableHeaderCell();
269 sink.text( bundle.getString( "report.checkstyle.rule.category" ) );
270 sink.tableHeaderCell_();
271
272 sink.tableHeaderCell();
273 sink.text( bundle.getString( "report.checkstyle.rule" ) );
274 sink.tableHeaderCell_();
275
276 sink.tableHeaderCell();
277 sink.text( bundle.getString( "report.checkstyle.violations" ) );
278 sink.tableHeaderCell_();
279
280 sink.tableHeaderCell();
281 sink.text( bundle.getString( "report.checkstyle.column.severity" ) );
282 sink.tableHeaderCell_();
283
284 sink.tableRow_();
285
286
287 if ( "checker".equalsIgnoreCase( checkstyleConfig.getName() ) )
288 {
289 String category = null;
290 for ( ConfReference ref: sortConfiguration( results ) )
291 {
292 doRuleRow( ref, results, category );
293
294 category = ref.category;
295 }
296 }
297 else
298 {
299 sink.tableRow();
300 sink.tableCell();
301 sink.text( bundle.getString( "report.checkstyle.norule" ) );
302 sink.tableCell_();
303 sink.tableRow_();
304 }
305
306 sink.table_();
307
308 sink.section1_();
309 }
310
311
312
313
314
315
316
317
318 private void doRuleRow( ConfReference ref, CheckstyleResults results, String previousCategory )
319 {
320 Configuration checkerConfig = ref.configuration;
321 ChainedItem<Configuration> parentConfiguration = ref.parentConfiguration;
322 String ruleName = checkerConfig.getName();
323
324 sink.tableRow();
325
326
327 sink.tableCell();
328 String category = ref.category;
329 if ( !category.equals( previousCategory ) )
330 {
331 sink.text( category );
332 }
333 sink.tableCell_();
334
335
336 sink.tableCell();
337 if ( !"extension".equals( category ) )
338 {
339 sink.link( "https://checkstyle.org/config_" + category + ".html#" + ruleName );
340 sink.text( ruleName );
341 sink.link_();
342 }
343 else
344 {
345 sink.text( ruleName );
346 }
347
348 List<String> attribnames = new ArrayList<>( Arrays.asList( checkerConfig.getAttributeNames() ) );
349 attribnames.remove( "severity" );
350 if ( !attribnames.isEmpty() )
351 {
352 sink.list();
353 for ( String name : attribnames )
354 {
355 sink.listItem();
356
357 sink.text( name );
358
359 String value = getConfigAttribute( checkerConfig, null, name, "" );
360
361 if ( "header".equals( name ) && ( "Header".equals( ruleName ) || "RegexpHeader".equals( ruleName ) ) )
362 {
363 String[] lines = StringUtils.split( value, "\\n" );
364 int linenum = 1;
365 for ( String line : lines )
366 {
367 sink.lineBreak();
368 sink.rawText( "<span style=\"color: gray\">" );
369 sink.text( linenum + ":" );
370 sink.rawText( "</span>" );
371 sink.nonBreakingSpace();
372 sink.monospaced();
373 sink.text( line );
374 sink.monospaced_();
375 linenum++;
376 }
377 }
378 else if ( "headerFile".equals( name ) && "RegexpHeader".equals( ruleName ) )
379 {
380 sink.text( ": " );
381 sink.monospaced();
382 sink.text( "\"" );
383 if ( basedir != null )
384 {
385
386 String path = siteTool.getRelativePath( value, basedir.getAbsolutePath() );
387 sink.text( path.replace( '\\', '/' ) );
388 }
389 else
390 {
391 sink.text( value );
392 }
393 sink.text( "\"" );
394 sink.monospaced_();
395 }
396 else
397 {
398 sink.text( ": " );
399 sink.monospaced();
400 sink.text( "\"" );
401 sink.text( value );
402 sink.text( "\"" );
403 sink.monospaced_();
404 }
405 sink.listItem_();
406 }
407 sink.list_();
408 }
409
410 sink.tableCell_();
411
412
413 sink.tableCell();
414 sink.text( String.valueOf( ref.violations ) );
415 sink.tableCell_();
416
417
418 sink.tableCell();
419
420
421 String severity = getConfigAttribute( checkerConfig, parentConfiguration, "severity", "error" );
422 iconTool.iconSeverity( severity, IconTool.TEXT_SIMPLE );
423 sink.tableCell_();
424
425 sink.tableRow_();
426 }
427
428
429
430
431
432
433
434
435
436
437 public boolean matchRule( AuditEvent event, String ruleName, String expectedMessage, String expectedSeverity )
438 {
439 if ( !ruleName.equals( RuleUtil.getName( event ) ) )
440 {
441 return false;
442 }
443
444
445
446 if ( expectedMessage != null )
447 {
448
449
450
451 String msgWithoutSingleQuote = StringUtils.replace( expectedMessage, "'", "" );
452
453 if ( ! ( expectedMessage.equals( event.getMessage() )
454 || msgWithoutSingleQuote.equals( event.getMessage() ) ) )
455 {
456 return false;
457 }
458 }
459
460
461
462
463 if ( expectedSeverity != null )
464 {
465 if ( !expectedSeverity.equals( event.getSeverityLevel().getName() ) )
466 {
467 return false;
468 }
469 }
470 return true;
471 }
472
473 private void doSeveritySummary( CheckstyleResults results )
474 {
475 sink.section1();
476 sink.sectionTitle1();
477 sink.text( bundle.getString( "report.checkstyle.summary" ) );
478 sink.sectionTitle1_();
479
480 sink.table();
481
482 sink.tableRow();
483 sink.tableHeaderCell();
484 sink.text( bundle.getString( "report.checkstyle.files" ) );
485 sink.tableHeaderCell_();
486
487 sink.tableHeaderCell();
488 iconTool.iconInfo( IconTool.TEXT_TITLE );
489 sink.tableHeaderCell_();
490
491 sink.tableHeaderCell();
492 iconTool.iconWarning( IconTool.TEXT_TITLE );
493 sink.tableHeaderCell_();
494
495 sink.tableHeaderCell();
496 iconTool.iconError( IconTool.TEXT_TITLE );
497 sink.tableHeaderCell_();
498 sink.tableRow_();
499
500 sink.tableRow();
501 sink.tableCell();
502 sink.text( String.valueOf( results.getFileCount() ) );
503 sink.tableCell_();
504 sink.tableCell();
505 sink.text( String.valueOf( results.getSeverityCount( SeverityLevel.INFO ) ) );
506 sink.tableCell_();
507 sink.tableCell();
508 sink.text( String.valueOf( results.getSeverityCount( SeverityLevel.WARNING ) ) );
509 sink.tableCell_();
510 sink.tableCell();
511 sink.text( String.valueOf( results.getSeverityCount( SeverityLevel.ERROR ) ) );
512 sink.tableCell_();
513 sink.tableRow_();
514
515 sink.table_();
516
517 sink.section1_();
518 }
519
520 private void doFilesSummary( CheckstyleResults results )
521 {
522 sink.section1();
523 sink.sectionTitle1();
524 sink.text( bundle.getString( "report.checkstyle.files" ) );
525 sink.sectionTitle1_();
526
527 sink.table();
528
529 sink.tableRow();
530 sink.tableHeaderCell();
531 sink.text( bundle.getString( "report.checkstyle.file" ) );
532 sink.tableHeaderCell_();
533 sink.tableHeaderCell();
534 iconTool.iconInfo( IconTool.TEXT_ABBREV );
535 sink.tableHeaderCell_();
536 sink.tableHeaderCell();
537 iconTool.iconWarning( IconTool.TEXT_ABBREV );
538 sink.tableHeaderCell_();
539 sink.tableHeaderCell();
540 iconTool.iconError( IconTool.TEXT_ABBREV );
541 sink.tableHeaderCell_();
542 sink.tableRow_();
543
544
545 List<String> fileList = new ArrayList<>( results.getFiles().keySet() );
546 Collections.sort( fileList );
547
548 for ( String filename : fileList )
549 {
550 List<AuditEvent> violations = results.getFileViolations( filename );
551 if ( violations.isEmpty() )
552 {
553
554 continue;
555 }
556
557 sink.tableRow();
558
559 sink.tableCell();
560 sink.link( "#" + filename.replace( '/', '.' ) );
561 sink.text( filename );
562 sink.link_();
563 sink.tableCell_();
564
565 sink.tableCell();
566 sink.text( String.valueOf( results.getSeverityCount( violations, SeverityLevel.INFO ) ) );
567 sink.tableCell_();
568
569 sink.tableCell();
570 sink.text( String.valueOf( results.getSeverityCount( violations, SeverityLevel.WARNING ) ) );
571 sink.tableCell_();
572
573 sink.tableCell();
574 sink.text( String.valueOf( results.getSeverityCount( violations, SeverityLevel.ERROR ) ) );
575 sink.tableCell_();
576
577 sink.tableRow_();
578 }
579
580 sink.table_();
581 sink.section1_();
582 }
583
584 private void doDetails( CheckstyleResults results )
585 {
586
587 sink.section1();
588 sink.sectionTitle1();
589 sink.text( bundle.getString( "report.checkstyle.details" ) );
590 sink.sectionTitle1_();
591
592
593 List<String> fileList = new ArrayList<>( results.getFiles().keySet() );
594 Collections.sort( fileList );
595
596 for ( String file : fileList )
597 {
598 List<AuditEvent> violations = results.getFileViolations( file );
599
600 if ( violations.isEmpty() )
601 {
602
603 continue;
604 }
605
606 sink.section2();
607 SinkEventAttributes attrs = new SinkEventAttributeSet();
608 attrs.addAttribute( SinkEventAttributes.ID, file.replace( '/', '.' ) );
609 sink.sectionTitle( Sink.SECTION_LEVEL_2, attrs );
610 sink.text( file );
611 sink.sectionTitle_( Sink.SECTION_LEVEL_2 );
612
613 sink.table();
614 sink.tableRow();
615 sink.tableHeaderCell();
616 sink.text( bundle.getString( "report.checkstyle.column.severity" ) );
617 sink.tableHeaderCell_();
618 sink.tableHeaderCell();
619 sink.text( bundle.getString( "report.checkstyle.rule.category" ) );
620 sink.tableHeaderCell_();
621 sink.tableHeaderCell();
622 sink.text( bundle.getString( "report.checkstyle.rule" ) );
623 sink.tableHeaderCell_();
624 sink.tableHeaderCell();
625 sink.text( bundle.getString( "report.checkstyle.column.message" ) );
626 sink.tableHeaderCell_();
627 sink.tableHeaderCell();
628 sink.text( bundle.getString( "report.checkstyle.column.line" ) );
629 sink.tableHeaderCell_();
630 sink.tableRow_();
631
632 doFileEvents( violations, file );
633
634 sink.table_();
635 sink.section2_();
636 }
637
638 sink.section1_();
639 }
640
641 private void doFileEvents( List<AuditEvent> eventList, String filename )
642 {
643 for ( AuditEvent event : eventList )
644 {
645 SeverityLevel level = event.getSeverityLevel();
646
647 if ( ( getSeverityLevel() != null ) && !( getSeverityLevel() != level ) )
648 {
649 continue;
650 }
651
652 sink.tableRow();
653
654 sink.tableCell();
655 iconTool.iconSeverity( level.getName(), IconTool.TEXT_SIMPLE );
656 sink.tableCell_();
657
658 sink.tableCell();
659 String category = RuleUtil.getCategory( event );
660 if ( category != null )
661 {
662 sink.text( category );
663 }
664 sink.tableCell_();
665
666 sink.tableCell();
667 String ruleName = RuleUtil.getName( event );
668 if ( ruleName != null )
669 {
670 sink.text( ruleName );
671 }
672 sink.tableCell_();
673
674 sink.tableCell();
675 sink.text( event.getMessage() );
676 sink.tableCell_();
677
678 sink.tableCell();
679
680 int line = event.getLine();
681 String effectiveXrefLocation = getEffectiveXrefLocation( eventList );
682 if ( effectiveXrefLocation != null && line != 0 )
683 {
684 sink.link( effectiveXrefLocation + "/" + filename.replaceAll( "\\.java$", ".html" ) + "#L"
685 + line );
686 sink.text( String.valueOf( line ) );
687 sink.link_();
688 }
689 else if ( line != 0 )
690 {
691 sink.text( String.valueOf( line ) );
692 }
693 sink.tableCell_();
694
695 sink.tableRow_();
696 }
697 }
698
699 private String getEffectiveXrefLocation( List<AuditEvent> eventList )
700 {
701 String absoluteFilename = eventList.get( 0 ).getFileName();
702 if ( isTestSource( absoluteFilename ) )
703 {
704 return getXrefTestLocation();
705 }
706 else
707 {
708 return getXrefLocation();
709 }
710 }
711
712 private boolean isTestSource( final String absoluteFilename )
713 {
714 for ( File testSourceDirectory : testSourceDirectories )
715 {
716 if ( absoluteFilename.startsWith( testSourceDirectory.getAbsolutePath() ) )
717 {
718 return true;
719 }
720 }
721
722 return false;
723 }
724
725 public SeverityLevel getSeverityLevel()
726 {
727 return severityLevel;
728 }
729
730 public void setSeverityLevel( SeverityLevel severityLevel )
731 {
732 this.severityLevel = severityLevel;
733 }
734
735 public boolean isEnableRulesSummary()
736 {
737 return enableRulesSummary;
738 }
739
740 public void setEnableRulesSummary( boolean enableRulesSummary )
741 {
742 this.enableRulesSummary = enableRulesSummary;
743 }
744
745 public boolean isEnableSeveritySummary()
746 {
747 return enableSeveritySummary;
748 }
749
750 public void setEnableSeveritySummary( boolean enableSeveritySummary )
751 {
752 this.enableSeveritySummary = enableSeveritySummary;
753 }
754
755 public boolean isEnableFilesSummary()
756 {
757 return enableFilesSummary;
758 }
759
760 public void setEnableFilesSummary( boolean enableFilesSummary )
761 {
762 this.enableFilesSummary = enableFilesSummary;
763 }
764
765 @Deprecated
766 public boolean isEnableRSS()
767 {
768 return enableRSS;
769 }
770
771 @Deprecated
772 public void setEnableRSS( boolean enableRSS )
773 {
774 this.enableRSS = enableRSS;
775 }
776
777 public String getXrefLocation()
778 {
779 return xrefLocation;
780 }
781
782 public void setXrefLocation( String xrefLocation )
783 {
784 this.xrefLocation = xrefLocation;
785 }
786
787 public String getXrefTestLocation()
788 {
789 return xrefTestLocation;
790 }
791
792 public void setXrefTestLocation( String xrefTestLocation )
793 {
794 this.xrefTestLocation = xrefTestLocation;
795 }
796
797 public List<File> getTestSourceDirectories()
798 {
799 return testSourceDirectories;
800 }
801
802 public void setTestSourceDirectories( List<File> testSourceDirectories )
803 {
804 this.testSourceDirectories = testSourceDirectories;
805 }
806
807 public Configuration getCheckstyleConfig()
808 {
809 return checkstyleConfig;
810 }
811
812 public void setCheckstyleConfig( Configuration config )
813 {
814 this.checkstyleConfig = config;
815 }
816
817 public void setTreeWalkerNames( List<String> treeWalkerNames )
818 {
819 this.treeWalkerNames = treeWalkerNames;
820 }
821
822 public List<String> getTreeWalkerNames()
823 {
824 return treeWalkerNames;
825 }
826
827
828
829
830
831 private String getCheckstyleVersion()
832 {
833 Package checkstyleApiPackage = Configuration.class.getPackage();
834
835 return ( checkstyleApiPackage == null ) ? null : checkstyleApiPackage.getImplementationVersion();
836 }
837
838 public List<ConfReference> sortConfiguration( CheckstyleResults results )
839 {
840 List<ConfReference> result = new ArrayList<>();
841
842 sortConfiguration( result, checkstyleConfig, null, results );
843
844 Collections.sort( result );
845
846 return result;
847 }
848
849 private void sortConfiguration( List<ConfReference> result, Configuration config,
850 ChainedItem<Configuration> parent, CheckstyleResults results )
851 {
852 for ( Configuration childConfig : config.getChildren() )
853 {
854 String ruleName = childConfig.getName();
855
856 if ( treeWalkerNames.contains( ruleName ) )
857 {
858
859 sortConfiguration( result, childConfig, new ChainedItem<>( config, parent ), results );
860 }
861 else
862 {
863 String fixedmessage = getConfigAttribute( childConfig, null, "message", null );
864
865
866
867 String configSeverity = getConfigAttribute( childConfig, null, "severity", null );
868
869
870 long violations = 0;
871 AuditEvent lastMatchedEvent = null;
872 for ( List<AuditEvent> errors : results.getFiles().values() )
873 {
874 for ( AuditEvent event : errors )
875 {
876 if ( matchRule( event, ruleName, fixedmessage, configSeverity ) )
877 {
878 lastMatchedEvent = event;
879 violations++;
880 }
881 }
882 }
883
884 if ( violations > 0 )
885 {
886 String category = RuleUtil.getCategory( lastMatchedEvent );
887
888 result.add( new ConfReference( category, childConfig, parent, violations, result.size() ) );
889 }
890 }
891 }
892 }
893
894 private static class ConfReference
895 implements Comparable<ConfReference>
896 {
897 private final String category;
898 private final Configuration configuration;
899 private final ChainedItem<Configuration> parentConfiguration;
900 private final long violations;
901 private final int count;
902
903 ConfReference( String category, Configuration configuration,
904 ChainedItem<Configuration> parentConfiguration, long violations, int count )
905 {
906 this.category = category;
907 this.configuration = configuration;
908 this.parentConfiguration = parentConfiguration;
909 this.violations = violations;
910 this.count = count;
911 }
912
913 public int compareTo( ConfReference o )
914 {
915 int compare = category.compareTo( o.category );
916 if ( compare == 0 )
917 {
918 compare = configuration.getName().compareTo( o.configuration.getName() );
919 }
920 return ( compare == 0 ) ? ( o.count - count ) : compare;
921 }
922 }
923
924 private static class ChainedItem<T>
925 {
926 private final ChainedItem<T> parent;
927
928 private final T value;
929
930 ChainedItem( T value, ChainedItem<T> parent )
931 {
932 this.parent = parent;
933 this.value = value;
934 }
935 }
936
937 }