/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.rule.codesize;
import java.util.ArrayList;
import java.util.List;
import net.sourceforge.pmd.lang.java.ast.ASTConditionalAndExpression;
import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression;
import net.sourceforge.pmd.lang.java.ast.ASTConditionalOrExpression;
import net.sourceforge.pmd.lang.java.ast.ASTDoStatement;
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
import net.sourceforge.pmd.lang.java.ast.ASTStatement;
import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
import net.sourceforge.pmd.lang.java.ast.ASTTryStatement;
import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement;
import net.sourceforge.pmd.lang.java.ast.JavaNode;
import net.sourceforge.pmd.lang.java.rule.AbstractStatisticalJavaRule;
import net.sourceforge.pmd.stat.DataPoint;
import net.sourceforge.pmd.util.NumericConstants;
/**
* NPath complexity is a measurement of the acyclic execution paths through a
* function. See Nejmeh, Communications of the ACM Feb 1988 pp 188-200.
*
* @author Jason Bennett
*/
public class NPathComplexityRule extends AbstractStatisticalJavaRule {
public NPathComplexityRule() {
super();
setProperty(MINIMUM_DESCRIPTOR, 200d);
}
private int complexityMultipleOf(JavaNode node, int npathStart, Object data) {
int npath = npathStart;
JavaNode n;
for (int i = 0; i < node.jjtGetNumChildren(); i++) {
n = (JavaNode) node.jjtGetChild(i);
npath *= (Integer) n.jjtAccept(this, data);
}
return npath;
}
private int complexitySumOf(JavaNode node, int npathStart, Object data) {
int npath = npathStart;
JavaNode n;
for (int i = 0; i < node.jjtGetNumChildren(); i++) {
n = (JavaNode) node.jjtGetChild(i);
npath += (Integer) n.jjtAccept(this, data);
}
return npath;
}
@Override
public Object visit(ASTMethodDeclaration node, Object data) {
int npath = complexityMultipleOf(node, 1, data);
DataPoint point = new DataPoint();
point.setNode(node);
point.setScore(1.0 * npath);
point.setMessage(getMessage());
addDataPoint(point);
return Integer.valueOf(npath);
}
@Override
public Object visit(JavaNode node, Object data) {
int npath = complexityMultipleOf(node, 1, data);
return Integer.valueOf(npath);
}
@Override
public Object visit(ASTIfStatement node, Object data) {
// (npath of if + npath of else (or 1) + bool_comp of if) * npath of
// next
List<JavaNode> statementChildren = new ArrayList<>();
for (int i = 0; i < node.jjtGetNumChildren(); i++) {
if (node.jjtGetChild(i).getClass() == ASTStatement.class) {
statementChildren.add((JavaNode) node.jjtGetChild(i));
}
}
if (statementChildren.isEmpty() || statementChildren.size() == 1 && node.hasElse()
|| statementChildren.size() != 1 && !node.hasElse()) {
throw new IllegalStateException("If node has wrong number of children");
}
// add path for not taking if
int complexity = 0;
if (!node.hasElse()) {
complexity++;
}
for (JavaNode element : statementChildren) {
complexity += (Integer) element.jjtAccept(this, data);
}
int boolCompIf = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
return Integer.valueOf(boolCompIf + complexity);
}
@Override
public Object visit(ASTWhileStatement node, Object data) {
// (npath of while + bool_comp of while + 1) * npath of next
int boolCompWhile = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
Integer nPathWhile = (Integer) ((JavaNode) node.getFirstChildOfType(ASTStatement.class)).jjtAccept(this, data);
return Integer.valueOf(boolCompWhile + nPathWhile + 1);
}
@Override
public Object visit(ASTDoStatement node, Object data) {
// (npath of do + bool_comp of do + 1) * npath of next
int boolCompDo = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
Integer nPathDo = (Integer) ((JavaNode) node.getFirstChildOfType(ASTStatement.class)).jjtAccept(this, data);
return Integer.valueOf(boolCompDo + nPathDo + 1);
}
@Override
public Object visit(ASTForStatement node, Object data) {
// (npath of for + bool_comp of for + 1) * npath of next
int boolCompFor = sumExpressionComplexity(node.getFirstDescendantOfType(ASTExpression.class));
Integer nPathFor = (Integer) ((JavaNode) node.getFirstChildOfType(ASTStatement.class)).jjtAccept(this, data);
return Integer.valueOf(boolCompFor + nPathFor + 1);
}
@Override
public Object visit(ASTReturnStatement node, Object data) {
// return statements are valued at 1, or the value of the boolean
// expression
ASTExpression expr = node.getFirstChildOfType(ASTExpression.class);
if (expr == null) {
return NumericConstants.ONE;
}
int boolCompReturn = sumExpressionComplexity(expr);
int conditionalExpressionComplexity = complexityMultipleOf(expr, 1, data);
if (conditionalExpressionComplexity > 1) {
boolCompReturn += conditionalExpressionComplexity;
}
if (boolCompReturn > 0) {
return Integer.valueOf(boolCompReturn);
}
return NumericConstants.ONE;
}
@Override
public Object visit(ASTSwitchStatement node, Object data) {
// bool_comp of switch + sum(npath(case_range))
int boolCompSwitch = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
int npath = 0;
int caseRange = 0;
for (int i = 0; i < node.jjtGetNumChildren(); i++) {
JavaNode n = (JavaNode) node.jjtGetChild(i);
// Fall-through labels count as 1 for complexity
if (n instanceof ASTSwitchLabel) {
npath += caseRange;
caseRange = 1;
} else {
Integer complexity = (Integer) n.jjtAccept(this, data);
caseRange *= complexity;
}
}
// add in npath of last label
npath += caseRange;
return Integer.valueOf(boolCompSwitch + npath);
}
@Override
public Object visit(ASTTryStatement node, Object data) {
/*
* This scenario was not addressed by the original paper. Based on the
* principles outlined in the paper, as well as the Checkstyle NPath
* implementation, this code will add the complexity of the try to the
* complexities of the catch and finally blocks.
*/
int npath = complexitySumOf(node, 0, data);
return Integer.valueOf(npath);
}
@Override
public Object visit(ASTConditionalExpression node, Object data) {
if (node.isTernary()) {
int npath = complexitySumOf(node, 0, data);
npath += 2;
return Integer.valueOf(npath);
}
return NumericConstants.ONE;
}
/**
* Calculate the boolean complexity of the given expression. NPath boolean
* complexity is the sum of && and || tokens. This is calculated by summing
* the number of children of the &&'s (minus one) and the children of the
* ||'s (minus one).
*
* <p>Note that this calculation applies to Cyclomatic Complexity as well.</p>
*
* @param expr
* control structure expression
* @return complexity of the boolean expression
*/
public static int sumExpressionComplexity(ASTExpression expr) {
if (expr == null) {
return 0;
}
List<ASTConditionalAndExpression> andNodes = expr.findDescendantsOfType(ASTConditionalAndExpression.class);
List<ASTConditionalOrExpression> orNodes = expr.findDescendantsOfType(ASTConditionalOrExpression.class);
int children = 0;
for (ASTConditionalOrExpression element : orNodes) {
children += element.jjtGetNumChildren();
children--;
}
for (ASTConditionalAndExpression element : andNodes) {
children += element.jjtGetNumChildren();
children--;
}
return children;
}
@Override
public Object[] getViolationParameters(DataPoint point) {
return new String[] { ((ASTMethodDeclaration) point.getNode()).getMethodName(),
String.valueOf((int) point.getScore()), };
}
}