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