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.ASTExpression;
18  import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
19  import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
20  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
21  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator;
22  import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
23  import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
24  import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement;
25  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
26  import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
27  import net.sourceforge.pmd.lang.rule.properties.IntegerProperty;
28  
29  /**
30   * @author Donald A. Leckie,
31   *
32   * @version $Revision: 5956 $, $Date: 2008-04-04 04:59:25 -0500 (Fri, 04 Apr 2008) $
33   * @since January 14, 2003
34   */
35  public class CyclomaticComplexityRule extends AbstractJavaRule {
36  
37      public static final IntegerProperty REPORT_LEVEL_DESCRIPTOR = new IntegerProperty("reportLevel",
38  	    "Cyclomatic Complexity reporting threshold", 1, 30, 10, 1.0f);
39  
40      public static final BooleanProperty SHOW_CLASSES_COMPLEXITY_DESCRIPTOR = new BooleanProperty("showClassesComplexity",
41  	"Add class average violations to the report", true, 2.0f);
42  
43      public static final BooleanProperty SHOW_METHODS_COMPLEXITY_DESCRIPTOR = new BooleanProperty("showMethodsComplexity",
44  	"Add method average violations to the report", true, 3.0f);
45  
46    private int reportLevel;
47    private boolean showClassesComplexity = true;
48    private boolean showMethodsComplexity = true;
49  
50    private static class Entry {
51      private Node node;
52      private int decisionPoints = 1;
53      public int highestDecisionPoints;
54      public int methodCount;
55  
56      private Entry(Node node) {
57        this.node = node;
58      }
59  
60      public void bumpDecisionPoints() {
61        decisionPoints++;
62      }
63  
64      public void bumpDecisionPoints(int size) {
65        decisionPoints += size;
66      }
67  
68      public int getComplexityAverage() {
69        return (double) methodCount == 0 ? 1
70            : (int) Math.rint( (double) decisionPoints / (double) methodCount );
71      }
72    }
73  
74    private Stack<Entry> entryStack = new Stack<Entry>();
75  
76    public CyclomaticComplexityRule() {
77        definePropertyDescriptor(REPORT_LEVEL_DESCRIPTOR);
78        definePropertyDescriptor(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR);
79        definePropertyDescriptor(SHOW_METHODS_COMPLEXITY_DESCRIPTOR);
80    }
81  
82    @Override
83  public Object visit(ASTCompilationUnit node, Object data) {
84      reportLevel = getProperty(REPORT_LEVEL_DESCRIPTOR);
85      showClassesComplexity = getProperty(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR);
86      showMethodsComplexity = getProperty(SHOW_METHODS_COMPLEXITY_DESCRIPTOR);
87      super.visit( node, data );
88      return data;
89    }
90  
91    @Override
92  public Object visit(ASTIfStatement node, Object data) {
93      int boolCompIf = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
94      // If statement always has a complexity of at least 1
95      boolCompIf++;
96  
97      entryStack.peek().bumpDecisionPoints( boolCompIf );
98      super.visit( node, data );
99      return data;
100   }
101 
102   @Override
103 public Object visit(ASTCatchStatement node, Object data) {
104     entryStack.peek().bumpDecisionPoints();
105     super.visit( node, data );
106     return data;
107   }
108 
109   @Override
110 public Object visit(ASTForStatement node, Object data) {
111     int boolCompFor = NPathComplexityRule.sumExpressionComplexity( node.getFirstDescendantOfType( ASTExpression.class ) );
112     // For statement always has a complexity of at least 1
113     boolCompFor++;
114 
115     entryStack.peek().bumpDecisionPoints( boolCompFor );
116     super.visit( node, data );
117     return data;
118   }
119 
120   @Override
121 public Object visit(ASTDoStatement node, Object data) {
122     int boolCompDo = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
123     // Do statement always has a complexity of at least 1
124     boolCompDo++;
125 
126     entryStack.peek().bumpDecisionPoints( boolCompDo );
127     super.visit( node, data );
128     return data;
129   }
130 
131   @Override
132 public Object visit(ASTSwitchStatement node, Object data) {
133     Entry entry = entryStack.peek();
134 
135     int boolCompSwitch = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
136     entry.bumpDecisionPoints( boolCompSwitch );
137 
138     int childCount = node.jjtGetNumChildren();
139     int lastIndex = childCount - 1;
140     for ( int n = 0; n < lastIndex; n++ ) {
141       Node childNode = node.jjtGetChild( n );
142       if ( childNode instanceof ASTSwitchLabel ) {
143         // default is generally not considered a decision (same as "else")
144         ASTSwitchLabel sl = (ASTSwitchLabel) childNode;
145         if ( !sl.isDefault() ) {
146           childNode = node.jjtGetChild( n + 1 );
147           if ( childNode instanceof ASTBlockStatement ) {
148             entry.bumpDecisionPoints();
149           }
150         }
151       }
152     }
153     super.visit( node, data );
154     return data;
155   }
156 
157   @Override
158 public Object visit(ASTWhileStatement node, Object data) {
159     int boolCompWhile = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
160     // While statement always has a complexity of at least 1
161     boolCompWhile++;
162 
163     entryStack.peek().bumpDecisionPoints( boolCompWhile );
164     super.visit( node, data );
165     return data;
166   }
167 
168   @Override
169 public Object visit(ASTConditionalExpression node, Object data) {
170     if ( node.isTernary() ) {
171       int boolCompTern = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
172       // Ternary statement always has a complexity of at least 1
173       boolCompTern++;
174 
175       entryStack.peek().bumpDecisionPoints( boolCompTern );
176       super.visit( node, data );
177     }
178     return data;
179   }
180 
181   @Override
182 public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
183     if ( node.isInterface() ) {
184       return data;
185     }
186 
187     entryStack.push( new Entry( node ) );
188     super.visit( node, data );
189     if ( showClassesComplexity ) {
190     	Entry classEntry = entryStack.pop();
191 	    if ( classEntry.getComplexityAverage() >= reportLevel
192 	        || classEntry.highestDecisionPoints >= reportLevel ) {
193 	      addViolation( data, node, new String[] {
194 	          "class",
195 	          node.getImage(),
196 	          classEntry.getComplexityAverage() + " (Highest = "
197 	              + classEntry.highestDecisionPoints + ')' } );
198 	    }
199     }
200     return data;
201   }
202 
203   @Override
204 public Object visit(ASTMethodDeclaration node, Object data) {
205     entryStack.push( new Entry( node ) );
206     super.visit( node, data );
207     if ( showMethodsComplexity ) {
208 	    Entry methodEntry = entryStack.pop();
209 	    int methodDecisionPoints = methodEntry.decisionPoints;
210 	    Entry classEntry = entryStack.peek();
211 	    classEntry.methodCount++;
212 	    classEntry.bumpDecisionPoints( methodDecisionPoints );
213 
214 	    if ( methodDecisionPoints > classEntry.highestDecisionPoints ) {
215 	      classEntry.highestDecisionPoints = methodDecisionPoints;
216 	    }
217 
218 	    ASTMethodDeclarator methodDeclarator = null;
219 	    for ( int n = 0; n < node.jjtGetNumChildren(); n++ ) {
220 	      Node childNode = node.jjtGetChild( n );
221 	      if ( childNode instanceof ASTMethodDeclarator ) {
222 	        methodDeclarator = (ASTMethodDeclarator) childNode;
223 	        break;
224 	      }
225 	    }
226 
227 	    if ( methodEntry.decisionPoints >= reportLevel ) {
228 	        addViolation( data, node, new String[] { "method",
229 	            methodDeclarator == null ? "" : methodDeclarator.getImage(),
230 	            String.valueOf( methodEntry.decisionPoints ) } );
231 	      }
232     }
233     return data;
234   }
235 
236   @Override
237 public Object visit(ASTEnumDeclaration node, Object data) {
238     entryStack.push( new Entry( node ) );
239     super.visit( node, data );
240     Entry classEntry = entryStack.pop();
241     if ( classEntry.getComplexityAverage() >= reportLevel
242         || classEntry.highestDecisionPoints >= reportLevel ) {
243       addViolation( data, node, new String[] {
244           "class",
245           node.getImage(),
246           classEntry.getComplexityAverage() + "(Highest = "
247               + classEntry.highestDecisionPoints + ')' } );
248     }
249     return data;
250   }
251 
252   @Override
253 public Object visit(ASTConstructorDeclaration node, Object data) {
254     entryStack.push( new Entry( node ) );
255     super.visit( node, data );
256     Entry constructorEntry = entryStack.pop();
257     int constructorDecisionPointCount = constructorEntry.decisionPoints;
258     Entry classEntry = entryStack.peek();
259     classEntry.methodCount++;
260     classEntry.decisionPoints += constructorDecisionPointCount;
261     if ( constructorDecisionPointCount > classEntry.highestDecisionPoints ) {
262       classEntry.highestDecisionPoints = constructorDecisionPointCount;
263     }
264     if ( constructorEntry.decisionPoints >= reportLevel ) {
265       addViolation( data, node, new String[] { "constructor",
266           classEntry.node.getImage(),
267           String.valueOf( constructorDecisionPointCount ) } );
268     }
269     return data;
270   }
271 
272 }