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.SinkEventAttributeSet;
31 import org.apache.maven.doxia.sink.SinkEventAttributes;
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 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 if ( ! ( expectedMessage.equals( event.getMessage() )
449 || msgWithoutSingleQuote.equals( event.getMessage() ) ) )
450 {
451 return false;
452 }
453 }
454
455
456
457
458 if ( expectedSeverity != null )
459 {
460 if ( !expectedSeverity.equals( event.getSeverityLevel().getName() ) )
461 {
462 return false;
463 }
464 }
465 return true;
466 }
467
468 private void doSeveritySummary( CheckstyleResults results )
469 {
470 sink.section1();
471 sink.sectionTitle1();
472 sink.text( bundle.getString( "report.checkstyle.summary" ) );
473 sink.sectionTitle1_();
474
475 sink.table();
476
477 sink.tableRow();
478 sink.tableHeaderCell();
479 sink.text( bundle.getString( "report.checkstyle.files" ) );
480 sink.tableHeaderCell_();
481
482 sink.tableHeaderCell();
483 iconTool.iconInfo( IconTool.TEXT_TITLE );
484 sink.tableHeaderCell_();
485
486 sink.tableHeaderCell();
487 iconTool.iconWarning( IconTool.TEXT_TITLE );
488 sink.tableHeaderCell_();
489
490 sink.tableHeaderCell();
491 iconTool.iconError( IconTool.TEXT_TITLE );
492 sink.tableHeaderCell_();
493 sink.tableRow_();
494
495 sink.tableRow();
496 sink.tableCell();
497 sink.text( String.valueOf( results.getFileCount() ) );
498 sink.tableCell_();
499 sink.tableCell();
500 sink.text( String.valueOf( results.getSeverityCount( SeverityLevel.INFO ) ) );
501 sink.tableCell_();
502 sink.tableCell();
503 sink.text( String.valueOf( results.getSeverityCount( SeverityLevel.WARNING ) ) );
504 sink.tableCell_();
505 sink.tableCell();
506 sink.text( String.valueOf( results.getSeverityCount( SeverityLevel.ERROR ) ) );
507 sink.tableCell_();
508 sink.tableRow_();
509
510 sink.table_();
511
512 sink.section1_();
513 }
514
515 private void doFilesSummary( CheckstyleResults results )
516 {
517 sink.section1();
518 sink.sectionTitle1();
519 sink.text( bundle.getString( "report.checkstyle.files" ) );
520 sink.sectionTitle1_();
521
522 sink.table();
523
524 sink.tableRow();
525 sink.tableHeaderCell();
526 sink.text( bundle.getString( "report.checkstyle.file" ) );
527 sink.tableHeaderCell_();
528 sink.tableHeaderCell();
529 iconTool.iconInfo( IconTool.TEXT_ABBREV );
530 sink.tableHeaderCell_();
531 sink.tableHeaderCell();
532 iconTool.iconWarning( IconTool.TEXT_ABBREV );
533 sink.tableHeaderCell_();
534 sink.tableHeaderCell();
535 iconTool.iconError( IconTool.TEXT_ABBREV );
536 sink.tableHeaderCell_();
537 sink.tableRow_();
538
539
540 List<String> fileList = new ArrayList<>( results.getFiles().keySet() );
541 Collections.sort( fileList );
542
543 for ( String filename : fileList )
544 {
545 List<AuditEvent> violations = results.getFileViolations( filename );
546 if ( violations.isEmpty() )
547 {
548
549 continue;
550 }
551
552 sink.tableRow();
553
554 sink.tableCell();
555 sink.link( "#" + filename.replace( '/', '.' ) );
556 sink.text( filename );
557 sink.link_();
558 sink.tableCell_();
559
560 sink.tableCell();
561 sink.text( String.valueOf( results.getSeverityCount( violations, SeverityLevel.INFO ) ) );
562 sink.tableCell_();
563
564 sink.tableCell();
565 sink.text( String.valueOf( results.getSeverityCount( violations, SeverityLevel.WARNING ) ) );
566 sink.tableCell_();
567
568 sink.tableCell();
569 sink.text( String.valueOf( results.getSeverityCount( violations, SeverityLevel.ERROR ) ) );
570 sink.tableCell_();
571
572 sink.tableRow_();
573 }
574
575 sink.table_();
576 sink.section1_();
577 }
578
579 private void doDetails( CheckstyleResults results )
580 {
581
582 sink.section1();
583 sink.sectionTitle1();
584 sink.text( bundle.getString( "report.checkstyle.details" ) );
585 sink.sectionTitle1_();
586
587
588 List<String> fileList = new ArrayList<>( results.getFiles().keySet() );
589 Collections.sort( fileList );
590
591 for ( String file : fileList )
592 {
593 List<AuditEvent> violations = results.getFileViolations( file );
594
595 if ( violations.isEmpty() )
596 {
597
598 continue;
599 }
600
601 sink.section2();
602 SinkEventAttributes attrs = new SinkEventAttributeSet();
603 attrs.addAttribute( SinkEventAttributes.ID, file.replace( '/', '.' ) );
604 sink.sectionTitle( Sink.SECTION_LEVEL_2, attrs );
605 sink.text( file );
606 sink.sectionTitle_( Sink.SECTION_LEVEL_2 );
607
608 sink.table();
609 sink.tableRow();
610 sink.tableHeaderCell();
611 sink.text( bundle.getString( "report.checkstyle.column.severity" ) );
612 sink.tableHeaderCell_();
613 sink.tableHeaderCell();
614 sink.text( bundle.getString( "report.checkstyle.rule.category" ) );
615 sink.tableHeaderCell_();
616 sink.tableHeaderCell();
617 sink.text( bundle.getString( "report.checkstyle.rule" ) );
618 sink.tableHeaderCell_();
619 sink.tableHeaderCell();
620 sink.text( bundle.getString( "report.checkstyle.column.message" ) );
621 sink.tableHeaderCell_();
622 sink.tableHeaderCell();
623 sink.text( bundle.getString( "report.checkstyle.column.line" ) );
624 sink.tableHeaderCell_();
625 sink.tableRow_();
626
627 doFileEvents( violations, file );
628
629 sink.table_();
630 sink.section2_();
631 }
632
633 sink.section1_();
634 }
635
636 private void doFileEvents( List<AuditEvent> eventList, String filename )
637 {
638 for ( AuditEvent event : eventList )
639 {
640 SeverityLevel level = event.getSeverityLevel();
641
642 if ( ( getSeverityLevel() != null ) && !( getSeverityLevel() != level ) )
643 {
644 continue;
645 }
646
647 sink.tableRow();
648
649 sink.tableCell();
650 iconTool.iconSeverity( level.getName(), IconTool.TEXT_SIMPLE );
651 sink.tableCell_();
652
653 sink.tableCell();
654 String category = RuleUtil.getCategory( event );
655 if ( category != null )
656 {
657 sink.text( category );
658 }
659 sink.tableCell_();
660
661 sink.tableCell();
662 String ruleName = RuleUtil.getName( event );
663 if ( ruleName != null )
664 {
665 sink.text( ruleName );
666 }
667 sink.tableCell_();
668
669 sink.tableCell();
670 sink.text( event.getMessage() );
671 sink.tableCell_();
672
673 sink.tableCell();
674
675 int line = event.getLine();
676 if ( getXrefLocation() != null && line != 0 )
677 {
678 sink.link( getXrefLocation() + "/" + filename.replaceAll( "\\.java$", ".html" ) + "#L"
679 + line );
680 sink.text( String.valueOf( line ) );
681 sink.link_();
682 }
683 else if ( line != 0 )
684 {
685 sink.text( String.valueOf( line ) );
686 }
687 sink.tableCell_();
688
689 sink.tableRow_();
690 }
691 }
692
693 public SeverityLevel getSeverityLevel()
694 {
695 return severityLevel;
696 }
697
698 public void setSeverityLevel( SeverityLevel severityLevel )
699 {
700 this.severityLevel = severityLevel;
701 }
702
703 public boolean isEnableRulesSummary()
704 {
705 return enableRulesSummary;
706 }
707
708 public void setEnableRulesSummary( boolean enableRulesSummary )
709 {
710 this.enableRulesSummary = enableRulesSummary;
711 }
712
713 public boolean isEnableSeveritySummary()
714 {
715 return enableSeveritySummary;
716 }
717
718 public void setEnableSeveritySummary( boolean enableSeveritySummary )
719 {
720 this.enableSeveritySummary = enableSeveritySummary;
721 }
722
723 public boolean isEnableFilesSummary()
724 {
725 return enableFilesSummary;
726 }
727
728 public void setEnableFilesSummary( boolean enableFilesSummary )
729 {
730 this.enableFilesSummary = enableFilesSummary;
731 }
732
733 public boolean isEnableRSS()
734 {
735 return enableRSS;
736 }
737
738 public void setEnableRSS( boolean enableRSS )
739 {
740 this.enableRSS = enableRSS;
741 }
742
743 public String getXrefLocation()
744 {
745 return xrefLocation;
746 }
747
748 public void setXrefLocation( String xrefLocation )
749 {
750 this.xrefLocation = xrefLocation;
751 }
752
753 public Configuration getCheckstyleConfig()
754 {
755 return checkstyleConfig;
756 }
757
758 public void setCheckstyleConfig( Configuration config )
759 {
760 this.checkstyleConfig = config;
761 }
762
763 public void setTreeWalkerNames( List<String> treeWalkerNames )
764 {
765 this.treeWalkerNames = treeWalkerNames;
766 }
767
768 public List<String> getTreeWalkerNames()
769 {
770 return treeWalkerNames;
771 }
772
773
774
775
776
777 private String getCheckstyleVersion()
778 {
779 Package checkstyleApiPackage = Configuration.class.getPackage();
780
781 return ( checkstyleApiPackage == null ) ? null : checkstyleApiPackage.getImplementationVersion();
782 }
783
784 public List<ConfReference> sortConfiguration( CheckstyleResults results )
785 {
786 List<ConfReference> result = new ArrayList<>();
787
788 sortConfiguration( result, checkstyleConfig, null, results );
789
790 Collections.sort( result );
791
792 return result;
793 }
794
795 private void sortConfiguration( List<ConfReference> result, Configuration config,
796 ChainedItem<Configuration> parent, CheckstyleResults results )
797 {
798 for ( Configuration childConfig : config.getChildren() )
799 {
800 String ruleName = childConfig.getName();
801
802 if ( treeWalkerNames.contains( ruleName ) )
803 {
804
805 sortConfiguration( result, childConfig, new ChainedItem<>( config, parent ), results );
806 }
807 else
808 {
809 String fixedmessage = getConfigAttribute( childConfig, null, "message", null );
810
811
812
813 String configSeverity = getConfigAttribute( childConfig, null, "severity", null );
814
815
816 long violations = 0;
817 AuditEvent lastMatchedEvent = null;
818 for ( List<AuditEvent> errors : results.getFiles().values() )
819 {
820 for ( AuditEvent event : errors )
821 {
822 if ( matchRule( event, ruleName, fixedmessage, configSeverity ) )
823 {
824 lastMatchedEvent = event;
825 violations++;
826 }
827 }
828 }
829
830 if ( violations > 0 )
831 {
832 String category = RuleUtil.getCategory( lastMatchedEvent );
833
834 result.add( new ConfReference( category, childConfig, parent, violations, result.size() ) );
835 }
836 }
837 }
838 }
839
840 private static class ConfReference
841 implements Comparable<ConfReference>
842 {
843 private final String category;
844 private final Configuration configuration;
845 private final ChainedItem<Configuration> parentConfiguration;
846 private final long violations;
847 private final int count;
848
849 ConfReference( String category, Configuration configuration,
850 ChainedItem<Configuration> parentConfiguration, long violations, int count )
851 {
852 this.category = category;
853 this.configuration = configuration;
854 this.parentConfiguration = parentConfiguration;
855 this.violations = violations;
856 this.count = count;
857 }
858
859 public int compareTo( ConfReference o )
860 {
861 int compare = category.compareTo( o.category );
862 if ( compare == 0 )
863 {
864 compare = configuration.getName().compareTo( o.configuration.getName() );
865 }
866 return ( compare == 0 ) ? ( o.count - count ) : compare;
867 }
868 }
869
870 private static class ChainedItem<T>
871 {
872 private final ChainedItem<T> parent;
873
874 private final T value;
875
876 ChainedItem( T value, ChainedItem<T> parent )
877 {
878 this.parent = parent;
879 this.value = value;
880 }
881 }
882
883 }