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
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
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
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
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
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
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 }