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