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: 5773 $, $Date: 2008-02-14 01:06:49 -0800 (Thu, 14 Feb 2008) $
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
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
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
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
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
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
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 }