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