View Javadoc
1   package org.apache.maven.plugins.checkstyle;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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.List;
27  import java.util.ResourceBundle;
28  
29  import org.apache.commons.lang3.StringUtils;
30  import org.apache.maven.doxia.sink.Sink;
31  import org.apache.maven.doxia.sink.SinkEventAttributes;
32  import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
33  import org.apache.maven.doxia.tools.SiteTool;
34  import org.apache.maven.plugin.logging.Log;
35  import org.apache.maven.plugin.logging.SystemStreamLog;
36  import org.apache.maven.plugins.checkstyle.exec.CheckstyleResults;
37  
38  import com.puppycrawl.tools.checkstyle.api.AuditEvent;
39  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
40  import com.puppycrawl.tools.checkstyle.api.Configuration;
41  import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
42  
43  /**
44   * Generate a report based on CheckstyleResults.
45   *
46   *
47   */
48  public class CheckstyleReportGenerator
49  {
50      private Log log;
51  
52      private final File basedir;
53  
54      private final ResourceBundle bundle;
55  
56      private final Sink sink;
57  
58      private SeverityLevel severityLevel;
59  
60      private Configuration checkstyleConfig;
61  
62      private boolean enableRulesSummary;
63  
64      private boolean enableSeveritySummary;
65  
66      private boolean enableFilesSummary;
67  
68      @Deprecated
69      private boolean enableRSS;
70  
71      private final SiteTool siteTool;
72  
73      private String xrefLocation;
74  
75      private String xrefTestLocation;
76  
77      private List<File> testSourceDirectories = new ArrayList<>();
78  
79      private List<String> treeWalkerNames = Collections.singletonList( "TreeWalker" );
80  
81      private final IconTool iconTool;
82  
83      private final String ruleset;
84  
85      public CheckstyleReportGenerator( Sink sink, ResourceBundle bundle, File basedir, SiteTool siteTool,
86                                        String ruleset )
87      {
88          this.bundle = bundle;
89  
90          this.sink = sink;
91  
92          this.basedir = basedir;
93  
94          this.siteTool = siteTool;
95  
96          this.ruleset = ruleset;
97  
98          this.enableRulesSummary = true;
99          this.enableSeveritySummary = true;
100         this.enableFilesSummary = true;
101         this.enableRSS = false;
102         this.iconTool = new IconTool( sink, bundle );
103     }
104 
105     public Log getLog()
106     {
107         if ( this.log == null )
108         {
109             this.log = new SystemStreamLog();
110         }
111         return this.log;
112     }
113 
114     public void setLog( Log log )
115     {
116         this.log = log;
117     }
118 
119     private String getTitle()
120     {
121         String title;
122 
123         if ( getSeverityLevel() == null )
124         {
125             title = bundle.getString( "report.checkstyle.title" );
126         }
127         else
128         {
129             title = bundle.getString( "report.checkstyle.severity_title" ) + severityLevel.getName();
130         }
131 
132         return title;
133     }
134 
135     public void generateReport( CheckstyleResults results )
136     {
137         doHeading();
138 
139         if ( getSeverityLevel() == null )
140         {
141             if ( enableSeveritySummary )
142             {
143                 doSeveritySummary( results );
144             }
145 
146             if ( enableFilesSummary )
147             {
148                 doFilesSummary( results );
149             }
150 
151             if ( enableRulesSummary )
152             {
153                 doRulesSummary( results );
154             }
155         }
156 
157         doDetails( results );
158         sink.body_();
159         sink.flush();
160         sink.close();
161     }
162 
163     private void doHeading()
164     {
165         sink.head();
166         sink.title();
167         sink.text( getTitle() );
168         sink.title_();
169         sink.head_();
170 
171         sink.body();
172 
173         sink.section1();
174         sink.sectionTitle1();
175         sink.text( getTitle() );
176         sink.sectionTitle1_();
177 
178         sink.paragraph();
179         sink.text( bundle.getString( "report.checkstyle.checkstylelink" ) + " " );
180         sink.link( "https://checkstyle.org/" );
181         sink.text( "Checkstyle" );
182         sink.link_();
183         String version = getCheckstyleVersion();
184         if ( version != null )
185         {
186             sink.text( " " );
187             sink.text( version );
188         }
189         sink.text( " " );
190         sink.text( String.format( bundle.getString( "report.checkstyle.ruleset" ), ruleset ) );
191         sink.text( "." );
192 
193         if ( enableRSS )
194         {
195             sink.nonBreakingSpace();
196             sink.link( "checkstyle.rss" );
197             sink.figure();
198             sink.figureCaption();
199             sink.text( "rss feed" );
200             sink.figureCaption_();
201             sink.figureGraphics( "images/rss.png" );
202             sink.figure_();
203             sink.link_();
204         }
205 
206         sink.paragraph_();
207         sink.section1_();
208     }
209 
210     /**
211      * Get the value of the specified attribute from the Checkstyle configuration.
212      * If parentConfigurations is non-null and non-empty, the parent
213      * configurations are searched if the attribute cannot be found in the
214      * current configuration. If the attribute is still not found, the
215      * specified default value will be returned.
216      *
217      * @param config The current Checkstyle configuration
218      * @param parentConfiguration The configuration of the parent of the current configuration
219      * @param attributeName The name of the attribute
220      * @param defaultValue The default value to use if the attribute cannot be found in any configuration
221      * @return The value of the specified attribute
222      */
223     private String getConfigAttribute( Configuration config, ChainedItem<Configuration> parentConfiguration,
224                                        String attributeName, String defaultValue )
225     {
226         String ret;
227         try
228         {
229             ret = config.getAttribute( attributeName );
230         }
231         catch ( CheckstyleException e )
232         {
233             // Try to find the attribute in a parent, if there are any
234             if ( parentConfiguration != null )
235             {
236                 ret =
237                     getConfigAttribute( parentConfiguration.value, parentConfiguration.parent, attributeName,
238                                         defaultValue );
239             }
240             else
241             {
242                 ret = defaultValue;
243             }
244         }
245         return ret;
246     }
247 
248     /**
249      * Create the rules summary section of the report.
250      *
251      * @param results The results to summarize
252      */
253     private void doRulesSummary( CheckstyleResults results )
254     {
255         if ( checkstyleConfig == null )
256         {
257             return;
258         }
259 
260         sink.section1();
261         sink.sectionTitle1();
262         sink.text( bundle.getString( "report.checkstyle.rules" ) );
263         sink.sectionTitle1_();
264 
265         sink.table();
266         sink.tableRows( null, false );
267 
268         sink.tableRow();
269         sink.tableHeaderCell();
270         sink.text( bundle.getString( "report.checkstyle.rule.category" ) );
271         sink.tableHeaderCell_();
272 
273         sink.tableHeaderCell();
274         sink.text( bundle.getString( "report.checkstyle.rule" ) );
275         sink.tableHeaderCell_();
276 
277         sink.tableHeaderCell();
278         sink.text( bundle.getString( "report.checkstyle.violations" ) );
279         sink.tableHeaderCell_();
280 
281         sink.tableHeaderCell();
282         sink.text( bundle.getString( "report.checkstyle.column.severity" ) );
283         sink.tableHeaderCell_();
284 
285         sink.tableRow_();
286 
287         // Top level should be the checker.
288         if ( "checker".equalsIgnoreCase( checkstyleConfig.getName() ) )
289         {
290             String category = null;
291             for ( ConfReference ref: sortConfiguration( results ) )
292             {
293                 doRuleRow( ref, results, category );
294 
295                 category = ref.category;
296             }
297         }
298         else
299         {
300             sink.tableRow();
301             sink.tableCell();
302             sink.text( bundle.getString( "report.checkstyle.norule" ) );
303             sink.tableCell_();
304             sink.tableRow_();
305         }
306 
307         sink.tableRows_();
308         sink.table_();
309 
310         sink.section1_();
311     }
312 
313     /**
314      * Create a summary for one Checkstyle rule.
315      *
316      * @param ref The configuration reference for the row
317      * @param results The results to summarize
318      * @param previousCategory The previous row's category
319      */
320     private void doRuleRow( ConfReference ref, CheckstyleResults results, String previousCategory )
321     {
322         Configuration checkerConfig = ref.configuration;
323         ChainedItem<Configuration> parentConfiguration = ref.parentConfiguration;
324         String ruleName = checkerConfig.getName();
325 
326         sink.tableRow();
327 
328         // column 1: rule category
329         sink.tableCell();
330         String category = ref.category;
331         if ( !category.equals( previousCategory ) )
332         {
333             sink.text( category );
334         }
335         sink.tableCell_();
336 
337         // column 2: Rule name + configured attributes
338         sink.tableCell();
339         if ( !"extension".equals( category ) )
340         {
341             sink.link( "https://checkstyle.org/config_" + category + ".html#" + ruleName );
342             sink.text( ruleName );
343             sink.link_();
344         }
345         else
346         {
347             sink.text( ruleName );
348         }
349 
350         List<String> attribnames = new ArrayList<>( Arrays.asList( checkerConfig.getAttributeNames() ) );
351         attribnames.remove( "severity" ); // special value (deserves unique column)
352         if ( !attribnames.isEmpty() )
353         {
354             sink.list();
355             for ( String name : attribnames )
356             {
357                 sink.listItem();
358 
359                 sink.text( name );
360 
361                 String value = getConfigAttribute( checkerConfig, null, name, "" );
362                 // special case, Header.header and RegexpHeader.header
363                 if ( "header".equals( name ) && ( "Header".equals( ruleName ) || "RegexpHeader".equals( ruleName ) ) )
364                 {
365                     String[] lines = StringUtils.split( value, "\\n" );
366                     int linenum = 1;
367                     for ( String line : lines )
368                     {
369                         sink.lineBreak();
370                         sink.rawText( "<span style=\"color: gray\">" );
371                         sink.text( linenum + ":" );
372                         sink.rawText( "</span>" );
373                         sink.nonBreakingSpace();
374                         sink.monospaced();
375                         sink.text( line );
376                         sink.monospaced_();
377                         linenum++;
378                     }
379                 }
380                 else if ( "headerFile".equals( name ) && "RegexpHeader".equals( ruleName ) )
381                 {
382                     sink.text( ": " );
383                     sink.monospaced();
384                     sink.text( "\"" );
385                     if ( basedir != null )
386                     {
387                         // Make the headerFile value relative to ${basedir}
388                         String path = siteTool.getRelativePath( value, basedir.getAbsolutePath() );
389                         sink.text( path.replace( '\\', '/' ) );
390                     }
391                     else
392                     {
393                         sink.text( value );
394                     }
395                     sink.text( "\"" );
396                     sink.monospaced_();
397                 }
398                 else
399                 {
400                     sink.text( ": " );
401                     sink.monospaced();
402                     sink.text( "\"" );
403                     sink.text( value );
404                     sink.text( "\"" );
405                     sink.monospaced_();
406                 }
407                 sink.listItem_();
408             }
409             sink.list_();
410         }
411 
412         sink.tableCell_();
413 
414         // column 3: rule violation count
415         sink.tableCell();
416         sink.text( String.valueOf( ref.violations ) );
417         sink.tableCell_();
418 
419         // column 4: severity
420         sink.tableCell();
421         // Grab the severity from the rule configuration, this time use error as default value
422         // Also pass along all parent configurations, so that we can try to find the severity there
423         String severity = getConfigAttribute( checkerConfig, parentConfiguration, "severity", "error" );
424         iconTool.iconSeverity( severity, IconTool.TEXT_SIMPLE );
425         sink.tableCell_();
426 
427         sink.tableRow_();
428     }
429 
430     /**
431      * Check if a violation matches a rule.
432      *
433      * @param event the violation to check
434      * @param ruleName The name of the rule
435      * @param expectedMessage A message that, if it's not null, will be matched to the message from the violation
436      * @param expectedSeverity A severity that, if it's not null, will be matched to the severity from the violation
437      * @return The number of rule violations
438      */
439     public boolean matchRule( AuditEvent event, String ruleName, String expectedMessage, String expectedSeverity )
440     {
441         if ( !ruleName.equals( RuleUtil.getName( event ) ) )
442         {
443             return false;
444         }
445 
446         // check message too, for those that have a specific one.
447         // like GenericIllegalRegexp and Regexp
448         if ( expectedMessage != null )
449         {
450             // event.getMessage() uses java.text.MessageFormat in its implementation.
451             // Read MessageFormat Javadoc about single quote:
452             // http://java.sun.com/j2se/1.4.2/docs/api/java/text/MessageFormat.html
453             String msgWithoutSingleQuote = StringUtils.replace( expectedMessage, "'", "" );
454 
455             if ( ! ( expectedMessage.equals( event.getMessage() )
456                 || msgWithoutSingleQuote.equals( event.getMessage() ) ) )
457             {
458                 return false;
459             }
460         }
461         // Check the severity. This helps to distinguish between
462         // different configurations for the same rule, where each
463         // configuration has a different severity, like JavadocMethod.
464         // See also https://issues.apache.org/jira/browse/MCHECKSTYLE-41
465         if ( expectedSeverity != null )
466         {
467             if ( !expectedSeverity.equals( event.getSeverityLevel().getName() ) )
468             {
469                 return false;
470             }
471         }
472         return true;
473     }
474 
475     private void doSeveritySummary( CheckstyleResults results )
476     {
477         sink.section1();
478         sink.sectionTitle1();
479         sink.text( bundle.getString( "report.checkstyle.summary" ) );
480         sink.sectionTitle1_();
481 
482         sink.table();
483         sink.tableRows( null, false );
484 
485         sink.tableRow();
486         sink.tableHeaderCell();
487         sink.text( bundle.getString( "report.checkstyle.files" ) );
488         sink.tableHeaderCell_();
489 
490         sink.tableHeaderCell();
491         iconTool.iconInfo( IconTool.TEXT_TITLE );
492         sink.tableHeaderCell_();
493 
494         sink.tableHeaderCell();
495         iconTool.iconWarning( IconTool.TEXT_TITLE );
496         sink.tableHeaderCell_();
497 
498         sink.tableHeaderCell();
499         iconTool.iconError( IconTool.TEXT_TITLE );
500         sink.tableHeaderCell_();
501         sink.tableRow_();
502 
503         sink.tableRow();
504         sink.tableCell();
505         sink.text( String.valueOf( results.getFileCount() ) );
506         sink.tableCell_();
507         sink.tableCell();
508         sink.text( String.valueOf( results.getSeverityCount( SeverityLevel.INFO ) ) );
509         sink.tableCell_();
510         sink.tableCell();
511         sink.text( String.valueOf( results.getSeverityCount( SeverityLevel.WARNING ) ) );
512         sink.tableCell_();
513         sink.tableCell();
514         sink.text( String.valueOf( results.getSeverityCount( SeverityLevel.ERROR ) ) );
515         sink.tableCell_();
516         sink.tableRow_();
517 
518         sink.tableRows_();
519         sink.table_();
520 
521         sink.section1_();
522     }
523 
524     private void doFilesSummary( CheckstyleResults results )
525     {
526         sink.section1();
527         sink.sectionTitle1();
528         sink.text( bundle.getString( "report.checkstyle.files" ) );
529         sink.sectionTitle1_();
530 
531         sink.table();
532         sink.tableRows( null, false );
533 
534         sink.tableRow();
535         sink.tableHeaderCell();
536         sink.text( bundle.getString( "report.checkstyle.file" ) );
537         sink.tableHeaderCell_();
538         sink.tableHeaderCell();
539         iconTool.iconInfo( IconTool.TEXT_ABBREV );
540         sink.tableHeaderCell_();
541         sink.tableHeaderCell();
542         iconTool.iconWarning( IconTool.TEXT_ABBREV );
543         sink.tableHeaderCell_();
544         sink.tableHeaderCell();
545         iconTool.iconError( IconTool.TEXT_ABBREV );
546         sink.tableHeaderCell_();
547         sink.tableRow_();
548 
549         // Sort the files before writing them to the report
550         List<String> fileList = new ArrayList<>( results.getFiles().keySet() );
551         Collections.sort( fileList );
552 
553         for ( String filename : fileList )
554         {
555             List<AuditEvent> violations = results.getFileViolations( filename );
556             if ( violations.isEmpty() )
557             {
558                 // skip files without violations
559                 continue;
560             }
561 
562             sink.tableRow();
563 
564             sink.tableCell();
565             sink.link( "#" + filename.replace( '/', '.' ) );
566             sink.text( filename );
567             sink.link_();
568             sink.tableCell_();
569 
570             sink.tableCell();
571             sink.text( String.valueOf( results.getSeverityCount( violations, SeverityLevel.INFO ) ) );
572             sink.tableCell_();
573 
574             sink.tableCell();
575             sink.text( String.valueOf( results.getSeverityCount( violations, SeverityLevel.WARNING ) ) );
576             sink.tableCell_();
577 
578             sink.tableCell();
579             sink.text( String.valueOf( results.getSeverityCount( violations, SeverityLevel.ERROR ) ) );
580             sink.tableCell_();
581 
582             sink.tableRow_();
583         }
584 
585         sink.tableRows_();
586         sink.table_();
587 
588         sink.section1_();
589     }
590 
591     private void doDetails( CheckstyleResults results )
592     {
593 
594         sink.section1();
595         sink.sectionTitle1();
596         sink.text( bundle.getString( "report.checkstyle.details" ) );
597         sink.sectionTitle1_();
598 
599         // Sort the files before writing their details to the report
600         List<String> fileList = new ArrayList<>( results.getFiles().keySet() );
601         Collections.sort( fileList );
602 
603         for ( String file : fileList )
604         {
605             List<AuditEvent> violations = results.getFileViolations( file );
606 
607             if ( violations.isEmpty() )
608             {
609                 // skip files without violations
610                 continue;
611             }
612 
613             sink.section2();
614             SinkEventAttributes attrs = new SinkEventAttributeSet();
615             attrs.addAttribute( SinkEventAttributes.ID, file.replace( '/', '.' ) );
616             sink.sectionTitle( Sink.SECTION_LEVEL_2, attrs );
617             sink.text( file );
618             sink.sectionTitle_( Sink.SECTION_LEVEL_2 );
619 
620             sink.table();
621             sink.tableRows( null, false );
622 
623             sink.tableRow();
624             sink.tableHeaderCell();
625             sink.text( bundle.getString( "report.checkstyle.column.severity" ) );
626             sink.tableHeaderCell_();
627             sink.tableHeaderCell();
628             sink.text( bundle.getString( "report.checkstyle.rule.category" ) );
629             sink.tableHeaderCell_();
630             sink.tableHeaderCell();
631             sink.text( bundle.getString( "report.checkstyle.rule" ) );
632             sink.tableHeaderCell_();
633             sink.tableHeaderCell();
634             sink.text( bundle.getString( "report.checkstyle.column.message" ) );
635             sink.tableHeaderCell_();
636             sink.tableHeaderCell();
637             sink.text( bundle.getString( "report.checkstyle.column.line" ) );
638             sink.tableHeaderCell_();
639             sink.tableRow_();
640 
641             doFileEvents( violations, file );
642 
643             sink.tableRows_();
644             sink.table_();
645 
646             sink.section2_();
647         }
648 
649         sink.section1_();
650     }
651 
652     private void doFileEvents( List<AuditEvent> eventList, String filename )
653     {
654         for ( AuditEvent event : eventList )
655         {
656             SeverityLevel level = event.getSeverityLevel();
657 
658             if ( ( getSeverityLevel() != null ) && !( getSeverityLevel() != level ) )
659             {
660                 continue;
661             }
662 
663             sink.tableRow();
664 
665             sink.tableCell();
666             iconTool.iconSeverity( level.getName(), IconTool.TEXT_SIMPLE );
667             sink.tableCell_();
668 
669             sink.tableCell();
670             String category = RuleUtil.getCategory( event );
671             if ( category != null )
672             {
673                 sink.text( category );
674             }
675             sink.tableCell_();
676 
677             sink.tableCell();
678             String ruleName = RuleUtil.getName( event );
679             if ( ruleName != null )
680             {
681                 sink.text( ruleName );
682             }
683             sink.tableCell_();
684 
685             sink.tableCell();
686             sink.text( event.getMessage() );
687             sink.tableCell_();
688 
689             sink.tableCell();
690 
691             int line = event.getLine();
692             String effectiveXrefLocation = getEffectiveXrefLocation( eventList );
693             if ( effectiveXrefLocation != null && line != 0 )
694             {
695                 sink.link( effectiveXrefLocation + "/" + filename.replaceAll( "\\.java$", ".html" ) + "#L"
696                     + line );
697                 sink.text( String.valueOf( line ) );
698                 sink.link_();
699             }
700             else if ( line != 0 )
701             {
702                 sink.text( String.valueOf( line ) );
703             }
704             sink.tableCell_();
705 
706             sink.tableRow_();
707         }
708     }
709 
710     private String getEffectiveXrefLocation( List<AuditEvent> eventList )
711     {
712         String absoluteFilename = eventList.get( 0 ).getFileName();
713         if ( isTestSource( absoluteFilename ) )
714         {
715             return getXrefTestLocation();
716         }
717         else
718         {
719             return getXrefLocation();
720         }
721     }
722 
723     private boolean isTestSource( final String absoluteFilename )
724     {
725         for ( File testSourceDirectory : testSourceDirectories )
726         {
727             if ( absoluteFilename.startsWith( testSourceDirectory.getAbsolutePath() ) )
728             {
729                 return true;
730             }
731         }
732 
733         return false;
734     }
735 
736     public SeverityLevel getSeverityLevel()
737     {
738         return severityLevel;
739     }
740 
741     public void setSeverityLevel( SeverityLevel severityLevel )
742     {
743         this.severityLevel = severityLevel;
744     }
745 
746     public boolean isEnableRulesSummary()
747     {
748         return enableRulesSummary;
749     }
750 
751     public void setEnableRulesSummary( boolean enableRulesSummary )
752     {
753         this.enableRulesSummary = enableRulesSummary;
754     }
755 
756     public boolean isEnableSeveritySummary()
757     {
758         return enableSeveritySummary;
759     }
760 
761     public void setEnableSeveritySummary( boolean enableSeveritySummary )
762     {
763         this.enableSeveritySummary = enableSeveritySummary;
764     }
765 
766     public boolean isEnableFilesSummary()
767     {
768         return enableFilesSummary;
769     }
770 
771     public void setEnableFilesSummary( boolean enableFilesSummary )
772     {
773         this.enableFilesSummary = enableFilesSummary;
774     }
775 
776     @Deprecated
777     public boolean isEnableRSS()
778     {
779         return enableRSS;
780     }
781 
782     @Deprecated
783     public void setEnableRSS( boolean enableRSS )
784     {
785         this.enableRSS = enableRSS;
786     }
787 
788     public String getXrefLocation()
789     {
790         return xrefLocation;
791     }
792 
793     public void setXrefLocation( String xrefLocation )
794     {
795         this.xrefLocation = xrefLocation;
796     }
797 
798     public String getXrefTestLocation()
799     {
800         return xrefTestLocation;
801     }
802 
803     public void setXrefTestLocation( String xrefTestLocation )
804     {
805         this.xrefTestLocation = xrefTestLocation;
806     }
807 
808     public List<File> getTestSourceDirectories()
809     {
810         return testSourceDirectories;
811     }
812 
813     public void setTestSourceDirectories( List<File> testSourceDirectories )
814     {
815         this.testSourceDirectories = testSourceDirectories;
816     }
817 
818     public Configuration getCheckstyleConfig()
819     {
820         return checkstyleConfig;
821     }
822 
823     public void setCheckstyleConfig( Configuration config )
824     {
825         this.checkstyleConfig = config;
826     }
827 
828     public void setTreeWalkerNames( List<String> treeWalkerNames )
829     {
830         this.treeWalkerNames = treeWalkerNames;
831     }
832 
833     public List<String> getTreeWalkerNames()
834     {
835         return treeWalkerNames;
836     }
837 
838     /**
839      * Get the effective Checkstyle version at runtime.
840      * @return the MANIFEST implementation version of Checkstyle API package (can be <code>null</code>)
841      */
842     private String getCheckstyleVersion()
843     {
844         Package checkstyleApiPackage = Configuration.class.getPackage();
845 
846         return ( checkstyleApiPackage == null ) ? null : checkstyleApiPackage.getImplementationVersion();
847     }
848 
849     public List<ConfReference> sortConfiguration( CheckstyleResults results )
850     {
851         List<ConfReference> result = new ArrayList<>();
852 
853         sortConfiguration( result, checkstyleConfig, null, results );
854 
855         Collections.sort( result );
856 
857         return result;
858     }
859 
860     private void sortConfiguration( List<ConfReference> result, Configuration config,
861                                     ChainedItem<Configuration> parent, CheckstyleResults results )
862     {
863         for ( Configuration childConfig : config.getChildren() )
864         {
865             String ruleName = childConfig.getName();
866 
867             if ( treeWalkerNames.contains( ruleName ) )
868             {
869                 // special sub-case: TreeWalker is the parent of multiple rules, not an effective rule
870                 sortConfiguration( result, childConfig, new ChainedItem<>( config, parent ), results );
871             }
872             else
873             {
874                 String fixedmessage = getConfigAttribute( childConfig, null, "message", null );
875                 // Grab the severity from the rule configuration. Do not set default value here as
876                 // it breaks our rule aggregate section entirely.  The counts are off but this is
877                 // not appropriate fix location per MCHECKSTYLE-365.
878                 String configSeverity = getConfigAttribute( childConfig, null, "severity", null );
879 
880                 // count rule violations
881                 long violations = 0;
882                 AuditEvent lastMatchedEvent = null;
883                 for ( List<AuditEvent> errors : results.getFiles().values() )
884                 {
885                     for ( AuditEvent event : errors )
886                     {
887                         if ( matchRule( event, ruleName, fixedmessage, configSeverity ) )
888                         {
889                             lastMatchedEvent = event;
890                             violations++;
891                         }
892                     }
893                 }
894 
895                 if ( violations > 0 ) // forget rules without violations
896                 {
897                     String category = RuleUtil.getCategory( lastMatchedEvent );
898 
899                     result.add( new ConfReference( category, childConfig, parent, violations, result.size() ) );
900                 }
901             }
902         }
903     }
904 
905     private static class ConfReference
906         implements Comparable<ConfReference>
907     {
908         private final String category;
909         private final Configuration configuration;
910         private final ChainedItem<Configuration> parentConfiguration;
911         private final long violations;
912         private final int count;
913 
914         ConfReference( String category, Configuration configuration,
915                               ChainedItem<Configuration> parentConfiguration, long violations, int count )
916         {
917             this.category = category;
918             this.configuration = configuration;
919             this.parentConfiguration = parentConfiguration;
920             this.violations = violations;
921             this.count = count;
922         }
923 
924         public int compareTo( ConfReference o )
925         {
926             int compare = category.compareTo( o.category );
927             if ( compare == 0 )
928             {
929                 compare = configuration.getName().compareTo( o.configuration.getName() );
930             }
931             return ( compare == 0 ) ? ( o.count - count ) : compare;
932         }
933     }
934 
935     private static class ChainedItem<T>
936     {
937         private final ChainedItem<T> parent;
938 
939         private final T value;
940 
941         ChainedItem( T value, ChainedItem<T> parent )
942         {
943             this.parent = parent;
944             this.value = value;
945         }
946     }
947 
948 }