ATFD metric

This commit is contained in:
Clément Fournier
2017-08-20 01:40:11 +02:00
parent d14603078d
commit 2f678791cc
6 changed files with 338 additions and 22 deletions

View File

@ -4,15 +4,15 @@
package net.sourceforge.pmd.lang.java.metrics.impl;
import java.util.List;
import org.apache.commons.lang3.mutable.MutableInt;
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration;
import net.sourceforge.pmd.lang.java.ast.JavaQualifiedName;
import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSigMask;
import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSignature.Role;
import net.sourceforge.pmd.lang.java.metrics.signature.JavaSignature.Visibility;
import net.sourceforge.pmd.lang.java.metrics.JavaMetrics;
import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey;
import net.sourceforge.pmd.lang.java.metrics.impl.visitors.AtfdBaseVisitor;
import net.sourceforge.pmd.lang.metrics.MetricOptions;
import net.sourceforge.pmd.lang.metrics.ResultOption;
/**
* Access to Foreign Data. Quantifies the number of foreign fields accessed directly or via accessors.
@ -24,31 +24,19 @@ public final class AtfdMetric {
public static final class AtfdOperationMetric extends AbstractJavaOperationMetric {
@Override // TODO:cf
@Override
public double computeFor(ASTMethodOrConstructorDeclaration node, MetricOptions options) {
JavaOperationSigMask targetOps = new JavaOperationSigMask();
targetOps.restrictVisibilitiesTo(Visibility.PUBLIC);
targetOps.restrictRolesTo(Role.GETTER_OR_SETTER);
List<JavaQualifiedName> callQNames = findAllCalls(node);
int foreignCalls = 0;
for (JavaQualifiedName name : callQNames) {
if (getSignatureMatcher().hasMatchingSig(name, targetOps)) {
foreignCalls++;
}
}
return foreignCalls / callQNames.size();
return ((MutableInt) node.jjtAccept(new AtfdBaseVisitor(), new MutableInt(0))).getValue();
}
}
public static final class AtfdClassMetric extends AbstractJavaClassMetric {
@Override
public double computeFor(ASTAnyTypeDeclaration node, MetricOptions options) {
// TODO:cf
return 0;
// TODO maybe consider code outside methods
return JavaMetrics.get(JavaOperationMetricKey.ATFD, node, options, ResultOption.SUM);
}

View File

@ -0,0 +1,109 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.metrics.impl.visitors;
import java.util.List;
import org.apache.commons.lang3.mutable.MutableInt;
import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression;
import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
import net.sourceforge.pmd.lang.java.ast.ASTName;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter;
import net.sourceforge.pmd.util.StringUtil;
/**
* Computes Atfd.
*
* @author Clément Fournier
* @since 6.0.0
*/
public class AtfdBaseVisitor extends JavaParserVisitorAdapter {
@Override
public Object visit(ASTPrimaryExpression node, Object data) {
if (isForeignAttributeOrMethod(node) && (isAttributeAccess(node)
|| isMethodCall(node) && isForeignGetterSetterCall(node))) {
((MutableInt) data).increment();
}
return super.visit(node, data);
}
private boolean isForeignGetterSetterCall(ASTPrimaryExpression node) {
String methodOrAttributeName = getMethodOrAttributeName(node);
return methodOrAttributeName != null && StringUtil.startsWithAny(methodOrAttributeName, "get", "is", "set");
}
private boolean isMethodCall(ASTPrimaryExpression node) {
boolean result = false;
List<ASTPrimarySuffix> suffixes = node.findDescendantsOfType(ASTPrimarySuffix.class);
if (suffixes.size() == 1) {
result = suffixes.get(0).isArguments();
}
return result;
}
private boolean isForeignAttributeOrMethod(ASTPrimaryExpression node) {
boolean result;
String nameImage = getNameImage(node);
if (nameImage != null && (!nameImage.contains(".") || nameImage.startsWith("this."))) {
result = false;
} else if (nameImage == null && node.getFirstDescendantOfType(ASTPrimaryPrefix.class).usesThisModifier()) {
result = false;
} else if (nameImage == null && node.hasDecendantOfAnyType(ASTLiteral.class, ASTAllocationExpression.class)) {
result = false;
} else {
result = true;
}
return result;
}
private String getNameImage(ASTPrimaryExpression node) {
ASTPrimaryPrefix prefix = node.getFirstDescendantOfType(ASTPrimaryPrefix.class);
ASTName name = prefix.getFirstDescendantOfType(ASTName.class);
String image = null;
if (name != null) {
image = name.getImage();
}
return image;
}
private String getMethodOrAttributeName(ASTPrimaryExpression node) {
ASTPrimaryPrefix prefix = node.getFirstDescendantOfType(ASTPrimaryPrefix.class);
ASTName name = prefix.getFirstDescendantOfType(ASTName.class);
String methodOrAttributeName = null;
if (name != null) {
int dotIndex = name.getImage().indexOf(".");
if (dotIndex > -1) {
methodOrAttributeName = name.getImage().substring(dotIndex + 1);
}
}
return methodOrAttributeName;
}
private boolean isAttributeAccess(ASTPrimaryExpression node) {
return node.findDescendantsOfType(ASTPrimarySuffix.class).isEmpty();
}
}

