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