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.maven.doxia.sink.Sink;
30  import org.apache.maven.doxia.sink.SinkEventAttributes;
31  import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
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.apache.maven.plugins.checkstyle.exec.CheckstyleResults;
36  import org.codehaus.plexus.util.StringUtils;
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 
267         sink.tableRow();
268         sink.tableHeaderCell();
269         sink.text( bundle.getString( "report.checkstyle.rule.category" ) );
270         sink.tableHeaderCell_();
271 
272         sink.tableHeaderCell();
273         sink.text( bundle.getString( "report.checkstyle.rule" ) );
274         sink.tableHeaderCell_();
275 
276         sink.tableHeaderCell();
277         sink.text( bundle.getString( "report.checkstyle.violations" ) );
278         sink.tableHeaderCell_();
279 
280         sink.tableHeaderCell();
281         sink.text( bundle.getString( "report.checkstyle.column.severity" ) );
282         sink.tableHeaderCell_();
283 
284         sink.tableRow_();
285 
286         // Top level should be the checker.
287         if ( "checker".equalsIgnoreCase( checkstyleConfig.getName() ) )
288         {
289             String category = null;
290             for ( ConfReference ref: sortConfiguration( results ) )
291             {
292                 doRuleRow( ref, results, category );
293 
294                 category = ref.category;
295             }
296         }
297         else
298         {
299             sink.tableRow();
300             sink.tableCell();
301             sink.text( bundle.getString( "report.checkstyle.norule" ) );
302             sink.tableCell_();
303             sink.tableRow_();
304         }
305 
306         sink.table_();
307 
308         sink.section1_();
309     }
310 
311     /**
312      * Create a summary for one Checkstyle rule.
313      *
314      * @param ref The configuration reference for the row
315      * @param results The results to summarize
316      * @param previousCategory The previous row's category
317      */
318     private void doRuleRow( ConfReference ref, CheckstyleResults results, String previousCategory )
319     {
320         Configuration checkerConfig = ref.configuration;
321         ChainedItem<Configuration> parentConfiguration = ref.parentConfiguration;
322         String ruleName = checkerConfig.getName();
323 
324         sink.tableRow();
325 
326         // column 1: rule category
327         sink.tableCell();
328         String category = ref.category;
329         if ( !category.equals( previousCategory ) )
330         {
331             sink.text( category );
332         }
333         sink.tableCell_();
334 
335         // column 2: Rule name + configured attributes
336         sink.tableCell();
337         if ( !"extension".equals( category ) )
338         {
339             sink.link( "https://checkstyle.org/config_" + category + ".html#" + ruleName );
340             sink.text( ruleName );
341             sink.link_();
342         }
343         else
344         {
345             sink.text( ruleName );
346         }
347 
348         List<String> attribnames = new ArrayList<>( Arrays.asList( checkerConfig.getAttributeNames() ) );
349         attribnames.remove( "severity" ); // special value (deserves unique column)
350         if ( !attribnames.isEmpty() )
351         {
352             sink.list();
353             for ( String name : attribnames )
354             {
355                 sink.listItem();
356 
357                 sink.text( name );
358 
359                 String value = getConfigAttribute( checkerConfig, null, name, "" );
360                 // special case, Header.header and RegexpHeader.header
361                 if ( "header".equals( name ) && ( "Header".equals( ruleName ) || "RegexpHeader".equals( ruleName ) ) )
362                 {
363                     String[] lines = StringUtils.split( value, "\\n" );
364                     int linenum = 1;
365                     for ( String line : lines )
366                     {
367                         sink.lineBreak();
368                         sink.rawText( "<span style=\"color: gray\">" );
369                         sink.text( linenum + ":" );
370                         sink.rawText( "</span>" );
371                         sink.nonBreakingSpace();
372                         sink.monospaced();
373                         sink.text( line );
374                         sink.monospaced_();
375                         linenum++;
376                     }
377                 }
378                 else if ( "headerFile".equals( name ) && "RegexpHeader".equals( ruleName ) )
379                 {
380                     sink.text( ": " );
381                     sink.monospaced();
382                     sink.text( "\"" );
383                     if ( basedir != null )
384                     {
385                         // Make the headerFile value relative to ${basedir}
386                         String path = siteTool.getRelativePath( value, basedir.getAbsolutePath() );
387                         sink.text( path.replace( '\\', '/' ) );
388                     }
389                     else
390                     {
391                         sink.text( value );
392                     }
393                     sink.text( "\"" );
394                     sink.monospaced_();
395                 }
396                 else
397                 {
398                     sink.text( ": " );
399                     sink.monospaced();
400                     sink.text( "\"" );
401                     sink.text( value );
402                     sink.text( "\"" );
403                     sink.monospaced_();
404                 }
405                 sink.listItem_();
406             }
407             sink.list_();
408         }
409 
410         sink.tableCell_();
411 
412         // column 3: rule violation count
413         sink.tableCell();
414         sink.text( String.valueOf( ref.violations ) );
415         sink.tableCell_();
416 
417         // column 4: severity
418         sink.tableCell();
419         // Grab the severity from the rule configuration, this time use error as default value
420         // Also pass along all parent configurations, so that we can try to find the severity there
421         String severity = getConfigAttribute( checkerConfig, parentConfiguration, "severity", "error" );
422         iconTool.iconSeverity( severity, IconTool.TEXT_SIMPLE );
423         sink.tableCell_();
424 
425         sink.tableRow_();
426     }
427 
428     /**
429      * Check if a violation matches a rule.
430      *
431      * @param event the violation to check
432      * @param ruleName The name of the rule
433      * @param expectedMessage A message that, if it's not null, will be matched to the message from the violation
434      * @param expectedSeverity A severity that, if it's not null, will be matched to the severity from the violation
435      * @return The number of rule violations
436      */
437     public boolean matchRule( AuditEvent event, String ruleName, String expectedMessage, String expectedSeverity )
438     {
439         if ( !ruleName.equals( RuleUtil.getName( event ) ) )
440         {
441             return false;
442         }
443 
444         // check message too, for those that have a specific one.
445         // like GenericIllegalRegexp and Regexp
446         if ( expectedMessage != null )
447         {
448             // event.getMessage() uses java.text.MessageFormat in its implementation.
449             // Read MessageFormat Javadoc about single quote:
450             // http://java.sun.com/j2se/1.4.2/docs/api/java/text/MessageFormat.html
451             String msgWithoutSingleQuote = StringUtils.replace( expectedMessage, "'", "" );
452 
453             if ( ! ( expectedMessage.equals( event.getMessage() )
454                 || msgWithoutSingleQuote.equals( event.getMessage() ) ) )
455             {
456                 return false;
457             }
458         }
459         // Check the severity. This helps to distinguish between
460         // different configurations for the same rule, where each
461         // configuration has a different severity, like JavadocMethod.
462         // See also https://issues.apache.org/jira/browse/MCHECKSTYLE-41
463         if ( expectedSeverity != null )
464         {
465             if ( !expectedSeverity.equals( event.getSeverityLevel().getName() ) )
466             {
467                 return false;
468             }
469         }
470         return true;
471     }
472 
473     private void doSeveritySummary( CheckstyleResults results )
474     {
475         sink.section1();
476         sink.sectionTitle1();
477         sink.text( bundle.getString( "report.checkstyle.summary" ) );
478         sink.sectionTitle1_();
479 
480         sink.table();
481 
482         sink.tableRow();
483         sink.tableHeaderCell();
484         sink.text( bundle.getString( "report.checkstyle.files" ) );
485         sink.tableHeaderCell_();
486 
487         sink.tableHeaderCell();
488         iconTool.iconInfo( IconTool.TEXT_TITLE );
489         sink.tableHeaderCell_();
490 
491         sink.tableHeaderCell();
492         iconTool.iconWarning( IconTool.TEXT_TITLE );
493         sink.tableHeaderCell_();
494 
495         sink.tableHeaderCell();
496         iconTool.iconError( IconTool.TEXT_TITLE );
497         sink.tableHeaderCell_();
498         sink.tableRow_();
499 
500         sink.tableRow();
501         sink.tableCell();
502         sink.text( String.valueOf( results.getFileCount() ) );
503         sink.tableCell_();
504         sink.tableCell();
505         sink.text( String.valueOf( results.getSeverityCount( SeverityLevel.INFO ) ) );
506         sink.tableCell_();
507         sink.tableCell();
508         sink.text( String.valueOf( results.getSeverityCount( SeverityLevel.WARNING ) ) );
509         sink.tableCell_();
510         sink.tableCell();
511         sink.text( String.valueOf( results.getSeverityCount( SeverityLevel.ERROR ) ) );
512         sink.tableCell_();
513         sink.tableRow_();
514 
515         sink.table_();
516 
517         sink.section1_();
518     }
519 
520     private void doFilesSummary( CheckstyleResults results )
521     {
522         sink.section1();
523         sink.sectionTitle1();
524         sink.text( bundle.getString( "report.checkstyle.files" ) );
525         sink.sectionTitle1_();
526 
527         sink.table();
528 
529         sink.tableRow();
530         sink.tableHeaderCell();
531         sink.text( bundle.getString( "report.checkstyle.file" ) );
532         sink.tableHeaderCell_();
533         sink.tableHeaderCell();
534         iconTool.iconInfo( IconTool.TEXT_ABBREV );
535         sink.tableHeaderCell_();
536         sink.tableHeaderCell();
537         iconTool.iconWarning( IconTool.TEXT_ABBREV );
538         sink.tableHeaderCell_();
539         sink.tableHeaderCell();
540         iconTool.iconError( IconTool.TEXT_ABBREV );
541         sink.tableHeaderCell_();
542         sink.tableRow_();
543 
544         // Sort the files before writing them to the report
545         List<String> fileList = new ArrayList<>( results.getFiles().keySet() );
546         Collections.sort( fileList );
547 
548         for ( String filename : fileList )
549         {
550             List<AuditEvent> violations = results.getFileViolations( filename );
551             if ( violations.isEmpty() )
552             {
553                 // skip files without violations
554                 continue;
555             }
556 
557             sink.tableRow();
558 
559             sink.tableCell();
560             sink.link( "#" + filename.replace( '/', '.' ) );
561             sink.text( filename );
562             sink.link_();
563             sink.tableCell_();
564 
565             sink.tableCell();
566             sink.text( String.valueOf( results.getSeverityCount( violations, SeverityLevel.INFO ) ) );
567             sink.tableCell_();
568 
569             sink.tableCell();
570             sink.text( String.valueOf( results.getSeverityCount( violations, SeverityLevel.WARNING ) ) );
571             sink.tableCell_();
572 
573             sink.tableCell();
574             sink.text( String.valueOf( results.getSeverityCount( violations, SeverityLevel.ERROR ) ) );
575             sink.tableCell_();
576 
577             sink.tableRow_();
578         }
579 
580         sink.table_();
581         sink.section1_();
582     }
583 
584     private void doDetails( CheckstyleResults results )
585     {
586 
587         sink.section1();
588         sink.sectionTitle1();
589         sink.text( bundle.getString( "report.checkstyle.details" ) );
590         sink.sectionTitle1_();
591 
592         // Sort the files before writing their details to the report
593         List<String> fileList = new ArrayList<>( results.getFiles().keySet() );
594         Collections.sort( fileList );
595 
596         for ( String file : fileList )
597         {
598             List<AuditEvent> violations = results.getFileViolations( file );
599 
600             if ( violations.isEmpty() )
601             {
602                 // skip files without violations
603                 continue;
604             }
605 
606             sink.section2();
607             SinkEventAttributes attrs = new SinkEventAttributeSet();
608             attrs.addAttribute( SinkEventAttributes.ID, file.replace( '/', '.' ) );
609             sink.sectionTitle( Sink.SECTION_LEVEL_2, attrs );
610             sink.text( file );
611             sink.sectionTitle_( Sink.SECTION_LEVEL_2 );
612 
613             sink.table();
614             sink.tableRow();
615             sink.tableHeaderCell();
616             sink.text( bundle.getString( "report.checkstyle.column.severity" ) );
617             sink.tableHeaderCell_();
618             sink.tableHeaderCell();
619             sink.text( bundle.getString( "report.checkstyle.rule.category" ) );
620             sink.tableHeaderCell_();
621             sink.tableHeaderCell();
622             sink.text( bundle.getString( "report.checkstyle.rule" ) );
623             sink.tableHeaderCell_();
624             sink.tableHeaderCell();
625             sink.text( bundle.getString( "report.checkstyle.column.message" ) );
626             sink.tableHeaderCell_();
627             sink.tableHeaderCell();
628             sink.text( bundle.getString( "report.checkstyle.column.line" ) );
629             sink.tableHeaderCell_();
630             sink.tableRow_();
631 
632             doFileEvents( violations, file );
633 
634             sink.table_();
635             sink.section2_();
636         }
637 
638         sink.section1_();
639     }
640 
641     private void doFileEvents( List<AuditEvent> eventList, String filename )
642     {
643         for ( AuditEvent event : eventList )
644         {
645             SeverityLevel level = event.getSeverityLevel();
646 
647             if ( ( getSeverityLevel() != null ) && !( getSeverityLevel() != level ) )
648             {
649                 continue;
650             }
651 
652             sink.tableRow();
653 
654             sink.tableCell();
655             iconTool.iconSeverity( level.getName(), IconTool.TEXT_SIMPLE );
656             sink.tableCell_();
657 
658             sink.tableCell();
659             String category = RuleUtil.getCategory( event );
660             if ( category != null )
661             {
662                 sink.text( category );
663             }
664             sink.tableCell_();
665 
666             sink.tableCell();
667             String ruleName = RuleUtil.getName( event );
668             if ( ruleName != null )
669             {
670                 sink.text( ruleName );
671             }
672             sink.tableCell_();
673 
674             sink.tableCell();
675             sink.text( event.getMessage() );
676             sink.tableCell_();
677 
678             sink.tableCell();
679 
680             int line = event.getLine();
681             String effectiveXrefLocation = getEffectiveXrefLocation( eventList );
682             if ( effectiveXrefLocation != null && line != 0 )
683             {
684                 sink.link( effectiveXrefLocation + "/" + filename.replaceAll( "\\.java$", ".html" ) + "#L"
685                     + line );
686                 sink.text( String.valueOf( line ) );
687                 sink.link_();
688             }
689             else if ( line != 0 )
690             {
691                 sink.text( String.valueOf( line ) );
692             }
693             sink.tableCell_();
694 
695             sink.tableRow_();
696         }
697     }
698 
699     private String getEffectiveXrefLocation( List<AuditEvent> eventList )
700     {
701         String absoluteFilename = eventList.get( 0 ).getFileName();
702         if ( isTestSource( absoluteFilename ) )
703         {
704             return getXrefTestLocation();
705         }
706         else
707         {
708             return getXrefLocation();
709         }
710     }
711 
712     private boolean isTestSource( final String absoluteFilename )
713     {
714         for ( File testSourceDirectory : testSourceDirectories )
715         {
716             if ( absoluteFilename.startsWith( testSourceDirectory.getAbsolutePath() ) )
717             {
718                 return true;
719             }
720         }
721 
722         return false;
723     }
724 
725     public SeverityLevel getSeverityLevel()
726     {
727         return severityLevel;
728     }
729 
730     public void setSeverityLevel( SeverityLevel severityLevel )
731     {
732         this.severityLevel = severityLevel;
733     }
734 
735     public boolean isEnableRulesSummary()
736     {
737         return enableRulesSummary;
738     }
739 
740     public void setEnableRulesSummary( boolean enableRulesSummary )
741     {
742         this.enableRulesSummary = enableRulesSummary;
743     }
744 
745     public boolean isEnableSeveritySummary()
746     {
747         return enableSeveritySummary;
748     }
749 
750     public void setEnableSeveritySummary( boolean enableSeveritySummary )
751     {
752         this.enableSeveritySummary = enableSeveritySummary;
753     }
754 
755     public boolean isEnableFilesSummary()
756     {
757         return enableFilesSummary;
758     }
759 
760     public void setEnableFilesSummary( boolean enableFilesSummary )
761     {
762         this.enableFilesSummary = enableFilesSummary;
763     }
764 
765     @Deprecated
766     public boolean isEnableRSS()
767     {
768         return enableRSS;
769     }
770 
771     @Deprecated
772     public void setEnableRSS( boolean enableRSS )
773     {
774         this.enableRSS = enableRSS;
775     }
776 
777     public String getXrefLocation()
778     {
779         return xrefLocation;
780     }
781 
782     public void setXrefLocation( String xrefLocation )
783     {
784         this.xrefLocation = xrefLocation;
785     }
786 
787     public String getXrefTestLocation()
788     {
789         return xrefTestLocation;
790     }
791 
792     public void setXrefTestLocation( String xrefTestLocation )
793     {
794         this.xrefTestLocation = xrefTestLocation;
795     }
796 
797     public List<File> getTestSourceDirectories()
798     {
799         return testSourceDirectories;
800     }
801 
802     public void setTestSourceDirectories( List<File> testSourceDirectories )
803     {
804         this.testSourceDirectories = testSourceDirectories;
805     }
806 
807     public Configuration getCheckstyleConfig()
808     {
809         return checkstyleConfig;
810     }
811 
812     public void setCheckstyleConfig( Configuration config )
813     {
814         this.checkstyleConfig = config;
815     }
816 
817     public void setTreeWalkerNames( List<String> treeWalkerNames )
818     {
819         this.treeWalkerNames = treeWalkerNames;
820     }
821 
822     public List<String> getTreeWalkerNames()
823     {
824         return treeWalkerNames;
825     }
826 
827     /**
828      * Get the effective Checkstyle version at runtime.
829      * @return the MANIFEST implementation version of Checkstyle API package (can be <code>null</code>)
830      */
831     private String getCheckstyleVersion()
832     {
833         Package checkstyleApiPackage = Configuration.class.getPackage();
834 
835         return ( checkstyleApiPackage == null ) ? null : checkstyleApiPackage.getImplementationVersion();
836     }
837 
838     public List<ConfReference> sortConfiguration( CheckstyleResults results )
839     {
840         List<ConfReference> result = new ArrayList<>();
841 
842         sortConfiguration( result, checkstyleConfig, null, results );
843 
844         Collections.sort( result );
845 
846         return result;
847     }
848 
849     private void sortConfiguration( List<ConfReference> result, Configuration config,
850                                     ChainedItem<Configuration> parent, CheckstyleResults results )
851     {
852         for ( Configuration childConfig : config.getChildren() )
853         {
854             String ruleName = childConfig.getName();
855 
856             if ( treeWalkerNames.contains( ruleName ) )
857             {
858                 // special sub-case: TreeWalker is the parent of multiple rules, not an effective rule
859                 sortConfiguration( result, childConfig, new ChainedItem<>( config, parent ), results );
860             }
861             else
862             {
863                 String fixedmessage = getConfigAttribute( childConfig, null, "message", null );
864                 // Grab the severity from the rule configuration. Do not set default value here as
865                 // it breaks our rule aggregate section entirely.  The counts are off but this is
866                 // not appropriate fix location per MCHECKSTYLE-365.
867                 String configSeverity = getConfigAttribute( childConfig, null, "severity", null );
868 
869                 // count rule violations
870                 long violations = 0;
871                 AuditEvent lastMatchedEvent = null;
872                 for ( List<AuditEvent> errors : results.getFiles().values() )
873                 {
874                     for ( AuditEvent event : errors )
875                     {
876                         if ( matchRule( event, ruleName, fixedmessage, configSeverity ) )
877                         {
878                             lastMatchedEvent = event;
879                             violations++;
880                         }
881                     }
882                 }
883 
884                 if ( violations > 0 ) // forget rules without violations
885                 {
886                     String category = RuleUtil.getCategory( lastMatchedEvent );
887 
888                     result.add( new ConfReference( category, childConfig, parent, violations, result.size() ) );
889                 }
890             }
891         }
892     }
893 
894     private static class ConfReference
895         implements Comparable<ConfReference>
896     {
897         private final String category;
898         private final Configuration configuration;
899         private final ChainedItem<Configuration> parentConfiguration;
900         private final long violations;
901         private final int count;
902 
903         ConfReference( String category, Configuration configuration,
904                               ChainedItem<Configuration> parentConfiguration, long violations, int count )
905         {
906             this.category = category;
907             this.configuration = configuration;
908             this.parentConfiguration = parentConfiguration;
909             this.violations = violations;
910             this.count = count;
911         }
912 
913         public int compareTo( ConfReference o )
914         {
915             int compare = category.compareTo( o.category );
916             if ( compare == 0 )
917             {
918                 compare = configuration.getName().compareTo( o.configuration.getName() );
919             }
920             return ( compare == 0 ) ? ( o.count - count ) : compare;
921         }
922     }
923 
924     private static class ChainedItem<T>
925     {
926         private final ChainedItem<T> parent;
927 
928         private final T value;
929 
930         ChainedItem( T value, ChainedItem<T> parent )
931         {
932             this.parent = parent;
933             this.value = value;
934         }
935     }
936 
937 }