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.SinkEventAttributeSet;
31  import org.apache.maven.doxia.sink.SinkEventAttributes;
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      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             if ( ! ( expectedMessage.equals( event.getMessage() )
449                 || msgWithoutSingleQuote.equals( event.getMessage() ) ) )
450             {
451                 return false;
452             }
453         }
454         // Check the severity. This helps to distinguish between
455         // different configurations for the same rule, where each
456         // configuration has a different severity, like JavadocMethod.
457         // See also https://issues.apache.org/jira/browse/MCHECKSTYLE-41
458         if ( expectedSeverity != null )
459         {
460             if ( !expectedSeverity.equals( event.getSeverityLevel().getName() ) )
461             {
462                 return false;
463             }
464         }
465         return true;
466     }
467 
468     private void doSeveritySummary( CheckstyleResults results )
469     {
470         sink.section1();
471         sink.sectionTitle1();
472         sink.text( bundle.getString( "report.checkstyle.summary" ) );
473         sink.sectionTitle1_();
474 
475         sink.table();
476 
477         sink.tableRow();
478         sink.tableHeaderCell();
479         sink.text( bundle.getString( "report.checkstyle.files" ) );
480         sink.tableHeaderCell_();
481 
482         sink.tableHeaderCell();
483         iconTool.iconInfo( IconTool.TEXT_TITLE );
484         sink.tableHeaderCell_();
485 
486         sink.tableHeaderCell();
487         iconTool.iconWarning( IconTool.TEXT_TITLE );
488         sink.tableHeaderCell_();
489 
490         sink.tableHeaderCell();
491         iconTool.iconError( IconTool.TEXT_TITLE );
492         sink.tableHeaderCell_();
493         sink.tableRow_();
494 
495         sink.tableRow();
496         sink.tableCell();
497         sink.text( String.valueOf( results.getFileCount() ) );
498         sink.tableCell_();
499         sink.tableCell();
500         sink.text( String.valueOf( results.getSeverityCount( SeverityLevel.INFO ) ) );
501         sink.tableCell_();
502         sink.tableCell();
503         sink.text( String.valueOf( results.getSeverityCount( SeverityLevel.WARNING ) ) );
504         sink.tableCell_();
505         sink.tableCell();
506         sink.text( String.valueOf( results.getSeverityCount( SeverityLevel.ERROR ) ) );
507         sink.tableCell_();
508         sink.tableRow_();
509 
510         sink.table_();
511 
512         sink.section1_();
513     }
514 
515     private void doFilesSummary( CheckstyleResults results )
516     {
517         sink.section1();
518         sink.sectionTitle1();
519         sink.text( bundle.getString( "report.checkstyle.files" ) );
520         sink.sectionTitle1_();
521 
522         sink.table();
523 
524         sink.tableRow();
525         sink.tableHeaderCell();
526         sink.text( bundle.getString( "report.checkstyle.file" ) );
527         sink.tableHeaderCell_();
528         sink.tableHeaderCell();
529         iconTool.iconInfo( IconTool.TEXT_ABBREV );
530         sink.tableHeaderCell_();
531         sink.tableHeaderCell();
532         iconTool.iconWarning( IconTool.TEXT_ABBREV );
533         sink.tableHeaderCell_();
534         sink.tableHeaderCell();
535         iconTool.iconError( IconTool.TEXT_ABBREV );
536         sink.tableHeaderCell_();
537         sink.tableRow_();
538 
539         // Sort the files before writing them to the report
540         List<String> fileList = new ArrayList<>( results.getFiles().keySet() );
541         Collections.sort( fileList );
542 
543         for ( String filename : fileList )
544         {
545             List<AuditEvent> violations = results.getFileViolations( filename );
546             if ( violations.isEmpty() )
547             {
548                 // skip files without violations
549                 continue;
550             }
551 
552             sink.tableRow();
553 
554             sink.tableCell();
555             sink.link( "#" + filename.replace( '/', '.' ) );
556             sink.text( filename );
557             sink.link_();
558             sink.tableCell_();
559 
560             sink.tableCell();
561             sink.text( String.valueOf( results.getSeverityCount( violations, SeverityLevel.INFO ) ) );
562             sink.tableCell_();
563 
564             sink.tableCell();
565             sink.text( String.valueOf( results.getSeverityCount( violations, SeverityLevel.WARNING ) ) );
566             sink.tableCell_();
567 
568             sink.tableCell();
569             sink.text( String.valueOf( results.getSeverityCount( violations, SeverityLevel.ERROR ) ) );
570             sink.tableCell_();
571 
572             sink.tableRow_();
573         }
574 
575         sink.table_();
576         sink.section1_();
577     }
578 
579     private void doDetails( CheckstyleResults results )
580     {
581 
582         sink.section1();
583         sink.sectionTitle1();
584         sink.text( bundle.getString( "report.checkstyle.details" ) );
585         sink.sectionTitle1_();
586 
587         // Sort the files before writing their details to the report
588         List<String> fileList = new ArrayList<>( results.getFiles().keySet() );
589         Collections.sort( fileList );
590 
591         for ( String file : fileList )
592         {
593             List<AuditEvent> violations = results.getFileViolations( file );
594 
595             if ( violations.isEmpty() )
596             {
597                 // skip files without violations
598                 continue;
599             }
600 
601             sink.section2();
602             SinkEventAttributes attrs = new SinkEventAttributeSet();
603             attrs.addAttribute( SinkEventAttributes.ID, file.replace( '/', '.' ) );
604             sink.sectionTitle( Sink.SECTION_LEVEL_2, attrs );
605             sink.text( file );
606             sink.sectionTitle_( Sink.SECTION_LEVEL_2 );
607 
608             sink.table();
609             sink.tableRow();
610             sink.tableHeaderCell();
611             sink.text( bundle.getString( "report.checkstyle.column.severity" ) );
612             sink.tableHeaderCell_();
613             sink.tableHeaderCell();
614             sink.text( bundle.getString( "report.checkstyle.rule.category" ) );
615             sink.tableHeaderCell_();
616             sink.tableHeaderCell();
617             sink.text( bundle.getString( "report.checkstyle.rule" ) );
618             sink.tableHeaderCell_();
619             sink.tableHeaderCell();
620             sink.text( bundle.getString( "report.checkstyle.column.message" ) );
621             sink.tableHeaderCell_();
622             sink.tableHeaderCell();
623             sink.text( bundle.getString( "report.checkstyle.column.line" ) );
624             sink.tableHeaderCell_();
625             sink.tableRow_();
626 
627             doFileEvents( violations, file );
628 
629             sink.table_();
630             sink.section2_();
631         }
632 
633         sink.section1_();
634     }
635 
636     private void doFileEvents( List<AuditEvent> eventList, String filename )
637     {
638         for ( AuditEvent event : eventList )
639         {
640             SeverityLevel level = event.getSeverityLevel();
641 
642             if ( ( getSeverityLevel() != null ) && !( getSeverityLevel() != level ) )
643             {
644                 continue;
645             }
646 
647             sink.tableRow();
648 
649             sink.tableCell();
650             iconTool.iconSeverity( level.getName(), IconTool.TEXT_SIMPLE );
651             sink.tableCell_();
652 
653             sink.tableCell();
654             String category = RuleUtil.getCategory( event );
655             if ( category != null )
656             {
657                 sink.text( category );
658             }
659             sink.tableCell_();
660 
661             sink.tableCell();
662             String ruleName = RuleUtil.getName( event );
663             if ( ruleName != null )
664             {
665                 sink.text( ruleName );
666             }
667             sink.tableCell_();
668 
669             sink.tableCell();
670             sink.text( event.getMessage() );
671             sink.tableCell_();
672 
673             sink.tableCell();
674 
675             int line = event.getLine();
676             if ( getXrefLocation() != null && line != 0 )
677             {
678                 sink.link( getXrefLocation() + "/" + filename.replaceAll( "\\.java$", ".html" ) + "#L"
679                     + line );
680                 sink.text( String.valueOf( line ) );
681                 sink.link_();
682             }
683             else if ( line != 0 )
684             {
685                 sink.text( String.valueOf( line ) );
686             }
687             sink.tableCell_();
688 
689             sink.tableRow_();
690         }
691     }
692 
693     public SeverityLevel getSeverityLevel()
694     {
695         return severityLevel;
696     }
697 
698     public void setSeverityLevel( SeverityLevel severityLevel )
699     {
700         this.severityLevel = severityLevel;
701     }
702 
703     public boolean isEnableRulesSummary()
704     {
705         return enableRulesSummary;
706     }
707 
708     public void setEnableRulesSummary( boolean enableRulesSummary )
709     {
710         this.enableRulesSummary = enableRulesSummary;
711     }
712 
713     public boolean isEnableSeveritySummary()
714     {
715         return enableSeveritySummary;
716     }
717 
718     public void setEnableSeveritySummary( boolean enableSeveritySummary )
719     {
720         this.enableSeveritySummary = enableSeveritySummary;
721     }
722 
723     public boolean isEnableFilesSummary()
724     {
725         return enableFilesSummary;
726     }
727 
728     public void setEnableFilesSummary( boolean enableFilesSummary )
729     {
730         this.enableFilesSummary = enableFilesSummary;
731     }
732 
733     public boolean isEnableRSS()
734     {
735         return enableRSS;
736     }
737 
738     public void setEnableRSS( boolean enableRSS )
739     {
740         this.enableRSS = enableRSS;
741     }
742 
743     public String getXrefLocation()
744     {
745         return xrefLocation;
746     }
747 
748     public void setXrefLocation( String xrefLocation )
749     {
750         this.xrefLocation = xrefLocation;
751     }
752 
753     public Configuration getCheckstyleConfig()
754     {
755         return checkstyleConfig;
756     }
757 
758     public void setCheckstyleConfig( Configuration config )
759     {
760         this.checkstyleConfig = config;
761     }
762 
763     public void setTreeWalkerNames( List<String> treeWalkerNames )
764     {
765         this.treeWalkerNames = treeWalkerNames;
766     }
767 
768     public List<String> getTreeWalkerNames()
769     {
770         return treeWalkerNames;
771     }
772 
773     /**
774      * Get the effective Checkstyle version at runtime.
775      * @return the MANIFEST implementation version of Checkstyle API package (can be <code>null</code>)
776      */
777     private String getCheckstyleVersion()
778     {
779         Package checkstyleApiPackage = Configuration.class.getPackage();
780 
781         return ( checkstyleApiPackage == null ) ? null : checkstyleApiPackage.getImplementationVersion();
782     }
783 
784     public List<ConfReference> sortConfiguration( CheckstyleResults results )
785     {
786         List<ConfReference> result = new ArrayList<>();
787 
788         sortConfiguration( result, checkstyleConfig, null, results );
789 
790         Collections.sort( result );
791 
792         return result;
793     }
794 
795     private void sortConfiguration( List<ConfReference> result, Configuration config,
796                                     ChainedItem<Configuration> parent, CheckstyleResults results )
797     {
798         for ( Configuration childConfig : config.getChildren() )
799         {
800             String ruleName = childConfig.getName();
801 
802             if ( treeWalkerNames.contains( ruleName ) )
803             {
804                 // special sub-case: TreeWalker is the parent of multiple rules, not an effective rule
805                 sortConfiguration( result, childConfig, new ChainedItem<>( config, parent ), results );
806             }
807             else
808             {
809                 String fixedmessage = getConfigAttribute( childConfig, null, "message", null );
810                 // Grab the severity from the rule configuration. Do not set default value here as
811                 // it breaks our rule aggregate section entirely.  The counts are off but this is
812                 // not appropriate fix location per MCHECKSTYLE-365.
813                 String configSeverity = getConfigAttribute( childConfig, null, "severity", null );
814 
815                 // count rule violations
816                 long violations = 0;
817                 AuditEvent lastMatchedEvent = null;
818                 for ( List<AuditEvent> errors : results.getFiles().values() )
819                 {
820                     for ( AuditEvent event : errors )
821                     {
822                         if ( matchRule( event, ruleName, fixedmessage, configSeverity ) )
823                         {
824                             lastMatchedEvent = event;
825                             violations++;
826                         }
827                     }
828                 }
829 
830                 if ( violations > 0 ) // forget rules without violations
831                 {
832                     String category = RuleUtil.getCategory( lastMatchedEvent );
833 
834                     result.add( new ConfReference( category, childConfig, parent, violations, result.size() ) );
835                 }
836             }
837         }
838     }
839 
840     private static class ConfReference
841         implements Comparable<ConfReference>
842     {
843         private final String category;
844         private final Configuration configuration;
845         private final ChainedItem<Configuration> parentConfiguration;
846         private final long violations;
847         private final int count;
848 
849         ConfReference( String category, Configuration configuration,
850                               ChainedItem<Configuration> parentConfiguration, long violations, int count )
851         {
852             this.category = category;
853             this.configuration = configuration;
854             this.parentConfiguration = parentConfiguration;
855             this.violations = violations;
856             this.count = count;
857         }
858 
859         public int compareTo( ConfReference o )
860         {
861             int compare = category.compareTo( o.category );
862             if ( compare == 0 )
863             {
864                 compare = configuration.getName().compareTo( o.configuration.getName() );
865             }
866             return ( compare == 0 ) ? ( o.count - count ) : compare;
867         }
868     }
869 
870     private static class ChainedItem<T>
871     {
872         private final ChainedItem<T> parent;
873 
874         private final T value;
875 
876         ChainedItem( T value, ChainedItem<T> parent )
877         {
878             this.parent = parent;
879             this.value = value;
880         }
881     }
882 
883 }