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