View Javadoc

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