View Javadoc
1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.java.rule.codesize;
5   
6   import java.util.Stack;
7   
8   import net.sourceforge.pmd.lang.ast.Node;
9   import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
10  import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement;
11  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
12  import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
13  import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression;
14  import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
15  import net.sourceforge.pmd.lang.java.ast.ASTDoStatement;
16  import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
17  import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
18  import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
19  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
20  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator;
21  import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
22  import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
23  import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement;
24  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
25  import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
26  import net.sourceforge.pmd.lang.rule.properties.IntegerProperty;
27  
28  /**
29   * Implements the standard cyclomatic complexity rule 
30   * <p>
31   * Standard rules: +1 for each decision point, including case statements
32   * but not including boolean operators unlike CyclomaticComplexityRule.
33   * 
34   * @author Alan Hohn, based on work by Donald A. Leckie
35   * 
36   * @since June 18, 2014
37   */
38  public class StdCyclomaticComplexityRule extends AbstractJavaRule {
39  
40      public static final IntegerProperty REPORT_LEVEL_DESCRIPTOR = new IntegerProperty("reportLevel",
41  	    "Cyclomatic Complexity reporting threshold", 1, 30, 10, 1.0f);
42  
43      public static final BooleanProperty SHOW_CLASSES_COMPLEXITY_DESCRIPTOR = new BooleanProperty("showClassesComplexity",
44  	"Add class average violations to the report", true, 2.0f);
45  
46      public static final BooleanProperty SHOW_METHODS_COMPLEXITY_DESCRIPTOR = new BooleanProperty("showMethodsComplexity",
47  	"Add method average violations to the report", true, 3.0f);
48  
49    private int reportLevel;
50    private boolean showClassesComplexity = true;
51    private boolean showMethodsComplexity = true;
52  
53    protected static class Entry {
54      private Node node;
55      private int decisionPoints = 1;
56      public int highestDecisionPoints;
57      public int methodCount;
58  
59      private Entry(Node node) {
60        this.node = node;
61      }
62  
63      public void bumpDecisionPoints() {
64        decisionPoints++;
65      }
66  
67      public void bumpDecisionPoints(int size) {
68        decisionPoints += size;
69      }
70  
71      public int getComplexityAverage() {
72        return (double) methodCount == 0 ? 1
73            : (int) Math.rint( (double) decisionPoints / (double) methodCount );
74      }
75    }
76  
77    protected Stack<Entry> entryStack = new Stack<>();
78  
79    public StdCyclomaticComplexityRule() {
80        definePropertyDescriptor(REPORT_LEVEL_DESCRIPTOR);
81        definePropertyDescriptor(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR);
82        definePropertyDescriptor(SHOW_METHODS_COMPLEXITY_DESCRIPTOR);
83    }
84  
85    @Override
86  public Object visit(ASTCompilationUnit node, Object data) {
87      reportLevel = getProperty(REPORT_LEVEL_DESCRIPTOR);
88      showClassesComplexity = getProperty(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR);
89      showMethodsComplexity = getProperty(SHOW_METHODS_COMPLEXITY_DESCRIPTOR);
90      super.visit( node, data );
91      return data;
92    }
93  
94    @Override
95  public Object visit(ASTIfStatement node, Object data) {
96      entryStack.peek().bumpDecisionPoints();
97      super.visit( node, data );
98      return data;
99    }
100 
101   @Override
102 public Object visit(ASTCatchStatement node, Object data) {
103     entryStack.peek().bumpDecisionPoints();
104     super.visit( node, data );
105     return data;
106   }
107 
108   @Override
109 public Object visit(ASTForStatement node, Object data) {
110     entryStack.peek().bumpDecisionPoints();
111     super.visit( node, data );
112     return data;
113   }
114 
115   @Override
116 public Object visit(ASTDoStatement node, Object data) {
117     entryStack.peek().bumpDecisionPoints();
118     super.visit( node, data );
119     return data;
120   }
121 
122   @Override
123 public Object visit(ASTSwitchStatement node, Object data) {
124     Entry entry = entryStack.peek();
125 
126     int childCount = node.jjtGetNumChildren();
127     int lastIndex = childCount - 1;
128     for ( int n = 0; n < lastIndex; n++ ) {
129       Node childNode = node.jjtGetChild( n );
130       if ( childNode instanceof ASTSwitchLabel ) {
131         // default is generally not considered a decision (same as "else")
132         ASTSwitchLabel sl = (ASTSwitchLabel) childNode;
133         if ( !sl.isDefault() ) {
134           childNode = node.jjtGetChild( n + 1 );
135           if ( childNode instanceof ASTBlockStatement ) {
136             entry.bumpDecisionPoints();
137           }
138         }
139       }
140     }
141     super.visit( node, data );
142     return data;
143   }
144 
145   @Override
146 public Object visit(ASTWhileStatement node, Object data) {
147     entryStack.peek().bumpDecisionPoints();
148     super.visit( node, data );
149     return data;
150   }
151 
152   @Override
153 public Object visit(ASTConditionalExpression node, Object data) {
154     if ( node.isTernary() ) {
155       entryStack.peek().bumpDecisionPoints();
156       super.visit( node, data );
157     }
158     return data;
159   }
160 
161   @Override
162 public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
163     if ( node.isInterface() ) {
164       return data;
165     }
166 
167     entryStack.push( new Entry( node ) );
168     super.visit( node, data );
169     if ( showClassesComplexity ) {
170     	Entry classEntry = entryStack.pop();
171 	    if ( classEntry.getComplexityAverage() >= reportLevel
172 	        || classEntry.highestDecisionPoints >= reportLevel ) {
173 	      addViolation( data, node, new String[] {
174 	          "class",
175 	          node.getImage(),
176 	          classEntry.getComplexityAverage() + " (Highest = "
177 	              + classEntry.highestDecisionPoints + ')' } );
178 	    }
179     }
180     return data;
181   }
182 
183   @Override
184 public Object visit(ASTMethodDeclaration node, Object data) {
185     entryStack.push( new Entry( node ) );
186     super.visit( node, data );
187 	    Entry methodEntry = entryStack.pop();
188     if (!isSuppressed(node)) {
189 	    int methodDecisionPoints = methodEntry.decisionPoints;
190 	    Entry classEntry = entryStack.peek();
191 	    classEntry.methodCount++;
192 	    classEntry.bumpDecisionPoints( methodDecisionPoints );
193 
194 	    if ( methodDecisionPoints > classEntry.highestDecisionPoints ) {
195 	      classEntry.highestDecisionPoints = methodDecisionPoints;
196 	    }
197 
198 	    ASTMethodDeclarator methodDeclarator = null;
199 	    for ( int n = 0; n < node.jjtGetNumChildren(); n++ ) {
200 	      Node childNode = node.jjtGetChild( n );
201 	      if ( childNode instanceof ASTMethodDeclarator ) {
202 	        methodDeclarator = (ASTMethodDeclarator) childNode;
203 	        break;
204 	      }
205 	    }
206 
207 	    if ( showMethodsComplexity && methodEntry.decisionPoints >= reportLevel ) {
208 	        addViolation( data, node, new String[] { "method",
209 	            methodDeclarator == null ? "" : methodDeclarator.getImage(),
210 	            String.valueOf( methodEntry.decisionPoints ) } );
211 	      }
212     }
213     return data;
214   }
215 
216   @Override
217 public Object visit(ASTEnumDeclaration node, Object data) {
218     entryStack.push( new Entry( node ) );
219     super.visit( node, data );
220     Entry classEntry = entryStack.pop();
221     if ( classEntry.getComplexityAverage() >= reportLevel
222         || classEntry.highestDecisionPoints >= reportLevel ) {
223       addViolation( data, node, new String[] {
224           "class",
225           node.getImage(),
226           classEntry.getComplexityAverage() + "(Highest = "
227               + classEntry.highestDecisionPoints + ')' } );
228     }
229     return data;
230   }
231 
232   @Override
233 public Object visit(ASTConstructorDeclaration node, Object data) {
234     entryStack.push( new Entry( node ) );
235     super.visit( node, data );
236     Entry constructorEntry = entryStack.pop();
237     if (!isSuppressed(node)) {
238     int constructorDecisionPointCount = constructorEntry.decisionPoints;
239     Entry classEntry = entryStack.peek();
240     classEntry.methodCount++;
241     classEntry.decisionPoints += constructorDecisionPointCount;
242     if ( constructorDecisionPointCount > classEntry.highestDecisionPoints ) {
243       classEntry.highestDecisionPoints = constructorDecisionPointCount;
244     }
245     if ( showMethodsComplexity && constructorEntry.decisionPoints >= reportLevel ) {
246       addViolation( data, node, new String[] { "constructor",
247           classEntry.node.getImage(),
248           String.valueOf( constructorDecisionPointCount ) } );
249     }
250     }
251     return data;
252   }
253 }