1 /***
2 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3 */
4 package net.sourceforge.pmd.rules;
5
6 import net.sourceforge.pmd.AbstractRule;
7 import net.sourceforge.pmd.RuleContext;
8 import net.sourceforge.pmd.RuleViolation;
9 import net.sourceforge.pmd.ast.ASTBlockStatement;
10 import net.sourceforge.pmd.ast.ASTClassOrInterfaceDeclaration;
11 import net.sourceforge.pmd.ast.ASTConstructorDeclaration;
12 import net.sourceforge.pmd.ast.ASTForStatement;
13 import net.sourceforge.pmd.ast.ASTIfStatement;
14 import net.sourceforge.pmd.ast.ASTMethodDeclaration;
15 import net.sourceforge.pmd.ast.ASTMethodDeclarator;
16 import net.sourceforge.pmd.ast.ASTSwitchLabel;
17 import net.sourceforge.pmd.ast.ASTSwitchStatement;
18 import net.sourceforge.pmd.ast.ASTWhileStatement;
19 import net.sourceforge.pmd.ast.Node;
20 import net.sourceforge.pmd.ast.SimpleNode;
21
22 import java.text.MessageFormat;
23 import java.util.Stack;
24
25 /***
26 * @author Donald A. Leckie
27 * @version $Revision: 1.5 $, $Date: 2005/03/24 21:53:50 $
28 * @since January 14, 2003
29 */
30 public class CyclomaticComplexity extends AbstractRule {
31
32 private static class Entry {
33 private SimpleNode node;
34 private int decisionPoints = 1;
35 public int highestDecisionPoints;
36 public int methodCount;
37
38 private Entry(SimpleNode node) {
39 this.node = node;
40 }
41
42 public void bumpDecisionPoints() {
43 decisionPoints++;
44 }
45
46 public void bumpDecisionPoints(int size) {
47 decisionPoints += size;
48 }
49
50 public int getComplexityAverage() {
51 return ((double) methodCount == 0) ? 1 : (int) (Math.rint((double) decisionPoints / (double) methodCount));
52 }
53 }
54
55 private Stack entryStack = new Stack();
56
57 public Object visit(ASTIfStatement node, Object data) {
58 ((Entry) entryStack.peek()).bumpDecisionPoints();
59 super.visit(node, data);
60 return data;
61 }
62
63 public Object visit(ASTForStatement node, Object data) {
64 ((Entry) entryStack.peek()).bumpDecisionPoints();
65 super.visit(node, data);
66 return data;
67 }
68
69 public Object visit(ASTSwitchStatement node, Object data) {
70 Entry entry = (Entry) entryStack.peek();
71 int childCount = node.jjtGetNumChildren();
72 int lastIndex = childCount - 1;
73 for (int n = 0; n < lastIndex; n++) {
74 Node childNode = node.jjtGetChild(n);
75 if (childNode instanceof ASTSwitchLabel) {
76 childNode = node.jjtGetChild(n + 1);
77 if (childNode instanceof ASTBlockStatement) {
78 entry.bumpDecisionPoints();
79 }
80 }
81 }
82 super.visit(node, data);
83 return data;
84 }
85
86 public Object visit(ASTWhileStatement node, Object data) {
87 ((Entry) entryStack.peek()).bumpDecisionPoints();
88 super.visit(node, data);
89 return data;
90 }
91
92 public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
93 if (node.isInterface()) {
94 return data;
95 }
96
97 entryStack.push(new Entry(node));
98 super.visit(node, data);
99 Entry classEntry = (Entry) entryStack.pop();
100 if ((classEntry.getComplexityAverage() >= getIntProperty("reportLevel")) || (classEntry.highestDecisionPoints >= getIntProperty("reportLevel"))) {
101 RuleContext ruleContext = (RuleContext) data;
102 String[] args = {"class", node.getImage(), String.valueOf(classEntry.getComplexityAverage()) + " (Highest = " + String.valueOf(classEntry.highestDecisionPoints) + ")"};
103 RuleViolation ruleViolation = createRuleViolation(ruleContext, node, MessageFormat.format(getMessage(), args));
104 ruleContext.getReport().addRuleViolation(ruleViolation);
105 }
106 return data;
107 }
108
109 public Object visit(ASTMethodDeclaration node, Object data) {
110 entryStack.push(new Entry(node));
111 super.visit(node, data);
112 Entry methodEntry = (Entry) entryStack.pop();
113 int methodDecisionPoints = methodEntry.decisionPoints;
114 Entry classEntry = (Entry) entryStack.peek();
115 classEntry.methodCount++;
116 classEntry.bumpDecisionPoints(methodDecisionPoints);
117
118 if (methodDecisionPoints > classEntry.highestDecisionPoints) {
119 classEntry.highestDecisionPoints = methodDecisionPoints;
120 }
121
122 ASTMethodDeclarator methodDeclarator = null;
123 for (int n = 0; n < node.jjtGetNumChildren(); n++) {
124 Node childNode = node.jjtGetChild(n);
125 if (childNode instanceof ASTMethodDeclarator) {
126 methodDeclarator = (ASTMethodDeclarator) childNode;
127 break;
128 }
129 }
130
131 if (methodEntry.decisionPoints >= getIntProperty("reportLevel")) {
132 RuleContext ruleContext = (RuleContext) data;
133 String[] args = {"method", (methodDeclarator == null) ? "" : methodDeclarator.getImage(), String.valueOf(methodEntry.decisionPoints)};
134 ruleContext.getReport().addRuleViolation(createRuleViolation(ruleContext, node, MessageFormat.format(getMessage(), args)));
135 }
136
137 return data;
138 }
139
140 public Object visit(ASTConstructorDeclaration node, Object data) {
141 entryStack.push(new Entry(node));
142 super.visit(node, data);
143 Entry constructorEntry = (Entry) entryStack.pop();
144 int constructorDecisionPointCount = constructorEntry.decisionPoints;
145 Entry classEntry = (Entry) entryStack.peek();
146 classEntry.methodCount++;
147 classEntry.decisionPoints += constructorDecisionPointCount;
148 if (constructorDecisionPointCount > classEntry.highestDecisionPoints) {
149 classEntry.highestDecisionPoints = constructorDecisionPointCount;
150 }
151 if (constructorEntry.decisionPoints >= getIntProperty("reportLevel")) {
152 RuleContext ruleContext = (RuleContext) data;
153 String[] args = {"constructor", classEntry.node.getImage(), String.valueOf(constructorDecisionPointCount)};
154 RuleViolation ruleViolation = createRuleViolation(ruleContext, node, MessageFormat.format(getMessage(), args));
155 ruleContext.getReport().addRuleViolation(ruleViolation);
156 }
157 return data;
158 }
159
160 }