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