001: /**
002: * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003: */package net.sourceforge.pmd.rules;
004:
005: import java.util.Stack;
006:
007: import net.sourceforge.pmd.AbstractJavaRule;
008: import net.sourceforge.pmd.ast.ASTBlockStatement;
009: import net.sourceforge.pmd.ast.ASTCatchStatement;
010: import net.sourceforge.pmd.ast.ASTClassOrInterfaceDeclaration;
011: import net.sourceforge.pmd.ast.ASTCompilationUnit;
012: import net.sourceforge.pmd.ast.ASTConditionalExpression;
013: import net.sourceforge.pmd.ast.ASTConstructorDeclaration;
014: import net.sourceforge.pmd.ast.ASTDoStatement;
015: import net.sourceforge.pmd.ast.ASTEnumDeclaration;
016: import net.sourceforge.pmd.ast.ASTExpression;
017: import net.sourceforge.pmd.ast.ASTForStatement;
018: import net.sourceforge.pmd.ast.ASTIfStatement;
019: import net.sourceforge.pmd.ast.ASTMethodDeclaration;
020: import net.sourceforge.pmd.ast.ASTMethodDeclarator;
021: import net.sourceforge.pmd.ast.ASTSwitchLabel;
022: import net.sourceforge.pmd.ast.ASTSwitchStatement;
023: import net.sourceforge.pmd.ast.ASTWhileStatement;
024: import net.sourceforge.pmd.ast.Node;
025: import net.sourceforge.pmd.ast.SimpleNode;
026: import net.sourceforge.pmd.rules.design.NpathComplexity;
027:
028: /**
029: * @author Donald A. Leckie,
030: *
031: * @version $Revision: 5773 $, $Date: 2008-02-14 01:06:49 -0800 (Thu, 14 Feb 2008) $
032: * @since January 14, 2003
033: */
034: public class CyclomaticComplexity extends AbstractJavaRule {
035:
036: private int reportLevel;
037: private boolean showClassesComplexity = true;
038: private boolean showMethodsComplexity = true;
039:
040: private static class Entry {
041: private SimpleNode node;
042: private int decisionPoints = 1;
043: public int highestDecisionPoints;
044: public int methodCount;
045:
046: private Entry(SimpleNode node) {
047: this .node = node;
048: }
049:
050: public void bumpDecisionPoints() {
051: decisionPoints++;
052: }
053:
054: public void bumpDecisionPoints(int size) {
055: decisionPoints += size;
056: }
057:
058: public int getComplexityAverage() {
059: return ((double) methodCount == 0) ? 1 : (int) (Math
060: .rint((double) decisionPoints
061: / (double) methodCount));
062: }
063: }
064:
065: private Stack<Entry> entryStack = new Stack<Entry>();
066:
067: public Object visit(ASTCompilationUnit node, Object data) {
068: reportLevel = getIntProperty("reportLevel");
069: showClassesComplexity = getBooleanProperty("showClassesComplexity");
070: showMethodsComplexity = getBooleanProperty("showMethodsComplexity");
071: super .visit(node, data);
072: return data;
073: }
074:
075: public Object visit(ASTIfStatement node, Object data) {
076: int boolCompIf = NpathComplexity.sumExpressionComplexity(node
077: .getFirstChildOfType(ASTExpression.class));
078: // If statement always has a complexity of at least 1
079: boolCompIf++;
080:
081: entryStack.peek().bumpDecisionPoints(boolCompIf);
082: super .visit(node, data);
083: return data;
084: }
085:
086: public Object visit(ASTCatchStatement node, Object data) {
087: entryStack.peek().bumpDecisionPoints();
088: super .visit(node, data);
089: return data;
090: }
091:
092: public Object visit(ASTForStatement node, Object data) {
093: int boolCompFor = NpathComplexity.sumExpressionComplexity(node
094: .getFirstChildOfType(ASTExpression.class));
095: // For statement always has a complexity of at least 1
096: boolCompFor++;
097:
098: entryStack.peek().bumpDecisionPoints(boolCompFor);
099: super .visit(node, data);
100: return data;
101: }
102:
103: public Object visit(ASTDoStatement node, Object data) {
104: int boolCompDo = NpathComplexity.sumExpressionComplexity(node
105: .getFirstChildOfType(ASTExpression.class));
106: // Do statement always has a complexity of at least 1
107: boolCompDo++;
108:
109: entryStack.peek().bumpDecisionPoints(boolCompDo);
110: super .visit(node, data);
111: return data;
112: }
113:
114: public Object visit(ASTSwitchStatement node, Object data) {
115: Entry entry = entryStack.peek();
116:
117: int boolCompSwitch = NpathComplexity
118: .sumExpressionComplexity(node
119: .getFirstChildOfType(ASTExpression.class));
120: entry.bumpDecisionPoints(boolCompSwitch);
121:
122: int childCount = node.jjtGetNumChildren();
123: int lastIndex = childCount - 1;
124: for (int n = 0; n < lastIndex; n++) {
125: Node childNode = node.jjtGetChild(n);
126: if (childNode instanceof ASTSwitchLabel) {
127: // default is generally not considered a decision (same as "else")
128: ASTSwitchLabel sl = (ASTSwitchLabel) childNode;
129: if (!sl.isDefault()) {
130: childNode = node.jjtGetChild(n + 1);
131: if (childNode instanceof ASTBlockStatement) {
132: entry.bumpDecisionPoints();
133: }
134: }
135: }
136: }
137: super .visit(node, data);
138: return data;
139: }
140:
141: public Object visit(ASTWhileStatement node, Object data) {
142: int boolCompWhile = NpathComplexity
143: .sumExpressionComplexity(node
144: .getFirstChildOfType(ASTExpression.class));
145: // While statement always has a complexity of at least 1
146: boolCompWhile++;
147:
148: entryStack.peek().bumpDecisionPoints(boolCompWhile);
149: super .visit(node, data);
150: return data;
151: }
152:
153: public Object visit(ASTConditionalExpression node, Object data) {
154: if (node.isTernary()) {
155: int boolCompTern = NpathComplexity
156: .sumExpressionComplexity(node
157: .getFirstChildOfType(ASTExpression.class));
158: // Ternary statement always has a complexity of at least 1
159: boolCompTern++;
160:
161: entryStack.peek().bumpDecisionPoints(boolCompTern);
162: super .visit(node, data);
163: }
164: return data;
165: }
166:
167: public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
168: if (node.isInterface()) {
169: return data;
170: }
171:
172: entryStack.push(new Entry(node));
173: super .visit(node, data);
174: if (showClassesComplexity) {
175: Entry classEntry = entryStack.pop();
176: if ((classEntry.getComplexityAverage() >= reportLevel)
177: || (classEntry.highestDecisionPoints >= reportLevel)) {
178: addViolation(data, node, new String[] {
179: "class",
180: node.getImage(),
181: classEntry.getComplexityAverage()
182: + " (Highest = "
183: + classEntry.highestDecisionPoints
184: + ')' });
185: }
186: }
187: return data;
188: }
189:
190: public Object visit(ASTMethodDeclaration node, Object data) {
191: entryStack.push(new Entry(node));
192: super .visit(node, data);
193: if (showMethodsComplexity) {
194: Entry methodEntry = entryStack.pop();
195: int methodDecisionPoints = methodEntry.decisionPoints;
196: Entry classEntry = entryStack.peek();
197: classEntry.methodCount++;
198: classEntry.bumpDecisionPoints(methodDecisionPoints);
199:
200: if (methodDecisionPoints > classEntry.highestDecisionPoints) {
201: classEntry.highestDecisionPoints = methodDecisionPoints;
202: }
203:
204: ASTMethodDeclarator methodDeclarator = null;
205: for (int n = 0; n < node.jjtGetNumChildren(); n++) {
206: Node childNode = node.jjtGetChild(n);
207: if (childNode instanceof ASTMethodDeclarator) {
208: methodDeclarator = (ASTMethodDeclarator) childNode;
209: break;
210: }
211: }
212:
213: if (methodEntry.decisionPoints >= reportLevel) {
214: addViolation(data, node, new String[] {
215: "method",
216: (methodDeclarator == null) ? ""
217: : methodDeclarator.getImage(),
218: String.valueOf(methodEntry.decisionPoints) });
219: }
220: }
221: return data;
222: }
223:
224: public Object visit(ASTEnumDeclaration node, Object data) {
225: entryStack.push(new Entry(node));
226: super .visit(node, data);
227: Entry classEntry = entryStack.pop();
228: if ((classEntry.getComplexityAverage() >= reportLevel)
229: || (classEntry.highestDecisionPoints >= reportLevel)) {
230: addViolation(data, node, new String[] {
231: "class",
232: node.getImage(),
233: classEntry.getComplexityAverage() + "(Highest = "
234: + classEntry.highestDecisionPoints + ')' });
235: }
236: return data;
237: }
238:
239: public Object visit(ASTConstructorDeclaration node, Object data) {
240: entryStack.push(new Entry(node));
241: super .visit(node, data);
242: Entry constructorEntry = entryStack.pop();
243: int constructorDecisionPointCount = constructorEntry.decisionPoints;
244: Entry classEntry = entryStack.peek();
245: classEntry.methodCount++;
246: classEntry.decisionPoints += constructorDecisionPointCount;
247: if (constructorDecisionPointCount > classEntry.highestDecisionPoints) {
248: classEntry.highestDecisionPoints = constructorDecisionPointCount;
249: }
250: if (constructorEntry.decisionPoints >= reportLevel) {
251: addViolation(data, node, new String[] { "constructor",
252: classEntry.node.getImage(),
253: String.valueOf(constructorDecisionPointCount) });
254: }
255: return data;
256: }
257:
258: }
|