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