ATFD metric
This commit is contained in:
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -37,6 +37,7 @@ public class AllMetricsTest extends SimpleAggregatorTst {
|
||||
addRule(RULESET, "NoamTest");
|
||||
addRule(RULESET, "WocTest");
|
||||
addRule(RULESET, "TccTest");
|
||||
addRule(RULESET, "AtfdTest");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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>
|
@ -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>
|
||||
|
Reference in New Issue
Block a user