001: package org.acm.seguin.pmd.rules;
002:
003: import org.acm.seguin.pmd.AbstractRule;
004: import org.acm.seguin.pmd.RuleContext;
005: import org.acm.seguin.pmd.RuleViolation;
006: import net.sourceforge.jrefactory.ast.ASTBlockStatement;
007: import net.sourceforge.jrefactory.ast.ASTConstructorDeclaration;
008: import net.sourceforge.jrefactory.ast.ASTForStatement;
009: import net.sourceforge.jrefactory.ast.ASTIfStatement;
010: import net.sourceforge.jrefactory.ast.ASTInterfaceDeclaration;
011: import net.sourceforge.jrefactory.ast.ASTMethodDeclaration;
012: import net.sourceforge.jrefactory.ast.ASTMethodDeclarator;
013: import net.sourceforge.jrefactory.ast.ASTSwitchLabel;
014: import net.sourceforge.jrefactory.ast.ASTSwitchStatement;
015: import net.sourceforge.jrefactory.ast.ASTUnmodifiedClassDeclaration;
016: import net.sourceforge.jrefactory.ast.ASTWhileStatement;
017: import net.sourceforge.jrefactory.ast.Node;
018: import net.sourceforge.jrefactory.ast.SimpleNode;
019:
020: import java.text.MessageFormat;
021: import java.util.Stack;
022:
023: /**
024: *
025: * @author Donald A. Leckie
026: * @since January 14, 2003
027: * @version $Revision: 1.3 $, $Date: 2003/11/11 18:48:38 $
028: */
029: public class CyclomaticComplexityRule extends AbstractRule {
030: private Stack m_entryStack = new Stack();
031:
032: /**
033: **************************************************************************
034: *
035: * @param node
036: * @param data
037: *
038: * @return
039: */
040: public Object visit(ASTIfStatement node, Object data) {
041: Entry entry = (Entry) m_entryStack.peek();
042: entry.m_decisionPoints++;
043: super .visit(node, data);
044:
045: return data;
046: }
047:
048: /**
049: **************************************************************************
050: *
051: * @param node
052: * @param data
053: *
054: * @return
055: */
056: public Object visit(ASTForStatement node, Object data) {
057: Entry entry = (Entry) m_entryStack.peek();
058: entry.m_decisionPoints++;
059: super .visit(node, data);
060:
061: return data;
062: }
063:
064: /**
065: **************************************************************************
066: *
067: * @param node
068: * @param data
069: *
070: * @return
071: */
072: public Object visit(ASTSwitchStatement node, Object data) {
073: Entry entry = (Entry) m_entryStack.peek();
074:
075: int childCount = node.jjtGetNumChildren();
076: int lastIndex = childCount - 1;
077:
078: for (int n = 0; n < lastIndex; n++) {
079: Node childNode = node.jjtGetChild(n);
080:
081: if (childNode instanceof ASTSwitchLabel) {
082: childNode = node.jjtGetChild(n + 1);
083:
084: if (childNode instanceof ASTBlockStatement) {
085: entry.m_decisionPoints++;
086: }
087: }
088: }
089:
090: super .visit(node, data);
091:
092: return data;
093: }
094:
095: /**
096: **************************************************************************
097: *
098: * @param node
099: * @param data
100: *
101: * @return
102: */
103: public Object visit(ASTWhileStatement node, Object data) {
104: Entry entry = (Entry) m_entryStack.peek();
105: entry.m_decisionPoints++;
106: super .visit(node, data);
107:
108: return data;
109: }
110:
111: /**
112: **************************************************************************
113: *
114: * @param node
115: * @param data
116: *
117: * @return
118: */
119: public Object visit(ASTUnmodifiedClassDeclaration node, Object data) {
120: m_entryStack.push(new Entry(node));
121: super .visit(node, data);
122: Entry classEntry = (Entry) m_entryStack.pop();
123: double decisionPoints = (double) classEntry.m_decisionPoints;
124: double methodCount = (double) classEntry.m_methodCount;
125: int complexityAverage = (methodCount == 0) ? 1 : (int) (Math
126: .rint(decisionPoints / methodCount));
127:
128: if ((complexityAverage >= getIntProperty("reportLevel"))
129: || (classEntry.m_highestDecisionPoints >= getIntProperty("reportLevel"))) {
130: // The {0} "{1}" has a cyclomatic complexity of {2}.
131: RuleContext ruleContext = (RuleContext) data;
132: String template = getMessage();
133: String className = node.getImage();
134: String complexityHighest = String
135: .valueOf(classEntry.m_highestDecisionPoints);
136: String complexity = String.valueOf(complexityAverage)
137: + " (Highest = " + complexityHighest + ")";
138: String[] args = { "class", className, complexity };
139: String message = MessageFormat.format(template, args);
140: int lineNumber = node.getBeginLine();
141: RuleViolation ruleViolation = createRuleViolation(
142: ruleContext, lineNumber, message);
143: ruleContext.getReport().addRuleViolation(ruleViolation);
144: }
145:
146: return data;
147: }
148:
149: /**
150: **************************************************************************
151: *
152: * @param node
153: * @param data
154: *
155: * @return
156: */
157: public Object visit(ASTMethodDeclaration node, Object data) {
158: Node parentNode = node.jjtGetParent();
159:
160: while (parentNode != null) {
161: if (parentNode instanceof ASTInterfaceDeclaration) {
162: return data;
163: }
164:
165: parentNode = parentNode.jjtGetParent();
166: }
167:
168: m_entryStack.push(new Entry(node));
169: super .visit(node, data);
170: Entry methodEntry = (Entry) m_entryStack.pop();
171: int methodDecisionPoints = methodEntry.m_decisionPoints;
172: Entry classEntry = (Entry) m_entryStack.peek();
173: classEntry.m_methodCount++;
174: classEntry.m_decisionPoints += methodDecisionPoints;
175:
176: if (methodDecisionPoints > classEntry.m_highestDecisionPoints) {
177: classEntry.m_highestDecisionPoints = methodDecisionPoints;
178: }
179:
180: // find the method declarator skipping over Annotations and TypeParameters
181: ASTMethodDeclarator methodDeclarator = null;
182: for (int n = 0; n < node.jjtGetNumChildren(); n++) {
183: Node childNode = node.jjtGetChild(n);
184: if (childNode instanceof ASTMethodDeclarator) {
185: methodDeclarator = (ASTMethodDeclarator) childNode;
186: break;
187: }
188: }
189:
190: if (methodEntry.m_decisionPoints >= getIntProperty("reportLevel")) {
191: // The {0} "{1}" has a cyclomatic complexity of {2}.
192: RuleContext ruleContext = (RuleContext) data;
193: String template = getMessage();
194: String methodName = (methodDeclarator == null) ? ""
195: : methodDeclarator.getImage();
196: String complexity = String
197: .valueOf(methodEntry.m_decisionPoints);
198: String[] args = { "method", methodName, complexity };
199: String message = MessageFormat.format(template, args);
200: int lineNumber = node.getBeginLine();
201: RuleViolation ruleViolation = createRuleViolation(
202: ruleContext, lineNumber, message);
203: ruleContext.getReport().addRuleViolation(ruleViolation);
204: }
205:
206: return data;
207: }
208:
209: /**
210: **************************************************************************
211: *
212: * @param node
213: * @param data
214: *
215: * @return
216: */
217: public Object visit(ASTConstructorDeclaration node, Object data) {
218: m_entryStack.push(new Entry(node));
219: super .visit(node, data);
220: Entry constructorEntry = (Entry) m_entryStack.pop();
221: int constructorDecisionPointCount = constructorEntry.m_decisionPoints;
222: Entry classEntry = (Entry) m_entryStack.peek();
223: classEntry.m_methodCount++;
224: classEntry.m_decisionPoints += constructorDecisionPointCount;
225:
226: if (constructorDecisionPointCount > classEntry.m_highestDecisionPoints) {
227: classEntry.m_highestDecisionPoints = constructorDecisionPointCount;
228: }
229:
230: if (constructorEntry.m_decisionPoints >= getIntProperty("reportLevel")) {
231: // The {0} "{1}" has a cyclomatic complexity of {2}.
232: RuleContext ruleContext = (RuleContext) data;
233: String template = getMessage();
234: String constructorName = classEntry.m_node.getImage();
235: String complexity = String
236: .valueOf(constructorDecisionPointCount);
237: String[] args = { "constructor", constructorName,
238: complexity };
239: String message = MessageFormat.format(template, args);
240: int lineNumber = node.getBeginLine();
241: RuleViolation ruleViolation = createRuleViolation(
242: ruleContext, lineNumber, message);
243: ruleContext.getReport().addRuleViolation(ruleViolation);
244: }
245:
246: return data;
247: }
248:
249: /**
250: ***************************************************************************
251: ***************************************************************************
252: ***************************************************************************
253: */
254: private class Entry {
255: private SimpleNode m_node;
256: public int m_decisionPoints = 1;
257: public int m_highestDecisionPoints;
258: public int m_methodCount;
259:
260: /**
261: ***********************************************************************
262: *
263: * @param node
264: */
265: private Entry(SimpleNode node) {
266: m_node = node;
267: }
268: }
269: }
|