1
2
3
4 package net.sourceforge.pmd.lang.java.rule.codesize;
5
6 import java.util.List;
7 import java.util.Stack;
8
9 import net.sourceforge.pmd.lang.ast.Node;
10 import net.sourceforge.pmd.lang.java.ast.ASTAnnotation;
11 import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
12 import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement;
13 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBodyDeclaration;
14 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
15 import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
16 import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression;
17 import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
18 import net.sourceforge.pmd.lang.java.ast.ASTDoStatement;
19 import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
20 import net.sourceforge.pmd.lang.java.ast.ASTExpression;
21 import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
22 import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
23 import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
24 import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
25 import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator;
26 import net.sourceforge.pmd.lang.java.ast.ASTName;
27 import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
28 import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
29 import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement;
30 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
31 import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
32 import net.sourceforge.pmd.lang.rule.properties.IntegerProperty;
33
34
35
36
37
38
39
40 public class CyclomaticComplexityRule extends AbstractJavaRule {
41
42 public static final IntegerProperty REPORT_LEVEL_DESCRIPTOR = new IntegerProperty("reportLevel",
43 "Cyclomatic Complexity reporting threshold", 1, 30, 10, 1.0f);
44
45 public static final BooleanProperty SHOW_CLASSES_COMPLEXITY_DESCRIPTOR = new BooleanProperty("showClassesComplexity",
46 "Add class average violations to the report", true, 2.0f);
47
48 public static final BooleanProperty SHOW_METHODS_COMPLEXITY_DESCRIPTOR = new BooleanProperty("showMethodsComplexity",
49 "Add method average violations to the report", true, 3.0f);
50
51 private int reportLevel;
52 private boolean showClassesComplexity = true;
53 private boolean showMethodsComplexity = true;
54
55 private static class Entry {
56 private Node node;
57 private int decisionPoints = 1;
58 public int highestDecisionPoints;
59 public int methodCount;
60
61 private Entry(Node node) {
62 this.node = node;
63 }
64
65 public void bumpDecisionPoints() {
66 decisionPoints++;
67 }
68
69 public void bumpDecisionPoints(int size) {
70 decisionPoints += size;
71 }
72
73 public int getComplexityAverage() {
74 return (double) methodCount == 0 ? 1
75 : (int) Math.rint( (double) decisionPoints / (double) methodCount );
76 }
77 }
78
79 private Stack<Entry> entryStack = new Stack<Entry>();
80
81 public CyclomaticComplexityRule() {
82 definePropertyDescriptor(REPORT_LEVEL_DESCRIPTOR);
83 definePropertyDescriptor(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR);
84 definePropertyDescriptor(SHOW_METHODS_COMPLEXITY_DESCRIPTOR);
85 }
86
87 @Override
88 public Object visit(ASTCompilationUnit node, Object data) {
89 reportLevel = getProperty(REPORT_LEVEL_DESCRIPTOR);
90 showClassesComplexity = getProperty(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR);
91 showMethodsComplexity = getProperty(SHOW_METHODS_COMPLEXITY_DESCRIPTOR);
92 super.visit( node, data );
93 return data;
94 }
95
96 @Override
97 public Object visit(ASTIfStatement node, Object data) {
98 int boolCompIf = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
99
100 boolCompIf++;
101
102 entryStack.peek().bumpDecisionPoints( boolCompIf );
103 super.visit( node, data );
104 return data;
105 }
106
107 @Override
108 public Object visit(ASTCatchStatement node, Object data) {
109 entryStack.peek().bumpDecisionPoints();
110 super.visit( node, data );
111 return data;
112 }
113
114 @Override
115 public Object visit(ASTForStatement node, Object data) {
116 int boolCompFor = NPathComplexityRule.sumExpressionComplexity( node.getFirstDescendantOfType( ASTExpression.class ) );
117
118 boolCompFor++;
119
120 entryStack.peek().bumpDecisionPoints( boolCompFor );
121 super.visit( node, data );
122 return data;
123 }
124
125 @Override
126 public Object visit(ASTDoStatement node, Object data) {
127 int boolCompDo = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
128
129 boolCompDo++;
130
131 entryStack.peek().bumpDecisionPoints( boolCompDo );
132 super.visit( node, data );
133 return data;
134 }
135
136 @Override
137 public Object visit(ASTSwitchStatement node, Object data) {
138 Entry entry = entryStack.peek();
139
140 int boolCompSwitch = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
141 entry.bumpDecisionPoints( boolCompSwitch );
142
143 int childCount = node.jjtGetNumChildren();
144 int lastIndex = childCount - 1;
145 for ( int n = 0; n < lastIndex; n++ ) {
146 Node childNode = node.jjtGetChild( n );
147 if ( childNode instanceof ASTSwitchLabel ) {
148
149 ASTSwitchLabel sl = (ASTSwitchLabel) childNode;
150 if ( !sl.isDefault() ) {
151 childNode = node.jjtGetChild( n + 1 );
152 if ( childNode instanceof ASTBlockStatement ) {
153 entry.bumpDecisionPoints();
154 }
155 }
156 }
157 }
158 super.visit( node, data );
159 return data;
160 }
161
162 @Override
163 public Object visit(ASTWhileStatement node, Object data) {
164 int boolCompWhile = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
165
166 boolCompWhile++;
167
168 entryStack.peek().bumpDecisionPoints( boolCompWhile );
169 super.visit( node, data );
170 return data;
171 }
172
173 @Override
174 public Object visit(ASTConditionalExpression node, Object data) {
175 if ( node.isTernary() ) {
176 int boolCompTern = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
177
178 boolCompTern++;
179
180 entryStack.peek().bumpDecisionPoints( boolCompTern );
181 super.visit( node, data );
182 }
183 return data;
184 }
185
186 @Override
187 public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
188 if ( node.isInterface() ) {
189 return data;
190 }
191
192 entryStack.push( new Entry( node ) );
193 super.visit( node, data );
194 if ( showClassesComplexity ) {
195 Entry classEntry = entryStack.pop();
196 if ( classEntry.getComplexityAverage() >= reportLevel
197 || classEntry.highestDecisionPoints >= reportLevel ) {
198 addViolation( data, node, new String[] {
199 "class",
200 node.getImage(),
201 classEntry.getComplexityAverage() + " (Highest = "
202 + classEntry.highestDecisionPoints + ')' } );
203 }
204 }
205 return data;
206 }
207
208 @Override
209 public Object visit(ASTMethodDeclaration node, Object data) {
210 entryStack.push( new Entry( node ) );
211 super.visit( node, data );
212 Entry methodEntry = entryStack.pop();
213 if (!isSuppressed(node)) {
214 int methodDecisionPoints = methodEntry.decisionPoints;
215 Entry classEntry = entryStack.peek();
216 classEntry.methodCount++;
217 classEntry.bumpDecisionPoints( methodDecisionPoints );
218
219 if ( methodDecisionPoints > classEntry.highestDecisionPoints ) {
220 classEntry.highestDecisionPoints = methodDecisionPoints;
221 }
222
223 ASTMethodDeclarator methodDeclarator = null;
224 for ( int n = 0; n < node.jjtGetNumChildren(); n++ ) {
225 Node childNode = node.jjtGetChild( n );
226 if ( childNode instanceof ASTMethodDeclarator ) {
227 methodDeclarator = (ASTMethodDeclarator) childNode;
228 break;
229 }
230 }
231
232 if ( showMethodsComplexity && methodEntry.decisionPoints >= reportLevel ) {
233 addViolation( data, node, new String[] { "method",
234 methodDeclarator == null ? "" : methodDeclarator.getImage(),
235 String.valueOf( methodEntry.decisionPoints ) } );
236 }
237 }
238 return data;
239 }
240
241 @Override
242 public Object visit(ASTEnumDeclaration node, Object data) {
243 entryStack.push( new Entry( node ) );
244 super.visit( node, data );
245 Entry classEntry = entryStack.pop();
246 if ( classEntry.getComplexityAverage() >= reportLevel
247 || classEntry.highestDecisionPoints >= reportLevel ) {
248 addViolation( data, node, new String[] {
249 "class",
250 node.getImage(),
251 classEntry.getComplexityAverage() + "(Highest = "
252 + classEntry.highestDecisionPoints + ')' } );
253 }
254 return data;
255 }
256
257 @Override
258 public Object visit(ASTConstructorDeclaration node, Object data) {
259 entryStack.push( new Entry( node ) );
260 super.visit( node, data );
261 Entry constructorEntry = entryStack.pop();
262 if (!isSuppressed(node)) {
263 int constructorDecisionPointCount = constructorEntry.decisionPoints;
264 Entry classEntry = entryStack.peek();
265 classEntry.methodCount++;
266 classEntry.decisionPoints += constructorDecisionPointCount;
267 if ( constructorDecisionPointCount > classEntry.highestDecisionPoints ) {
268 classEntry.highestDecisionPoints = constructorDecisionPointCount;
269 }
270 if ( showMethodsComplexity && constructorEntry.decisionPoints >= reportLevel ) {
271 addViolation( data, node, new String[] { "constructor",
272 classEntry.node.getImage(),
273 String.valueOf( constructorDecisionPointCount ) } );
274 }
275 }
276 return data;
277 }
278
279 private boolean isSuppressed(Node node) {
280 boolean result = false;
281
282 ASTClassOrInterfaceBodyDeclaration parent = node.getFirstParentOfType(ASTClassOrInterfaceBodyDeclaration.class);
283 List<ASTAnnotation> annotations = parent.findChildrenOfType(ASTAnnotation.class);
284 for (ASTAnnotation a : annotations) {
285 ASTName name = a.getFirstDescendantOfType(ASTName.class);
286 if ("SuppressWarnings".equals(name.getImage())) {
287 List<ASTLiteral> literals = a.findDescendantsOfType(ASTLiteral.class);
288 for (ASTLiteral l : literals) {
289 if (l.isStringLiteral() && "\"PMD.CyclomaticComplexity\"".equals(l.getImage())) {
290 result = true;
291 break;
292 }
293 }
294 }
295 if (result) {
296 break;
297 }
298 }
299
300 return result;
301 }
302 }