View File

@ -37,6 +37,7 @@ public class AllMetricsTest extends SimpleAggregatorTst {
addRule(RULESET, "NoamTest");
addRule(RULESET, "WocTest");
addRule(RULESET, "TccTest");
addRule(RULESET, "AtfdTest");
}
}

View File

@ -0,0 +1,26 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.metrics.impl;
import net.sourceforge.pmd.lang.java.metrics.api.JavaClassMetricKey;
import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey;
/**
* @author Clément Fournier
* @since 6.0.0
*/
public class AtfdTestRule extends AbstractMetricTestRule {
@Override
protected JavaClassMetricKey getClassKey() {
return JavaClassMetricKey.ATFD;
}
@Override
protected JavaOperationMetricKey getOpKey() {
return JavaOperationMetricKey.ATFD;
}
}

View File

@ -0,0 +1,186 @@
<?xml version="1.0" encoding="UTF-8"?>
<test-data>
<code-fragment id="full-example"><![CDATA[
public class StatementAndBraceFinder extends JavaParserVisitorAdapter {
private static final Logger LOGGER = Logger.getLogger(StatementAndBraceFinder.class.getName());
private final DataFlowHandler dataFlowHandler;
private Structure dataFlow;
public StatementAndBraceFinder(DataFlowHandler dataFlowHandler) {
this.dataFlowHandler = dataFlowHandler;
}
public void buildDataFlowFor(JavaNode node) {
if (!(node instanceof ASTMethodDeclaration) && !(node instanceof ASTConstructorDeclaration)) {
throw new RuntimeException("Can't build a data flow for anything other than a method or a constructor");
}
this.dataFlow = new Structure(dataFlowHandler);
this.dataFlow.createStartNode(node.getBeginLine());
this.dataFlow.createNewNode(node);
node.jjtAccept(this, dataFlow);
this.dataFlow.createEndNode(node.getEndLine());
if (LOGGER.isLoggable(Level.FINE)) {
// TODO SRT Remove after development
LOGGER.fine("DataFlow is " + this.dataFlow.dump());
}
Linker linker = new Linker(dataFlowHandler, dataFlow.getBraceStack(), dataFlow.getContinueBreakReturnStack());
try {
linker.computePaths();
} catch (SequenceException | LinkerException e) {
e.printStackTrace();
}
}
private void tryToLog(String tag, NodeType type, Node node) {
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("pushOnStack " + tag + " " + type + ": line " + node.getBeginLine()
+ ", column " + node.getBeginColumn());
}
}
private void tryToLog(NodeType type, Node node) {
tryToLog("", type, node);
}
@Override
public Object visit(ASTStatementExpression node, Object data) {
if (!(data instanceof Structure)) {
return data;
}
Structure dataFlow = (Structure) data;
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("createNewNode ASTStatementExpression: line " + node.getBeginLine() + ", column "
+ node.getBeginColumn());
}
dataFlow.createNewNode(node);
return super.visit(node, data);
}
@Override
public Object visit(ASTVariableDeclarator node, Object data) {
if (!(data instanceof Structure)) {
return data;
}
Structure dataFlow = (Structure) data;
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("createNewNode ASTVariableDeclarator: line " + node.getBeginLine() + ", column "
+ node.getBeginColumn());
}
dataFlow.createNewNode(node);
return super.visit(node, data);
}
@Override
public Object visit(ASTExpression node, Object data) {
if (!(data instanceof Structure)) {
return data;
}
Structure dataFlow = (Structure) data;
String loggerTag = "parent";
Node parent = node.jjtGetParent();
// TODO what about throw stmts?
if (parent instanceof ASTIfStatement) {
dataFlow.createNewNode(node); // START IF
dataFlow.pushOnStack(NodeType.IF_EXPR, dataFlow.getLast());
tryToLog(loggerTag, NodeType.IF_EXPR, node);
} else if (parent instanceof ASTWhileStatement) {
dataFlow.createNewNode(node); // START WHILE
dataFlow.pushOnStack(NodeType.WHILE_EXPR, dataFlow.getLast());
tryToLog(loggerTag, NodeType.WHILE_EXPR, node);
} else if (parent instanceof ASTSwitchStatement) {
dataFlow.createNewNode(node); // START SWITCH
dataFlow.pushOnStack(NodeType.SWITCH_START, dataFlow.getLast());
tryToLog(loggerTag, NodeType.SWITCH_START, node);
} else if (parent instanceof ASTForStatement) {
dataFlow.createNewNode(node); // FOR EXPR
dataFlow.pushOnStack(NodeType.FOR_EXPR, dataFlow.getLast());
tryToLog(loggerTag, NodeType.FOR_EXPR, node);
} else if (parent instanceof ASTDoStatement) {
dataFlow.createNewNode(node); // DO EXPR
dataFlow.pushOnStack(NodeType.DO_EXPR, dataFlow.getLast());
tryToLog(loggerTag, NodeType.DO_EXPR, node);
} else if (parent instanceof ASTAssertStatement) {
dataFlow.createNewNode(node);
dataFlow.pushOnStack(NodeType.ASSERT_STATEMENT, dataFlow.getLast());
tryToLog(loggerTag, NodeType.ASSERT_STATEMENT, node);
}
return super.visit(node, data);
}
}
]]></code-fragment>
<test-code>
<description>Full example</description>
<expected-problems>1</expected-problems>
<expected-messages>
<message>'.StatementAndBraceFinder' has value 10.</message>
</expected-messages>
<code-ref id="full-example"/>
</test-code>
<test-code>
<description>Test empty class</description>
<expected-problems>1</expected-problems>
<rule-property name="reportClasses">false</rule-property>
<expected-messages>
<message>'.Foo' has value 0.</message>
</expected-messages>
<code><![CDATA[
public class Foo {
void bar() {
a.foo();
}
}
]]></code>
</test-code>
<test-code>
<description>Test empty class</description>
<expected-problems>1</expected-problems>
<expected-messages>
<message>'.Foo' has value 0.</message>
</expected-messages>
<code><![CDATA[
public class Foo {
}
]]></code>
</test-code>
<test-code>
<description>NOPA doesn't support enums, interfaces or annotations</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
public interface Foo {
public final int h;
enum Bar{
FOO;
public int bel = 0;
}
@interface Tag {
public static final int num = 0;
}
}
]]></code>
</test-code>
</test-data>

View File

@ -63,4 +63,10 @@
metrics="true">
</rule>
<rule name="AtfdTest"
message = "''{0}'' has value {1}."
class="net.sourceforge.pmd.lang.java.metrics.impl.AtfdTestRule"
metrics="true">
</rule>
</ruleset>