1
2
3
4 package net.sourceforge.pmd.lang.plsql.rule.codesize;
5
6 import java.util.Stack;
7 import java.util.logging.Level;
8 import java.util.logging.Logger;
9
10 import net.sourceforge.pmd.lang.ast.Node;
11 import net.sourceforge.pmd.lang.plsql.ast.ASTExceptionHandler;
12 import net.sourceforge.pmd.lang.plsql.ast.ASTPackageSpecification;
13 import net.sourceforge.pmd.lang.plsql.ast.ASTPackageBody;
14 import net.sourceforge.pmd.lang.plsql.ast.ASTTypeSpecification;
15 import net.sourceforge.pmd.lang.plsql.ast.ASTInput;
16 import net.sourceforge.pmd.lang.plsql.ast.ASTConditionalOrExpression;
17 import net.sourceforge.pmd.lang.plsql.ast.ASTLoopStatement;
18 import net.sourceforge.pmd.lang.plsql.ast.ASTExpression;
19 import net.sourceforge.pmd.lang.plsql.ast.ASTForStatement;
20 import net.sourceforge.pmd.lang.plsql.ast.ASTIfStatement;
21 import net.sourceforge.pmd.lang.plsql.ast.ASTElsifClause;
22 import net.sourceforge.pmd.lang.plsql.ast.ASTMethodDeclarator;
23 import net.sourceforge.pmd.lang.plsql.ast.ASTProgramUnit;
24 import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerUnit;
25 import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerTimingPointSection;
26 import net.sourceforge.pmd.lang.plsql.ast.ASTCaseStatement;
27 import net.sourceforge.pmd.lang.plsql.ast.ASTCaseWhenClause;
28 import net.sourceforge.pmd.lang.plsql.ast.ASTTypeMethod;
29 import net.sourceforge.pmd.lang.plsql.ast.ASTWhileStatement;
30 import net.sourceforge.pmd.lang.plsql.rule.AbstractPLSQLRule;
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 if (LOGGER.isLoggable(Level.FINEST)) {
228 LOGGER.finest("ASTPackageBody: ComplexityAverage==" + classEntry.getComplexityAverage()
229 +", highestDecisionPoint="
230 + classEntry.highestDecisionPoints
231 );
232 }
233 if ( showClassesComplexity ) {
234 if ( classEntry.getComplexityAverage() >= reportLevel
235 || classEntry.highestDecisionPoints >= reportLevel ) {
236 addViolation( data, node, new String[] {
237 "class",
238 node.getImage(),
239 classEntry.getComplexityAverage() + " (Highest = "
240 + classEntry.highestDecisionPoints + ')' } );
241 }
242 }
243 LOGGER.exiting(CLASS_NAME,"visit(ASTPackageBody)");
244 return data;
245 }
246
247 @Override
248 public Object visit(ASTTriggerUnit node, Object data) {
249 LOGGER.entering(CLASS_NAME,"visit(ASTTriggerUnit)");
250
251 entryStack.push( new Entry( node ) );
252 super.visit( node, data );
253 Entry classEntry = entryStack.pop();
254 if (LOGGER.isLoggable(Level.FINEST)) {
255 LOGGER.finest("ASTTriggerUnit: ComplexityAverage==" + classEntry.getComplexityAverage()
256 +", highestDecisionPoint="
257 + classEntry.highestDecisionPoints
258 );
259 }
260 if ( showClassesComplexity ) {
261 if ( classEntry.getComplexityAverage() >= reportLevel
262 || classEntry.highestDecisionPoints >= reportLevel ) {
263 addViolation( data, node, new String[] {
264 "class",
265 node.getImage(),
266 classEntry.getComplexityAverage() + " (Highest = "
267 + classEntry.highestDecisionPoints + ')' } );
268 }
269 }
270 LOGGER.exiting(CLASS_NAME,"visit(ASTTriggerUnit)");
271 return data;
272 }
273
274 @Override
275 public Object visit(ASTProgramUnit node, Object data) {
276 LOGGER.entering(CLASS_NAME,"visit(ASTProgramUnit)");
277 entryStack.push( new Entry( node ) );
278 super.visit( node, data );
279 Entry methodEntry = entryStack.pop();
280 if (LOGGER.isLoggable(Level.FINEST)) {
281 LOGGER.finest("ASTProgramUnit: ComplexityAverage==" + methodEntry.getComplexityAverage()
282 +", highestDecisionPoint="
283 + methodEntry.highestDecisionPoints
284 );
285 }
286 if ( showMethodsComplexity ) {
287
288 int methodDecisionPoints = methodEntry.decisionPoints;
289 if (
290 null != node.getFirstParentOfType(ASTPackageBody.class)
291 || null != node.getFirstParentOfType(ASTTriggerUnit.class)
292
293
294 )
295 {
296
297
298
299
300
301
302
303
304 Entry classEntry = entryStack.peek();
305 classEntry.methodCount++;
306 classEntry.bumpDecisionPoints( methodDecisionPoints );
307
308 if ( methodDecisionPoints > classEntry.highestDecisionPoints ) {
309 classEntry.highestDecisionPoints = methodDecisionPoints;
310 }
311 }
312
313 ASTMethodDeclarator methodDeclarator = null;
314 for ( int n = 0; n < node.jjtGetNumChildren(); n++ ) {
315 Node childNode = node.jjtGetChild( n );
316 if ( childNode instanceof ASTMethodDeclarator ) {
317 methodDeclarator = (ASTMethodDeclarator) childNode;
318 break;
319 }
320 }
321
322 if ( methodEntry.decisionPoints >= reportLevel ) {
323 addViolation( data, node, new String[] { "method",
324 methodDeclarator == null ? "" : methodDeclarator.getImage(),
325 String.valueOf( methodEntry.decisionPoints ) } );
326 }
327 }
328 LOGGER.exiting(CLASS_NAME,"visit(ASTProgramUnit)");
329 return data;
330 }
331
332 @Override
333 public Object visit(ASTTypeMethod node, Object data) {
334 LOGGER.entering(CLASS_NAME,"visit(ASTTypeMethod)");
335 entryStack.push( new Entry( node ) );
336 super.visit( node, data );
337 Entry methodEntry = entryStack.pop();
338 if (LOGGER.isLoggable(Level.FINEST)) {
339 LOGGER.finest("ASTProgramUnit: ComplexityAverage==" + methodEntry.getComplexityAverage()
340 +", highestDecisionPoint="
341 + methodEntry.highestDecisionPoints
342 );
343 }
344 if ( showMethodsComplexity ) {
345
346 int methodDecisionPoints = methodEntry.decisionPoints;
347 if (
348 null != node.getFirstParentOfType(ASTPackageBody.class)
349 )
350 {
351
352
353
354
355 Entry classEntry = entryStack.peek();
356 classEntry.methodCount++;
357 classEntry.bumpDecisionPoints( methodDecisionPoints );
358
359 if ( methodDecisionPoints > classEntry.highestDecisionPoints ) {
360 classEntry.highestDecisionPoints = methodDecisionPoints;
361 }
362 }
363
364 ASTMethodDeclarator methodDeclarator = null;
365 for ( int n = 0; n < node.jjtGetNumChildren(); n++ ) {
366 Node childNode = node.jjtGetChild( n );
367 if ( childNode instanceof ASTMethodDeclarator ) {
368 methodDeclarator = (ASTMethodDeclarator) childNode;
369 break;
370 }
371 }
372
373 if ( methodEntry.decisionPoints >= reportLevel ) {
374 addViolation( data, node, new String[] { "method",
375 methodDeclarator == null ? "" : methodDeclarator.getImage(),
376 String.valueOf( methodEntry.decisionPoints ) } );
377 }
378 }
379 LOGGER.exiting(CLASS_NAME,"visit(ASTTypeMethod)");
380 return data;
381 }
382
383
384 @Override
385 public Object visit(ASTTriggerTimingPointSection node, Object data) {
386 LOGGER.entering(CLASS_NAME,"visit(ASTTriggerTimingPointSection)");
387 entryStack.push( new Entry( node ) );
388 super.visit( node, data );
389 Entry methodEntry = entryStack.pop();
390 if (LOGGER.isLoggable(Level.FINE)) {
391 LOGGER.fine("ASTTriggerTimingPointSection: ComplexityAverage==" + methodEntry.getComplexityAverage()
392 +", highestDecisionPoint="
393 + methodEntry.highestDecisionPoints
394 );
395 }
396 if ( showMethodsComplexity ) {
397 int methodDecisionPoints = methodEntry.decisionPoints;
398 Entry classEntry = entryStack.peek();
399 classEntry.methodCount++;
400 classEntry.bumpDecisionPoints( methodDecisionPoints );
401
402 if ( methodDecisionPoints > classEntry.highestDecisionPoints ) {
403 classEntry.highestDecisionPoints = methodDecisionPoints;
404 }
405
406 ASTMethodDeclarator methodDeclarator = null;
407 for ( int n = 0; n < node.jjtGetNumChildren(); n++ ) {
408 Node childNode = node.jjtGetChild( n );
409 if ( childNode instanceof ASTMethodDeclarator ) {
410 methodDeclarator = (ASTMethodDeclarator) childNode;
411 break;
412 }
413 }
414
415 if ( methodEntry.decisionPoints >= reportLevel ) {
416 addViolation( data, node, new String[] { "method",
417 methodDeclarator == null ? "" : methodDeclarator.getImage(),
418 String.valueOf( methodEntry.decisionPoints ) } );
419 }
420 }
421 LOGGER.exiting(CLASS_NAME,"visit(ASTTriggerTimingPointSection)");
422 return data;
423 }
424
425
426 }