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