/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.rules;
import net.sourceforge.pmd.AbstractRule;
import net.sourceforge.pmd.RuleContext;
import net.sourceforge.pmd.RuleViolation;
import net.sourceforge.pmd.ast.ASTBlockStatement;
import net.sourceforge.pmd.ast.ASTConstructorDeclaration;
import net.sourceforge.pmd.ast.ASTForStatement;
import net.sourceforge.pmd.ast.ASTIfStatement;
import net.sourceforge.pmd.ast.ASTInterfaceDeclaration;
import net.sourceforge.pmd.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.ast.ASTMethodDeclarator;
import net.sourceforge.pmd.ast.ASTSwitchLabel;
import net.sourceforge.pmd.ast.ASTSwitchStatement;
import net.sourceforge.pmd.ast.ASTUnmodifiedClassDeclaration;
import net.sourceforge.pmd.ast.ASTWhileStatement;
import net.sourceforge.pmd.ast.Node;
import net.sourceforge.pmd.ast.SimpleNode;
import java.text.MessageFormat;
import java.util.Stack;
/**
*
* @author Donald A. Leckie
* @since January 14, 2003
* @version $Revision: 1.12 $, $Date: 2004/04/15 19:04:26 $
*/
public class CyclomaticComplexityRule extends AbstractRule {
private Stack m_entryStack = new Stack();
/**
**************************************************************************
*
* @param node
* @param data
*
* @return
*/
public Object visit(ASTIfStatement node, Object data) {
Entry entry = (Entry) m_entryStack.peek();
entry.m_decisionPoints++;
super.visit(node, data);
return data;
}
/**
**************************************************************************
*
* @param node
* @param data
*
* @return
*/
public Object visit(ASTForStatement node, Object data) {
Entry entry = (Entry) m_entryStack.peek();
entry.m_decisionPoints++;
super.visit(node, data);
return data;
}
/**
**************************************************************************
*
* @param node
* @param data
*
* @return
*/
public Object visit(ASTSwitchStatement node, Object data) {
Entry entry = (Entry) m_entryStack.peek();
int childCount = node.jjtGetNumChildren();
int lastIndex = childCount - 1;
for (int n = 0; n < lastIndex; n++) {
Node childNode = node.jjtGetChild(n);
if (childNode instanceof ASTSwitchLabel) {
childNode = node.jjtGetChild(n + 1);
if (childNode instanceof ASTBlockStatement) {
entry.m_decisionPoints++;
}
}
}
super.visit(node, data);
return data;
}
/**
**************************************************************************
*
* @param node
* @param data
*
* @return
*/
public Object visit(ASTWhileStatement node, Object data) {
Entry entry = (Entry) m_entryStack.peek();
entry.m_decisionPoints++;
super.visit(node, data);
return data;
}
/**
**************************************************************************
*
* @param node
* @param data
*
* @return
*/
public Object visit(ASTUnmodifiedClassDeclaration node, Object data) {
m_entryStack.push(new Entry(node));
super.visit(node, data);
Entry classEntry = (Entry) m_entryStack.pop();
double decisionPoints = (double) classEntry.m_decisionPoints;
double methodCount = (double) classEntry.m_methodCount;
int complexityAverage = (methodCount == 0) ? 1 : (int) (Math.rint(decisionPoints / methodCount));
if ((complexityAverage >= getIntProperty("reportLevel")) || (classEntry.m_highestDecisionPoints >= getIntProperty("reportLevel"))) {
// The {0} "{1}" has a cyclomatic complexity of {2}.
RuleContext ruleContext = (RuleContext) data;
String template = getMessage();
String className = node.getImage();
String complexityHighest = String.valueOf(classEntry.m_highestDecisionPoints);
String complexity = String.valueOf(complexityAverage) + " (Highest = " + complexityHighest + ")";
String[] args = {"class", className, complexity};
String message = MessageFormat.format(template, args);
int lineNumber = node.getBeginLine();
RuleViolation ruleViolation = createRuleViolation(ruleContext, lineNumber, message);
ruleContext.getReport().addRuleViolation(ruleViolation);
}
return data;
}
/**
**************************************************************************
*
* @param node
* @param data
*
* @return
*/
public Object visit(ASTMethodDeclaration node, Object data) {
Node parentNode = node.jjtGetParent();
while (parentNode != null) {
if (parentNode instanceof ASTInterfaceDeclaration) {
return data;
}
parentNode = parentNode.jjtGetParent();
}
m_entryStack.push(new Entry(node));
super.visit(node, data);
Entry methodEntry = (Entry) m_entryStack.pop();
int methodDecisionPoints = methodEntry.m_decisionPoints;
Entry classEntry = (Entry) m_entryStack.peek();
classEntry.m_methodCount++;
classEntry.m_decisionPoints += methodDecisionPoints;
if (methodDecisionPoints > classEntry.m_highestDecisionPoints) {
classEntry.m_highestDecisionPoints = methodDecisionPoints;
}
ASTMethodDeclarator methodDeclarator = null;
for (int n = 0; n < node.jjtGetNumChildren(); n++) {
Node childNode = node.jjtGetChild(n);
if (childNode instanceof ASTMethodDeclarator) {
methodDeclarator = (ASTMethodDeclarator) childNode;
break;
}
}
if (methodEntry.m_decisionPoints >= getIntProperty("reportLevel")) {
// The {0} "{1}" has a cyclomatic complexity of {2}.
RuleContext ruleContext = (RuleContext) data;
String template = getMessage();
String methodName = (methodDeclarator == null) ? "" : methodDeclarator.getImage();
String complexity = String.valueOf(methodEntry.m_decisionPoints);
String[] args = {"method", methodName, complexity};
String message = MessageFormat.format(template, args);
int lineNumber = node.getBeginLine();
RuleViolation ruleViolation = createRuleViolation(ruleContext, lineNumber, message);
ruleContext.getReport().addRuleViolation(ruleViolation);
}
return data;
}
/**
**************************************************************************
*
* @param node
* @param data
*
* @return
*/
public Object visit(ASTConstructorDeclaration node, Object data) {
m_entryStack.push(new Entry(node));
super.visit(node, data);
Entry constructorEntry = (Entry) m_entryStack.pop();
int constructorDecisionPointCount = constructorEntry.m_decisionPoints;
Entry classEntry = (Entry) m_entryStack.peek();
classEntry.m_methodCount++;
classEntry.m_decisionPoints += constructorDecisionPointCount;
if (constructorDecisionPointCount > classEntry.m_highestDecisionPoints) {
classEntry.m_highestDecisionPoints = constructorDecisionPointCount;
}
if (constructorEntry.m_decisionPoints >= getIntProperty("reportLevel")) {
// The {0} "{1}" has a cyclomatic complexity of {2}.
RuleContext ruleContext = (RuleContext) data;
String template = getMessage();
String constructorName = classEntry.m_node.getImage();
String complexity = String.valueOf(constructorDecisionPointCount);
String[] args = {"constructor", constructorName, complexity};
String message = MessageFormat.format(template, args);
int lineNumber = node.getBeginLine();
RuleViolation ruleViolation = createRuleViolation(ruleContext, lineNumber, message);
ruleContext.getReport().addRuleViolation(ruleViolation);
}
return data;
}
/**
***************************************************************************
***************************************************************************
***************************************************************************
*/
private class Entry {
// ASTUnmodifedClassDeclaration or ASTMethodDeclarator or ASTConstructorDeclaration
private SimpleNode m_node;
public int m_decisionPoints = 1;
public int m_highestDecisionPoints;
public int m_methodCount;
/**
***********************************************************************
*
* @param node
*/
private Entry(SimpleNode node) {
m_node = node;
}
}
}