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