Reused existing npath implementation
This commit is contained in:
@ -4,7 +4,6 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.java.metrics.impl;
|
||||
|
||||
import static net.sourceforge.pmd.lang.java.metrics.impl.visitors.DefaultNpathVisitor.NpathDataNode;
|
||||
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.metrics.impl.visitors.DefaultNpathVisitor;
|
||||
@ -21,7 +20,6 @@ public class NpathMetric extends AbstractJavaOperationMetric {
|
||||
|
||||
@Override
|
||||
public double computeFor(ASTMethodOrConstructorDeclaration node, MetricVersion version) {
|
||||
NpathDataNode root = (NpathDataNode) node.jjtAccept(new DefaultNpathVisitor(), new NpathDataNode());
|
||||
return root.getComplexity();
|
||||
return (Integer) node.jjtAccept(new DefaultNpathVisitor(), null);
|
||||
}
|
||||
}
|
||||
|
@ -12,132 +12,183 @@ 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.ast.JavaParserVisitorAdapter;
|
||||
import net.sourceforge.pmd.lang.java.metrics.impl.CycloMetric;
|
||||
|
||||
/**
|
||||
* Visitor for the default n-path complexity version. It takes a root {@link NpathDataNode} as data.
|
||||
* Visitor for the default n-path complexity version.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
* @author Jason Bennett
|
||||
*/
|
||||
public class DefaultNpathVisitor extends JavaParserVisitorAdapter {
|
||||
|
||||
/* Multiplies the complexity of the children of this node. */
|
||||
private int multiplyChildrenComplexities(JavaNode node, Object data) {
|
||||
int product = 1;
|
||||
|
||||
for (int i = 0; i < node.jjtGetNumChildren(); i++) {
|
||||
JavaNode n = (JavaNode) node.jjtGetChild(i);
|
||||
product *= (Integer) n.jjtAccept(this, data);
|
||||
}
|
||||
|
||||
return product;
|
||||
}
|
||||
|
||||
|
||||
/* Sums the complexity of the children of the node. */
|
||||
private int sumChildrenComplexities(JavaNode node, Object data) {
|
||||
int sum = 0;
|
||||
|
||||
for (int i = 0; i < node.jjtGetNumChildren(); i++) {
|
||||
JavaNode n = (JavaNode) node.jjtGetChild(i);
|
||||
sum += (Integer) n.jjtAccept(this, data);
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(ASTMethodDeclaration node, Object data) {
|
||||
return multiplyChildrenComplexities(node, data);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(JavaNode node, Object data) {
|
||||
return multiplyChildrenComplexities(node, data);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(ASTIfStatement node, Object data) {
|
||||
int boolComplexity = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
|
||||
// (npath of if + npath of else (or 1) + bool_comp of if) * npath of next
|
||||
|
||||
NpathDataNode n = ((NpathDataNode) data).addAndGetChild(boolComplexity);
|
||||
super.visit(node, n);
|
||||
return n.parent;
|
||||
List<JavaNode> statementChildren = new ArrayList<>();
|
||||
for (int i = 0; i < node.jjtGetNumChildren(); i++) {
|
||||
if (node.jjtGetChild(i) instanceof ASTStatement) {
|
||||
statementChildren.add((JavaNode) node.jjtGetChild(i));
|
||||
}
|
||||
}
|
||||
|
||||
// add path for not taking if
|
||||
int complexity = node.hasElse() ? 0 : 1;
|
||||
|
||||
for (JavaNode element : statementChildren) {
|
||||
complexity += (Integer) element.jjtAccept(this, data);
|
||||
}
|
||||
|
||||
int boolCompIf = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
|
||||
return boolCompIf + complexity;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(ASTWhileStatement node, Object data) {
|
||||
int boolComplexity = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
|
||||
// (npath of while + bool_comp of while + 1) * npath of next
|
||||
|
||||
NpathDataNode n = ((NpathDataNode) data).addAndGetChild(boolComplexity);
|
||||
super.visit(node, n);
|
||||
return n.parent;
|
||||
}
|
||||
int boolCompWhile = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
|
||||
|
||||
int nPathWhile = (Integer) node.getFirstChildOfType(ASTStatement.class).jjtAccept(this, data);
|
||||
|
||||
@Override
|
||||
public Object visit(ASTForStatement node, Object data) {
|
||||
int boolComplexity = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
|
||||
|
||||
NpathDataNode n = ((NpathDataNode) data).addAndGetChild(boolComplexity);
|
||||
super.visit(node, n);
|
||||
return n.parent;
|
||||
return boolCompWhile + nPathWhile + 1;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(ASTDoStatement node, Object data) {
|
||||
int boolComplexity = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
|
||||
// (npath of do + bool_comp of do + 1) * npath of next
|
||||
|
||||
NpathDataNode n = ((NpathDataNode) data).addAndGetChild(boolComplexity);
|
||||
super.visit(node, n);
|
||||
return n.parent;
|
||||
int boolCompDo = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
|
||||
|
||||
int nPathDo = (Integer) node.getFirstChildOfType(ASTStatement.class).jjtAccept(this, data);
|
||||
|
||||
return boolCompDo + nPathDo + 1;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(ASTConditionalExpression node, Object data) {
|
||||
int boolComplexity = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
|
||||
public Object visit(ASTForStatement node, Object data) {
|
||||
// (npath of for + bool_comp of for + 1) * npath of next
|
||||
|
||||
NpathDataNode n = ((NpathDataNode) data).addAndGetChild(boolComplexity);
|
||||
super.visit(node, n);
|
||||
return n.parent;
|
||||
int boolCompFor = CycloMetric.booleanExpressionComplexity(node.getFirstDescendantOfType(ASTExpression.class));
|
||||
|
||||
int nPathFor = (Integer) node.getFirstChildOfType(ASTStatement.class).jjtAccept(this, data);
|
||||
|
||||
return boolCompFor + nPathFor + 1;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(ASTTryStatement node, Object data) {
|
||||
int boolComplexity = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
|
||||
public Object visit(ASTReturnStatement node, Object data) {
|
||||
// return statements are valued at 1, or the value of the boolean expression
|
||||
|
||||
NpathDataNode n = ((NpathDataNode) data).addAndGetChild(boolComplexity);
|
||||
super.visit(node, n);
|
||||
return n.parent;
|
||||
ASTExpression expr = node.getFirstChildOfType(ASTExpression.class);
|
||||
|
||||
if (expr == null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int boolCompReturn = CycloMetric.booleanExpressionComplexity(expr);
|
||||
int conditionalExpressionComplexity = multiplyChildrenComplexities(expr, data);
|
||||
|
||||
if (conditionalExpressionComplexity > 1) {
|
||||
boolCompReturn += conditionalExpressionComplexity;
|
||||
}
|
||||
|
||||
return (boolCompReturn > 0) ? boolCompReturn : 1;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(ASTSwitchStatement node, Object data) {
|
||||
int boolComplexity = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
|
||||
// bool_comp of switch + sum(npath(case_range))
|
||||
|
||||
NpathDataNode n = ((NpathDataNode) data).addAndGetChild(boolComplexity);
|
||||
super.visit(node, n);
|
||||
return n.parent;
|
||||
int boolCompSwitch = CycloMetric.booleanExpressionComplexity(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 boolCompSwitch + npath;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Heap structure used as the data of the visitor. Every node corresponds to a control flow statement. A control
|
||||
* flow statement nested inside another is represented by a node that's a children of the other.
|
||||
*
|
||||
* <p>The complexity of a node is given by multiplying the complexities of its children plus 1. The complexity of a
|
||||
* leaf is the boolean complexity of the guard condition of the statement. The root node bears the complexity of the
|
||||
* method.
|
||||
@Override
|
||||
public Object visit(ASTConditionalExpression node, Object data) {
|
||||
return node.isTernary() ? sumChildrenComplexities(node, data) + 2 : 1;
|
||||
}
|
||||
|
||||
|
||||
@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.
|
||||
*/
|
||||
public static class NpathDataNode {
|
||||
|
||||
private List<NpathDataNode> children = new ArrayList<>();
|
||||
private NpathDataNode parent;
|
||||
|
||||
private int booleanComplexity;
|
||||
|
||||
|
||||
/** Creates a root node. */
|
||||
public NpathDataNode() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
private NpathDataNode(int booleanComplexity, NpathDataNode parent) {
|
||||
this.booleanComplexity = booleanComplexity;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
|
||||
NpathDataNode addAndGetChild(int booleanComplexity) {
|
||||
NpathDataNode newChild = new NpathDataNode(booleanComplexity, this);
|
||||
children.add(newChild);
|
||||
return newChild;
|
||||
}
|
||||
|
||||
|
||||
/** Gets the complexity of this node. */
|
||||
public int getComplexity() {
|
||||
int complexity = 1 + booleanComplexity;
|
||||
for (NpathDataNode child : children) {
|
||||
complexity *= child.getComplexity();
|
||||
}
|
||||
|
||||
return complexity;
|
||||
}
|
||||
return sumChildrenComplexities(node, data);
|
||||
}
|
||||
}
|
||||
|
@ -3,40 +3,46 @@
|
||||
|
||||
<code-fragment id="full-example"><![CDATA[
|
||||
public class Foo {
|
||||
public static int bar() {
|
||||
try{
|
||||
if (true) {
|
||||
public static void bar() {
|
||||
boolean a, b = true;
|
||||
try { // 2 * 2 + 2
|
||||
if (true) { // 2
|
||||
List buz = new ArrayList();
|
||||
}
|
||||
for(int i = 0; i < 19; i++) {
|
||||
|
||||
for(int i = 0; i < 19; i++) { // 2
|
||||
List buz = new ArrayList();
|
||||
}
|
||||
int j = 0;
|
||||
if (true) {
|
||||
j = 10;
|
||||
}
|
||||
while (j++ < 20) {
|
||||
List buz = new ArrayList();
|
||||
}
|
||||
if (true) {
|
||||
j = 21;
|
||||
}
|
||||
if(false) {
|
||||
j = 0;
|
||||
}
|
||||
do {
|
||||
List buz = new ArrayList();
|
||||
} while (j++ < 30);
|
||||
} catch(Exception e) {
|
||||
if (true) {
|
||||
if (true) { // 2
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (true) {
|
||||
return 1;
|
||||
} else {
|
||||
return 2;
|
||||
int j = 0;
|
||||
if (true || a && b) { // 4
|
||||
j = 10;
|
||||
return;
|
||||
}
|
||||
|
||||
while (j++ < 20) { // 2
|
||||
List buz = new ArrayList();
|
||||
}
|
||||
|
||||
switch(j) { // 7
|
||||
case 1:
|
||||
case 2: break;
|
||||
case 3: j = 5; break;
|
||||
case 4: if (b && a) { bar(); } break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
do { // 3
|
||||
List buz = new ArrayList();
|
||||
} while (a && j++ < 30);
|
||||
|
||||
if (b) { // 2
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -47,7 +53,7 @@ public class Foo {
|
||||
<description>Full example</description>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-messages>
|
||||
<message>'.Foo#bar()' has value 10.</message>
|
||||
<message>'.Foo#bar()' has value 2016.</message>
|
||||
</expected-messages>
|
||||
<code-ref id="full-example"/>
|
||||
</test-code>
|
||||
@ -130,17 +136,11 @@ public class Foo {
|
||||
class Foo {
|
||||
void bar() {
|
||||
boolean a, b;
|
||||
if (a) {
|
||||
if (a) {}
|
||||
else {}
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
if (b) {
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
if (b) {}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
@ -156,15 +156,10 @@ public class Foo {
|
||||
class Foo {
|
||||
void bar() {
|
||||
boolean a, b;
|
||||
if (a) {
|
||||
if (a) {}
|
||||
else {}
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
if (b) {
|
||||
|
||||
}
|
||||
if (b) {}
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
@ -180,77 +175,69 @@ public class Foo {
|
||||
class Foo {
|
||||
void bar() {
|
||||
boolean a, b;
|
||||
if (a) { // 3
|
||||
if (b) {
|
||||
if (a) {
|
||||
if (b) {}
|
||||
} else {}
|
||||
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
if (b) { // times two
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
|
||||
|
||||
|
||||
<test-code>
|
||||
<description>ok</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
public class Foo {
|
||||
public static void bar() {
|
||||
if (true) {List buz = new ArrayList();}
|
||||
if (b) {}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
|
||||
|
||||
<test-code>
|
||||
<description>fail, with minimum</description>
|
||||
<rule-property name="reportLevel">1</rule-property>
|
||||
<description>Full switch</description>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-messages>
|
||||
<message>The method 'bar()' has value 2</message>
|
||||
<message>'.Foo#bar()' has value 7.</message>
|
||||
</expected-messages>
|
||||
<code><![CDATA[
|
||||
public class Foo {
|
||||
public static void bar() {
|
||||
if (true) {List buz = new ArrayList();}
|
||||
class Foo {
|
||||
void bar() {
|
||||
boolean a, b;
|
||||
int j = 23;
|
||||
switch(j) {
|
||||
case 1:
|
||||
case 2: break;
|
||||
case 3: j = 5; break;
|
||||
case 4: if (b && a) { bar(); } break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
|
||||
<test-code>
|
||||
<description>Boolean operators</description>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-messages>
|
||||
<message>'.Foo#bar()' has value 4.</message>
|
||||
</expected-messages>
|
||||
<code><![CDATA[
|
||||
class Foo {
|
||||
void bar() {
|
||||
if (a && b || c);
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
|
||||
|
||||
<code-fragment id="bug3484404"><![CDATA[
|
||||
class Bar
|
||||
{
|
||||
public void x(boolean x, boolean y)
|
||||
{
|
||||
z(
|
||||
(x ? 1 : 2),
|
||||
(y ? 3 : 4)
|
||||
);
|
||||
public void x(boolean x, boolean y) {
|
||||
z((x ? 1 : 2), (y ? 3 : 4));
|
||||
}
|
||||
|
||||
public int y(boolean x, boolean y)
|
||||
{
|
||||
return z(
|
||||
(x ? 1 : 2),
|
||||
(y ? 3 : 4)
|
||||
);
|
||||
public int y(boolean x, boolean y) {
|
||||
return z((x ? 1 : 2), (y ? 3 : 4));
|
||||
}
|
||||
|
||||
public int z(int x, int y)
|
||||
{
|
||||
public int z(int x, int y) {
|
||||
return x + y;
|
||||
}
|
||||
}
|
||||
@ -258,17 +245,11 @@ class Bar
|
||||
|
||||
<test-code>
|
||||
<description>test case for bug 3484404 (Invalid NPath calculation in return statement)</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code-ref id="bug3484404"/>
|
||||
</test-code>
|
||||
|
||||
<test-code>
|
||||
<description>test case for bug 3484404 (Invalid NPath calculation in return statement) with minimum 25
|
||||
</description>
|
||||
<expected-problems>2</expected-problems>
|
||||
<expected-problems>3</expected-problems>
|
||||
<expected-messages>
|
||||
<message>The method x() has an NPath complexity of 25</message>
|
||||
<message>The method y() has an NPath complexity of 25</message>
|
||||
<message>'.Bar#x(boolean, boolean)' has value 25.</message>
|
||||
<message>'.Bar#y(boolean, boolean)' has value 25.</message>
|
||||
<message>'.Bar#z(int, int)' has value 1.</message>
|
||||
</expected-messages>
|
||||
<code-ref id="bug3484404"/>
|
||||
</test-code>
|
||||
|
Reference in New Issue
Block a user