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