1
2
3
4 package net.sourceforge.pmd.lang.plsql.rule.codesize;
5
6 import java.util.ArrayList;
7 import java.util.List;
8 import java.util.logging.Level;
9 import java.util.logging.Logger;
10
11 import net.sourceforge.pmd.lang.plsql.ast.ASTCaseStatement;
12 import net.sourceforge.pmd.lang.plsql.ast.ASTCaseWhenClause;
13 import net.sourceforge.pmd.lang.plsql.ast.ASTConditionalAndExpression;
14 import net.sourceforge.pmd.lang.plsql.ast.ASTConditionalOrExpression;
15 import net.sourceforge.pmd.lang.plsql.ast.ASTElseClause;
16 import net.sourceforge.pmd.lang.plsql.ast.ASTElsifClause;
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.ASTLoopStatement;
21 import net.sourceforge.pmd.lang.plsql.ast.ASTMethodDeclaration;
22 import net.sourceforge.pmd.lang.plsql.ast.ASTProgramUnit;
23 import net.sourceforge.pmd.lang.plsql.ast.ASTReturnStatement;
24 import net.sourceforge.pmd.lang.plsql.ast.ASTStatement;
25 import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerTimingPointSection;
26 import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerUnit;
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.ast.ExecutableCode;
30 import net.sourceforge.pmd.lang.plsql.ast.PLSQLNode;
31 import net.sourceforge.pmd.lang.plsql.rule.AbstractStatisticalPLSQLRule;
32 import net.sourceforge.pmd.stat.DataPoint;
33 import net.sourceforge.pmd.util.NumericConstants;
34
35
36
37
38
39
40
41 public class NPathComplexityRule extends AbstractStatisticalPLSQLRule {
42 private final static String CLASS_NAME = NPathComplexityRule.class.getCanonicalName();
43 private final static Logger LOGGER = Logger.getLogger(NPathComplexityRule.class.getName());
44
45 public NPathComplexityRule() {
46 super();
47 setProperty(MINIMUM_DESCRIPTOR, 200d);
48 }
49
50 private int complexityMultipleOf(PLSQLNode node, int npathStart, Object data) {
51 LOGGER.entering(CLASS_NAME, "complexityMultipleOf(SimpleNode)");
52
53 int npath = npathStart;
54 PLSQLNode n;
55
56 for (int i = 0; i < node.jjtGetNumChildren(); i++) {
57 n = (PLSQLNode) node.jjtGetChild(i);
58 npath *= (Integer) n.jjtAccept(this, data);
59 }
60
61 LOGGER.exiting(CLASS_NAME, "complexityMultipleOf(SimpleNode)", npath);
62 return npath;
63 }
64
65 @Override
66 public Object visit(ASTMethodDeclaration node, Object data) {
67 LOGGER.entering(CLASS_NAME, "visit(ASTMethodDeclaration)");
68 int npath = complexityMultipleOf(node, 1, data);
69
70 DataPoint point = new DataPoint();
71 point.setNode(node);
72 point.setScore(1.0 * npath);
73 point.setMessage(getMessage());
74 addDataPoint(point);
75
76 if (LOGGER.isLoggable(Level.FINEST)) {
77 LOGGER.finest("NPath complexity: " + npath + " for line " + node.getBeginLine() + ", column "
78 + node.getBeginColumn());
79 }
80 LOGGER.exiting(CLASS_NAME, "visit(ASTMethodDeclaration)", npath);
81 return Integer.valueOf(npath);
82 }
83
84 @Override
85 public Object visit(ASTProgramUnit node, Object data) {
86 LOGGER.entering(CLASS_NAME, "visit(ASTProgramUnit)");
87 int npath = complexityMultipleOf(node, 1, data);
88
89 DataPoint point = new DataPoint();
90 point.setNode(node);
91 point.setScore(1.0 * npath);
92 point.setMessage(getMessage());
93 addDataPoint(point);
94
95 if (LOGGER.isLoggable(Level.FINEST)) {
96 LOGGER.finest("NPath complexity: " + npath + " for line " + node.getBeginLine() + ", column "
97 + node.getBeginColumn());
98 }
99 LOGGER.exiting(CLASS_NAME, "visit(ASTProgramUnit)", npath);
100 return Integer.valueOf(npath);
101 }
102
103 @Override
104 public Object visit(ASTTypeMethod node, Object data) {
105 LOGGER.entering(CLASS_NAME, "visit(ASTTypeMethod)");
106 int npath = complexityMultipleOf(node, 1, data);
107
108 DataPoint point = new DataPoint();
109 point.setNode(node);
110 point.setScore(1.0 * npath);
111 point.setMessage(getMessage());
112 addDataPoint(point);
113
114 if (LOGGER.isLoggable(Level.FINEST)) {
115 LOGGER.finest("NPath complexity: " + npath + " for line " + node.getBeginLine() + ", column "
116 + node.getBeginColumn());
117 }
118 LOGGER.exiting(CLASS_NAME, "visit(ASTTypeMethod)", npath);
119 return Integer.valueOf(npath);
120 }
121
122 @Override
123 public Object visit(ASTTriggerUnit node, Object data) {
124 LOGGER.entering(CLASS_NAME, "visit(ASTTriggerUnit)");
125 int npath = complexityMultipleOf(node, 1, data);
126
127 DataPoint point = new DataPoint();
128 point.setNode(node);
129 point.setScore(1.0 * npath);
130 point.setMessage(getMessage());
131 addDataPoint(point);
132
133 if (LOGGER.isLoggable(Level.FINEST)) {
134 LOGGER.finest("NPath complexity: " + npath + " for line " + node.getBeginLine() + ", column "
135 + node.getBeginColumn());
136 }
137 LOGGER.exiting(CLASS_NAME, "visit(ASTTriggerUnit)", npath);
138 return Integer.valueOf(npath);
139 }
140
141 @Override
142 public Object visit(ASTTriggerTimingPointSection node, Object data) {
143 LOGGER.entering(CLASS_NAME, "visit(ASTTriggerTimingPointSection)");
144 int npath = complexityMultipleOf(node, 1, data);
145
146 DataPoint point = new DataPoint();
147 point.setNode(node);
148 point.setScore(1.0 * npath);
149 point.setMessage(getMessage());
150 addDataPoint(point);
151
152 if (LOGGER.isLoggable(Level.FINEST)) {
153 LOGGER.finest("NPath complexity: " + npath + " for line " + node.getBeginLine() + ", column "
154 + node.getBeginColumn());
155 }
156 LOGGER.exiting(CLASS_NAME, "visit(ASTTriggerTimingPointSection)", npath);
157 return Integer.valueOf(npath);
158 }
159
160 @Override
161 public Object visit(PLSQLNode node, Object data) {
162 LOGGER.entering(CLASS_NAME, "visit(SimpleNode)");
163 int npath = complexityMultipleOf(node, 1, data);
164 LOGGER.exiting(CLASS_NAME, "visit(SimpleNode)", npath);
165 return Integer.valueOf(npath);
166 }
167
168 @Override
169 public Object visit(ASTIfStatement node, Object data) {
170 LOGGER.entering(CLASS_NAME, "visit(ASTIfStatement)");
171
172
173
174 int boolCompIf = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
175
176 int complexity = 0;
177
178 List<PLSQLNode> statementChildren = new ArrayList<PLSQLNode>();
179 for (int i = 0; i < node.jjtGetNumChildren(); i++) {
180 if (node.jjtGetChild(i).getClass() == ASTStatement.class
181 || node.jjtGetChild(i).getClass() == ASTElsifClause.class
182 || node.jjtGetChild(i).getClass() == ASTElseClause.class) {
183 statementChildren.add((PLSQLNode) node.jjtGetChild(i));
184 }
185 }
186 if (LOGGER.isLoggable(Level.FINEST)) {
187 LOGGER.finest(statementChildren.size() + " statementChildren found for IF statement " + node.getBeginLine()
188 + ", column " + node.getBeginColumn());
189 }
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210 for (PLSQLNode element : statementChildren) {
211 complexity += (Integer) element.jjtAccept(this, data);
212 }
213
214 LOGGER.exiting(CLASS_NAME, "visit(ASTIfStatement)", boolCompIf + complexity);
215 return Integer.valueOf(boolCompIf + complexity);
216 }
217
218 @Override
219 public Object visit(ASTElsifClause node, Object data) {
220 LOGGER.entering(CLASS_NAME, "visit(ASTElsifClause)");
221
222
223
224 int boolCompIf = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
225
226 int complexity = 0;
227
228 List<PLSQLNode> statementChildren = new ArrayList<PLSQLNode>();
229 for (int i = 0; i < node.jjtGetNumChildren(); i++) {
230 if (node.jjtGetChild(i).getClass() == ASTStatement.class) {
231 statementChildren.add((PLSQLNode) node.jjtGetChild(i));
232 }
233 }
234 if (LOGGER.isLoggable(Level.FINEST)) {
235 LOGGER.finest(statementChildren.size() + " statementChildren found for ELSIF statement "
236 + node.getBeginLine() + ", column " + node.getBeginColumn());
237 }
238
239
240
241
242
243
244
245
246
247
248 for (PLSQLNode element : statementChildren) {
249 complexity += (Integer) element.jjtAccept(this, data);
250 }
251
252 LOGGER.exiting(CLASS_NAME, "visit(ASTElsifClause)", boolCompIf + complexity);
253 return Integer.valueOf(boolCompIf + complexity);
254 }
255
256 @Override
257 public Object visit(ASTElseClause node, Object data) {
258 LOGGER.entering(CLASS_NAME, "visit(ASTElseClause)");
259
260
261
262 int complexity = 0;
263
264 List<PLSQLNode> statementChildren = new ArrayList<PLSQLNode>();
265 for (int i = 0; i < node.jjtGetNumChildren(); i++) {
266 if (node.jjtGetChild(i).getClass() == ASTStatement.class) {
267 statementChildren.add((PLSQLNode) node.jjtGetChild(i));
268 }
269 }
270 if (LOGGER.isLoggable(Level.FINEST)) {
271 LOGGER.finest(statementChildren.size() + " statementChildren found for ELSE clause statement "
272 + node.getBeginLine() + ", column " + node.getBeginColumn());
273 }
274
275 for (PLSQLNode element : statementChildren) {
276 complexity += (Integer) element.jjtAccept(this, data);
277 }
278
279 LOGGER.exiting(CLASS_NAME, "visit(ASTElseClause)", complexity);
280 return Integer.valueOf(complexity);
281 }
282
283 @Override
284 public Object visit(ASTWhileStatement node, Object data) {
285 LOGGER.entering(CLASS_NAME, "visit(ASTWhileStatement)");
286
287
288 int boolCompWhile = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
289
290 Integer nPathWhile = (Integer) ((PLSQLNode) node.getFirstChildOfType(ASTStatement.class)).jjtAccept(this, data);
291
292 LOGGER.exiting(CLASS_NAME, "visit(ASTWhileStatement)", boolCompWhile + nPathWhile + 1);
293 return Integer.valueOf(boolCompWhile + nPathWhile + 1);
294 }
295
296 @Override
297 public Object visit(ASTLoopStatement node, Object data) {
298 LOGGER.entering(CLASS_NAME, "visit(ASTLoopStatement)");
299
300
301 int boolCompDo = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
302
303 Integer nPathDo = (Integer) ((PLSQLNode) node.getFirstChildOfType(ASTStatement.class)).jjtAccept(this, data);
304
305 LOGGER.exiting(CLASS_NAME, "visit(ASTLoopStatement)", boolCompDo + nPathDo + 1);
306 return Integer.valueOf(boolCompDo + nPathDo + 1);
307 }
308
309 @Override
310 public Object visit(ASTForStatement node, Object data) {
311 LOGGER.entering(CLASS_NAME, "visit(ASTForStatement)");
312
313
314 int boolCompFor = sumExpressionComplexity(node.getFirstDescendantOfType(ASTExpression.class));
315
316 Integer nPathFor = (Integer) ((PLSQLNode) node.getFirstChildOfType(ASTStatement.class)).jjtAccept(this, data);
317
318 LOGGER.exiting(CLASS_NAME, "visit(ASTForStatement)", boolCompFor + nPathFor + 1);
319 return Integer.valueOf(boolCompFor + nPathFor + 1);
320 }
321
322 @Override
323 public Object visit(ASTReturnStatement node, Object data) {
324 LOGGER.entering(CLASS_NAME, "visit(ASTReturnStatement)");
325
326
327
328 ASTExpression expr = node.getFirstChildOfType(ASTExpression.class);
329
330 if (expr == null) {
331 return NumericConstants.ONE;
332 }
333
334 int boolCompReturn = sumExpressionComplexity(expr);
335 int conditionalExpressionComplexity = complexityMultipleOf(expr, 1, data);
336
337 if (conditionalExpressionComplexity > 1) {
338 boolCompReturn += conditionalExpressionComplexity;
339 }
340
341 if (boolCompReturn > 0) {
342 return Integer.valueOf(boolCompReturn);
343 }
344 LOGGER.entering(CLASS_NAME, "visit(ASTReturnStatement)", NumericConstants.ONE);
345 return NumericConstants.ONE;
346 }
347
348 @Override
349 public Object visit(ASTCaseWhenClause node, Object data) {
350 LOGGER.entering(CLASS_NAME, "visit(ASTCaseWhenClause)");
351
352
353 int boolCompSwitch = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
354
355 int npath = 1;
356 int caseRange = 0;
357 for (int i = 0; i < node.jjtGetNumChildren(); i++) {
358 PLSQLNode n = (PLSQLNode) node.jjtGetChild(i);
359
360
361 Integer complexity = (Integer) n.jjtAccept(this, data);
362 caseRange *= complexity;
363 }
364
365 npath += caseRange;
366 LOGGER.exiting(CLASS_NAME, "visit(ASTCaseWhenClause)", (boolCompSwitch + npath));
367 return Integer.valueOf(boolCompSwitch + npath);
368 }
369
370 @Override
371 public Object visit(ASTCaseStatement node, Object data) {
372 LOGGER.entering(CLASS_NAME, "visit(ASTCaseStatement)");
373
374
375 int boolCompSwitch = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
376
377 int npath = 0;
378 int caseRange = 0;
379 for (int i = 0; i < node.jjtGetNumChildren(); i++) {
380 PLSQLNode n = (PLSQLNode) node.jjtGetChild(i);
381
382
383 Integer complexity = (Integer) n.jjtAccept(this, data);
384 caseRange *= complexity;
385 }
386
387 npath += caseRange;
388 LOGGER.exiting(CLASS_NAME, "visit(ASTCaseStatement)", (boolCompSwitch + npath));
389 return Integer.valueOf(boolCompSwitch + npath);
390 }
391
392 @Override
393 public Object visit(ASTConditionalOrExpression node, Object data) {
394 return NumericConstants.ONE;
395 }
396
397
398
399
400
401
402
403
404
405
406
407
408
409 public static int sumExpressionComplexity(ASTExpression expr) {
410 LOGGER.entering(CLASS_NAME, "visit(ASTExpression)");
411 if (expr == null) {
412 LOGGER.exiting(CLASS_NAME, "visit(ASTExpression)", 0);
413 return 0;
414 }
415
416 List<ASTConditionalAndExpression> andNodes = expr.findDescendantsOfType(ASTConditionalAndExpression.class);
417 List<ASTConditionalOrExpression> orNodes = expr.findDescendantsOfType(ASTConditionalOrExpression.class);
418
419 int children = 0;
420
421 for (ASTConditionalOrExpression element : orNodes) {
422 children += element.jjtGetNumChildren();
423 children--;
424 }
425
426 for (ASTConditionalAndExpression element : andNodes) {
427 children += element.jjtGetNumChildren();
428 children--;
429 }
430
431 LOGGER.exiting(CLASS_NAME, "visit(ASTExpression)", children);
432 return children;
433 }
434
435 @Override
436 public Object[] getViolationParameters(DataPoint point) {
437 return new String[] { ((ExecutableCode) point.getNode()).getMethodName(),
438 String.valueOf((int) point.getScore()) };
439 }
440 }