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