/** * 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()), }; } }