1
2
3
4 package net.sourceforge.pmd.lang.plsql.rule.codesize;
5
6 import java.util.Stack;
7 import java.util.logging.Logger;
8
9 import net.sourceforge.pmd.lang.ast.Node;
10 import net.sourceforge.pmd.lang.plsql.ast.ASTExceptionHandler;
11 import net.sourceforge.pmd.lang.plsql.ast.ASTPackageSpecification;
12 import net.sourceforge.pmd.lang.plsql.ast.ASTPackageBody;
13 import net.sourceforge.pmd.lang.plsql.ast.ASTTypeSpecification;
14 import net.sourceforge.pmd.lang.plsql.ast.ASTInput;
15 import net.sourceforge.pmd.lang.plsql.ast.ASTConditionalOrExpression;
16 import net.sourceforge.pmd.lang.plsql.ast.ASTLoopStatement;
17 import net.sourceforge.pmd.lang.plsql.ast.ASTExpression;
18 import net.sourceforge.pmd.lang.plsql.ast.ASTForStatement;
19 import net.sourceforge.pmd.lang.plsql.ast.ASTIfStatement;
20 import net.sourceforge.pmd.lang.plsql.ast.ASTElsifClause;
21 import net.sourceforge.pmd.lang.plsql.ast.ASTMethodDeclarator;
22 import net.sourceforge.pmd.lang.plsql.ast.ASTProgramUnit;
23 import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerUnit;
24 import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerTimingPointSection;
25 import net.sourceforge.pmd.lang.plsql.ast.ASTCaseStatement;
26 import net.sourceforge.pmd.lang.plsql.ast.ASTCaseWhenClause;
27 import net.sourceforge.pmd.lang.plsql.ast.ASTTypeMethod;
28 import net.sourceforge.pmd.lang.plsql.ast.ASTWhileStatement;
29 import net.sourceforge.pmd.lang.plsql.rule.AbstractPLSQLRule;
30
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 AbstractPLSQLRule {
41 private final static Logger LOGGER = Logger.getLogger(CyclomaticComplexityRule.class.getName());
42 private final static String CLASS_NAME =CyclomaticComplexityRule.class.getName();
43
44 public static final IntegerProperty REPORT_LEVEL_DESCRIPTOR = new IntegerProperty("reportLevel",
45 "Cyclomatic Complexity reporting threshold", 1, 30, 10, 1.0f);
46
47 public static final BooleanProperty SHOW_CLASSES_COMPLEXITY_DESCRIPTOR = new BooleanProperty("showClassesComplexity",
48 "Add class average violations to the report", true, 2.0f);
49
50 public static final BooleanProperty SHOW_METHODS_COMPLEXITY_DESCRIPTOR = new BooleanProperty("showMethodsComplexity",
51 "Add method average violations to the report", true, 3.0f);
52
53 private int reportLevel;
54 private boolean showClassesComplexity = true;
55 private boolean showMethodsComplexity = true;
56
57 private static class Entry {
58 private Node node;
59 private int decisionPoints = 1;
60 public int highestDecisionPoints;
61 public int methodCount;
62
63 private Entry(Node node) {
64 this.node = node;
65 }
66
67 public void bumpDecisionPoints() {
68 decisionPoints++;
69 }
70
71 public void bumpDecisionPoints(int size) {
72 decisionPoints += size;
73 }
74
75 public int getComplexityAverage() {
76 return (double) methodCount == 0 ? 1
77 : (int) Math.rint( (double) decisionPoints / (double) methodCount );
78 }
79 }
80
81 private Stack<Entry> entryStack = new Stack<Entry>();
82
83 public CyclomaticComplexityRule() {
84 definePropertyDescriptor(REPORT_LEVEL_DESCRIPTOR);
85 definePropertyDescriptor(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR);
86 definePropertyDescriptor(SHOW_METHODS_COMPLEXITY_DESCRIPTOR);
87 }
88
89 @Override
90 public Object visit(ASTInput node, Object data) {
91 LOGGER.entering(CLASS_NAME,"visit(ASTInput)");
92 reportLevel = getProperty(REPORT_LEVEL_DESCRIPTOR);
93 showClassesComplexity = getProperty(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR);
94 showMethodsComplexity = getProperty(SHOW_METHODS_COMPLEXITY_DESCRIPTOR);
95 super.visit( node, data );
96 LOGGER.exiting(CLASS_NAME,"visit(ASTInput)");
97 return data;
98 }
99
100
101 @Override
102 public Object visit(ASTElsifClause node, Object data) {
103 LOGGER.entering(CLASS_NAME,"visit(ASTElsifClause)");
104 int boolCompIf = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
105
106 boolCompIf++;
107
108 entryStack.peek().bumpDecisionPoints( boolCompIf );
109 super.visit( node, data );
110 LOGGER.exiting(CLASS_NAME,"visit(ASTElsifClause)");
111 return data;
112 }
113
114 @Override
115 public Object visit(ASTIfStatement node, Object data) {
116 LOGGER.entering(CLASS_NAME,"visit(ASTIfClause)");
117 int boolCompIf = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
118
119 boolCompIf++;
120
121 entryStack.peek().bumpDecisionPoints( boolCompIf );
122 LOGGER.exiting(CLASS_NAME,"visit(ASTIfClause)");
123 super.visit( node, data );
124 return data;
125 }
126
127 @Override
128 public Object visit(ASTExceptionHandler node, Object data) {
129 LOGGER.entering(CLASS_NAME,"visit(ASTExceptionHandler)");
130 entryStack.peek().bumpDecisionPoints();
131 LOGGER.exiting(CLASS_NAME,"visit(ASTExceptionHandler)");
132 super.visit( node, data );
133 return data;
134 }
135
136 @Override
137 public Object visit(ASTForStatement node, Object data) {
138 LOGGER.entering(CLASS_NAME,"visit(ASTForStatement)");
139 int boolCompFor = NPathComplexityRule.sumExpressionComplexity( node.getFirstDescendantOfType( ASTExpression.class ) );
140
141 boolCompFor++;
142
143 entryStack.peek().bumpDecisionPoints( boolCompFor );
144 super.visit( node, data );
145 LOGGER.exiting(CLASS_NAME,"visit(ASTForStatement)");
146 return data;
147 }
148
149 @Override
150 public Object visit(ASTLoopStatement node, Object data) {
151 LOGGER.entering(CLASS_NAME,"visit(ASTLoopStatement)");
152 int boolCompDo = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
153
154 boolCompDo++;
155
156 entryStack.peek().bumpDecisionPoints( boolCompDo );
157 super.visit( node, data );
158 LOGGER.exiting(CLASS_NAME,"visit(ASTLoopStatement)");
159 return data;
160 }
161
162 @Override
163 public Object visit(ASTCaseStatement node, Object data) {
164 LOGGER.entering(CLASS_NAME,"visit(ASTCaseStatement)");
165 Entry entry = entryStack.peek();
166
167 int boolCompSwitch = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
168 entry.bumpDecisionPoints( boolCompSwitch );
169
170 super.visit( node, data );
171 LOGGER.exiting(CLASS_NAME,"visit(ASTCaseStatement)");
172 return data;
173 }
174
175 @Override
176 public Object visit(ASTCaseWhenClause node, Object data) {
177 LOGGER.entering(CLASS_NAME,"visit(ASTCaseWhenClause)");
178 Entry entry = entryStack.peek();
179
180 entry.bumpDecisionPoints();
181 super.visit( node, data );
182 LOGGER.exiting(CLASS_NAME,"visit(ASTCaseWhenClause)");
183 return data;
184 }
185
186 @Override
187 public Object visit(ASTWhileStatement node, Object data) {
188 LOGGER.entering(CLASS_NAME,"visit(ASTWhileStatement)");
189 int boolCompWhile = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
190
191 boolCompWhile++;
192
193 entryStack.peek().bumpDecisionPoints( boolCompWhile );
194 super.visit( node, data );
195 LOGGER.exiting(CLASS_NAME,"visit(ASTWhileStatement)");
196 return data;
197 }
198
199 @Override
200 public Object visit(ASTConditionalOrExpression node, Object data) {
201 return data;
202 }
203
204 @Override
205 public Object visit(ASTPackageSpecification node, Object data) {
206 LOGGER.entering(CLASS_NAME,"visit(ASTPackageSpecification)");
207
208 LOGGER.exiting(CLASS_NAME,"visit(ASTPackageSpecification)");
209 return data;
210 }
211
212 @Override
213 public Object visit(ASTTypeSpecification node, Object data) {
214 LOGGER.entering(CLASS_NAME,"visit(ASTTypeSpecification)");
215
216 LOGGER.exiting(CLASS_NAME,"visit(ASTTypeSpecification)");
217 return data;
218 }
219
220 @Override
221 public Object visit(ASTPackageBody node, Object data) {
222 LOGGER.entering(CLASS_NAME,"visit(ASTPackageBody)");
223
224 entryStack.push( new Entry( node ) );
225 super.visit( node, data );
226 Entry classEntry = entryStack.pop();
227 LOGGER.finest("ASTPackageBody: ComplexityAverage==" + classEntry.getComplexityAverage()
228 +", highestDecisionPoint="
229 + classEntry.highestDecisionPoints
230 );
231 if ( showClassesComplexity ) {
232 if ( classEntry.getComplexityAverage() >= reportLevel
233 || classEntry.highestDecisionPoints >= reportLevel ) {
234 addViolation( data, node, new String[] {
235 "class",
236 node.getImage(),
237 classEntry.getComplexityAverage() + " (Highest = "
238 + classEntry.highestDecisionPoints + ')' } );
239 }
240 }
241 LOGGER.exiting(CLASS_NAME,"visit(ASTPackageBody)");
242 return data;
243 }
244
245 @Override
246 public Object visit(ASTTriggerUnit node, Object data) {
247 LOGGER.entering(CLASS_NAME,"visit(ASTTriggerUnit)");
248
249 entryStack.push( new Entry( node ) );
250 super.visit( node, data );
251 Entry classEntry = entryStack.pop();
252 LOGGER.finest("ASTTriggerUnit: ComplexityAverage==" + classEntry.getComplexityAverage()
253 +", highestDecisionPoint="
254 + classEntry.highestDecisionPoints
255 );
256 if ( showClassesComplexity ) {
257 if ( classEntry.getComplexityAverage() >= reportLevel
258 || classEntry.highestDecisionPoints >= reportLevel ) {
259 addViolation( data, node, new String[] {
260 "class",
261 node.getImage(),
262 classEntry.getComplexityAverage() + " (Highest = "
263 + classEntry.highestDecisionPoints + ')' } );
264 }
265 }
266 LOGGER.exiting(CLASS_NAME,"visit(ASTTriggerUnit)");
267 return data;
268 }
269
270 @Override
271 public Object visit(ASTProgramUnit node, Object data) {
272 LOGGER.entering(CLASS_NAME,"visit(ASTProgramUnit)");
273 entryStack.push( new Entry( node ) );
274 super.visit( node, data );
275 Entry methodEntry = entryStack.pop();
276 LOGGER.finest("ASTProgramUnit: ComplexityAverage==" + methodEntry.getComplexityAverage()
277 +", highestDecisionPoint="
278 + methodEntry.highestDecisionPoints
279 );
280 if ( showMethodsComplexity ) {
281
282 int methodDecisionPoints = methodEntry.decisionPoints;
283 if (
284 null != node.getFirstParentOfType(ASTPackageBody.class)
285 || null != node.getFirstParentOfType(ASTTriggerUnit.class)
286
287
288 )
289 {
290
291
292
293
294
295
296
297
298 Entry classEntry = entryStack.peek();
299 classEntry.methodCount++;
300 classEntry.bumpDecisionPoints( methodDecisionPoints );
301
302 if ( methodDecisionPoints > classEntry.highestDecisionPoints ) {
303 classEntry.highestDecisionPoints = methodDecisionPoints;
304 }
305 }
306
307 ASTMethodDeclarator methodDeclarator = null;
308 for ( int n = 0; n < node.jjtGetNumChildren(); n++ ) {
309 Node childNode = node.jjtGetChild( n );
310 if ( childNode instanceof ASTMethodDeclarator ) {
311 methodDeclarator = (ASTMethodDeclarator) childNode;
312 break;
313 }
314 }
315
316 if ( methodEntry.decisionPoints >= reportLevel ) {
317 addViolation( data, node, new String[] { "method",
318 methodDeclarator == null ? "" : methodDeclarator.getImage(),
319 String.valueOf( methodEntry.decisionPoints ) } );
320 }
321 }
322 LOGGER.exiting(CLASS_NAME,"visit(ASTProgramUnit)");
323 return data;
324 }
325
326 @Override
327 public Object visit(ASTTypeMethod node, Object data) {
328 LOGGER.entering(CLASS_NAME,"visit(ASTTypeMethod)");
329 entryStack.push( new Entry( node ) );
330 super.visit( node, data );
331 Entry methodEntry = entryStack.pop();
332 LOGGER.finest("ASTProgramUnit: ComplexityAverage==" + methodEntry.getComplexityAverage()
333 +", highestDecisionPoint="
334 + methodEntry.highestDecisionPoints
335 );
336 if ( showMethodsComplexity ) {
337
338 int methodDecisionPoints = methodEntry.decisionPoints;
339 if (
340 null != node.getFirstParentOfType(ASTPackageBody.class)
341 )
342 {
343
344
345
346
347 Entry classEntry = entryStack.peek();
348 classEntry.methodCount++;
349 classEntry.bumpDecisionPoints( methodDecisionPoints );
350
351 if ( methodDecisionPoints > classEntry.highestDecisionPoints ) {
352 classEntry.highestDecisionPoints = methodDecisionPoints;
353 }
354 }
355
356 ASTMethodDeclarator methodDeclarator = null;
357 for ( int n = 0; n < node.jjtGetNumChildren(); n++ ) {
358 Node childNode = node.jjtGetChild( n );
359 if ( childNode instanceof ASTMethodDeclarator ) {
360 methodDeclarator = (ASTMethodDeclarator) childNode;
361 break;
362 }
363 }
364
365 if ( methodEntry.decisionPoints >= reportLevel ) {
366 addViolation( data, node, new String[] { "method",
367 methodDeclarator == null ? "" : methodDeclarator.getImage(),
368 String.valueOf( methodEntry.decisionPoints ) } );
369 }
370 }
371 LOGGER.exiting(CLASS_NAME,"visit(ASTTypeMethod)");
372 return data;
373 }
374
375
376 @Override
377 public Object visit(ASTTriggerTimingPointSection node, Object data) {
378 LOGGER.entering(CLASS_NAME,"visit(ASTTriggerTimingPointSection)");
379 entryStack.push( new Entry( node ) );
380 super.visit( node, data );
381 Entry methodEntry = entryStack.pop();
382 LOGGER.fine("ASTTriggerTimingPointSection: ComplexityAverage==" + methodEntry.getComplexityAverage()
383 +", highestDecisionPoint="
384 + methodEntry.highestDecisionPoints
385 );
386 if ( showMethodsComplexity ) {
387 int methodDecisionPoints = methodEntry.decisionPoints;
388 Entry classEntry = entryStack.peek();
389 classEntry.methodCount++;
390 classEntry.bumpDecisionPoints( methodDecisionPoints );
391
392 if ( methodDecisionPoints > classEntry.highestDecisionPoints ) {
393 classEntry.highestDecisionPoints = methodDecisionPoints;
394 }
395
396 ASTMethodDeclarator methodDeclarator = null;
397 for ( int n = 0; n < node.jjtGetNumChildren(); n++ ) {
398 Node childNode = node.jjtGetChild( n );
399 if ( childNode instanceof ASTMethodDeclarator ) {
400 methodDeclarator = (ASTMethodDeclarator) childNode;
401 break;
402 }
403 }
404
405 if ( methodEntry.decisionPoints >= reportLevel ) {
406 addViolation( data, node, new String[] { "method",
407 methodDeclarator == null ? "" : methodDeclarator.getImage(),
408 String.valueOf( methodEntry.decisionPoints ) } );
409 }
410 }
411 LOGGER.exiting(CLASS_NAME,"visit(ASTTriggerTimingPointSection)");
412 return data;
413 }
414
415
416 }