TCC metric
This commit is contained in:
@ -12,19 +12,19 @@ package net.sourceforge.pmd.lang.java.ast;
|
||||
public class JavaParserVisitorReducedAdapter extends JavaParserVisitorAdapter {
|
||||
|
||||
@Override
|
||||
public final Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
|
||||
public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
|
||||
return visit((ASTAnyTypeDeclaration) node, data);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final Object visit(ASTAnnotationTypeDeclaration node, Object data) {
|
||||
public Object visit(ASTAnnotationTypeDeclaration node, Object data) {
|
||||
return visit((ASTAnyTypeDeclaration) node, data);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final Object visit(ASTEnumDeclaration node, Object data) {
|
||||
public Object visit(ASTEnumDeclaration node, Object data) {
|
||||
return visit((ASTAnyTypeDeclaration) node, data);
|
||||
}
|
||||
|
||||
@ -35,13 +35,13 @@ public class JavaParserVisitorReducedAdapter extends JavaParserVisitorAdapter {
|
||||
|
||||
|
||||
@Override
|
||||
public final Object visit(ASTMethodDeclaration node, Object data) {
|
||||
public Object visit(ASTMethodDeclaration node, Object data) {
|
||||
return visit((ASTMethodOrConstructorDeclaration) node, data);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final Object visit(ASTConstructorDeclaration node, Object data) {
|
||||
public Object visit(ASTConstructorDeclaration node, Object data) {
|
||||
return visit((ASTMethodOrConstructorDeclaration) node, data);
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import net.sourceforge.pmd.lang.java.metrics.impl.LocMetric.LocClassMetric;
|
||||
import net.sourceforge.pmd.lang.java.metrics.impl.NcssMetric.NcssClassMetric;
|
||||
import net.sourceforge.pmd.lang.java.metrics.impl.NoamMetric;
|
||||
import net.sourceforge.pmd.lang.java.metrics.impl.NopaMetric;
|
||||
import net.sourceforge.pmd.lang.java.metrics.impl.TccMetric;
|
||||
import net.sourceforge.pmd.lang.java.metrics.impl.WmcMetric;
|
||||
import net.sourceforge.pmd.lang.java.metrics.impl.WocMetric;
|
||||
import net.sourceforge.pmd.lang.metrics.MetricKey;
|
||||
@ -60,13 +61,20 @@ public enum JavaClassMetricKey implements MetricKey<ASTAnyTypeDeclaration> {
|
||||
* @see NopaMetric
|
||||
*/
|
||||
NOAM(new NoamMetric()),
|
||||
|
||||
|
||||
/**
|
||||
* Weight of class.
|
||||
*
|
||||
* @see WocMetric
|
||||
*/
|
||||
WOC(new WocMetric());
|
||||
WOC(new WocMetric()),
|
||||
|
||||
/**
|
||||
* Tight Class Cohesion.
|
||||
*
|
||||
* @see TccMetric
|
||||
*/
|
||||
TCC(new TccMetric());
|
||||
|
||||
|
||||
private final JavaClassMetric calculator;
|
||||
|
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.metrics.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.metrics.impl.visitors.TccMethodPairVisitor;
|
||||
import net.sourceforge.pmd.lang.metrics.MetricOptions;
|
||||
|
||||
/**
|
||||
* Tight class cohesion.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class TccMetric extends AbstractJavaClassMetric {
|
||||
|
||||
|
||||
@Override
|
||||
public double computeFor(ASTAnyTypeDeclaration node, MetricOptions options) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Set<String>> usagesByMethod = (Map<String, Set<String>>) node.jjtAccept(new TccMethodPairVisitor(), null);
|
||||
|
||||
int numPairs = numMethodsRelatedByAttributeAccess(usagesByMethod);
|
||||
int maxPairs = maxMethodPairs(usagesByMethod.size());
|
||||
|
||||
double tcc = maxPairs == 0 ? 0. : numPairs / (double) maxPairs;
|
||||
|
||||
return tcc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the number of pairs of methods that use at least one attribute in common.
|
||||
*
|
||||
* @param usagesByMethod Map of method name to names of local attributes accessed
|
||||
*
|
||||
* @return The number of pairs
|
||||
*/
|
||||
private int numMethodsRelatedByAttributeAccess(Map<String, Set<String>> usagesByMethod) {
|
||||
List<String> methods = new ArrayList<>(usagesByMethod.keySet());
|
||||
int methodCount = methods.size();
|
||||
int pairs = 0;
|
||||
|
||||
if (methodCount > 1) {
|
||||
for (int i = 0; i < methodCount; i++) {
|
||||
for (int j = i + 1; j < methodCount; j++) {
|
||||
String firstMethodName = methods.get(i);
|
||||
String secondMethodName = methods.get(j);
|
||||
|
||||
if (!Collections.disjoint(usagesByMethod.get(firstMethodName),
|
||||
usagesByMethod.get(secondMethodName))) {
|
||||
pairs++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return pairs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the number of possible method pairs of two methods.
|
||||
*
|
||||
* @param methods Number of methods in the class
|
||||
*
|
||||
* @return Number of possible method pairs
|
||||
*/
|
||||
private int maxMethodPairs(int methods) {
|
||||
return methods * (methods - 1) / 2;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
/**
|
||||
* 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.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
|
||||
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.JavaParserVisitorReducedAdapter;
|
||||
import net.sourceforge.pmd.lang.java.symboltable.ClassScope;
|
||||
import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
|
||||
import net.sourceforge.pmd.lang.symboltable.Scope;
|
||||
|
||||
/**
|
||||
* Returns the map of method names to the set local attributes accessed when visiting a class.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class TccMethodPairVisitor extends JavaParserVisitorReducedAdapter {
|
||||
|
||||
/**
|
||||
* Collects for each method of the current class, which local attributes are accessed.
|
||||
*/
|
||||
Stack<Map<String, Set<String>>> methodAttributeAccess = new Stack<>();
|
||||
|
||||
/** The name of the current method. */
|
||||
private String currentMethodName;
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(ASTAnyTypeDeclaration node, Object data) {
|
||||
methodAttributeAccess.push(new HashMap<String, Set<String>>());
|
||||
super.visit(node, data);
|
||||
|
||||
methodAttributeAccess.peek().remove(null);
|
||||
return methodAttributeAccess.pop();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(ASTMethodDeclaration node, Object data) {
|
||||
|
||||
if (!node.isAbstract()) {
|
||||
currentMethodName = node.getQualifiedName().getOperation();
|
||||
methodAttributeAccess.peek().put(currentMethodName, new HashSet<String>());
|
||||
|
||||
super.visit(node, data);
|
||||
|
||||
currentMethodName = null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The primary expression node is used to detect access to attributes and method calls. If the access is not for a
|
||||
* foreign class, then the {@link #methodAttributeAccess} map is updated for the current method.
|
||||
*/
|
||||
@Override
|
||||
public Object visit(ASTPrimaryExpression node, Object data) {
|
||||
if (currentMethodName != null) {
|
||||
Set<String> methodAccess = methodAttributeAccess.peek().get(currentMethodName);
|
||||
String variableName = getVariableName(node);
|
||||
if (isLocalAttributeAccess(variableName, node.getScope())) {
|
||||
methodAccess.add(variableName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return super.visit(node, data);
|
||||
}
|
||||
|
||||
|
||||
private String getVariableName(ASTPrimaryExpression node) {
|
||||
ASTPrimaryPrefix prefix = node.getFirstDescendantOfType(ASTPrimaryPrefix.class);
|
||||
ASTName name = prefix.getFirstDescendantOfType(ASTName.class);
|
||||
|
||||
String variableName = null;
|
||||
|
||||
if (name != null) {
|
||||
int dotIndex = name.getImage().indexOf(".");
|
||||
if (dotIndex == -1) {
|
||||
variableName = name.getImage();
|
||||
} else {
|
||||
variableName = name.getImage().substring(0, dotIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return variableName;
|
||||
}
|
||||
|
||||
|
||||
private boolean isLocalAttributeAccess(String varName, Scope scope) {
|
||||
Scope currentScope = scope;
|
||||
|
||||
while (currentScope != null) {
|
||||
for (VariableNameDeclaration decl : currentScope.getDeclarations(VariableNameDeclaration.class).keySet()) {
|
||||
if (decl.getImage().equals(varName)) {
|
||||
if (currentScope instanceof ClassScope) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
currentScope = currentScope.getParent(); // WARNING doesn't consider inherited fields or static imports
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -36,6 +36,7 @@ public class AllMetricsTest extends SimpleAggregatorTst {
|
||||
addRule(RULESET, "NopaTest");
|
||||
addRule(RULESET, "NoamTest");
|
||||
addRule(RULESET, "WocTest");
|
||||
addRule(RULESET, "TccTest");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 TccTestRule extends AbstractMetricTestRule {
|
||||
|
||||
@Override
|
||||
protected JavaClassMetricKey getClassKey() {
|
||||
return JavaClassMetricKey.TCC;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected JavaOperationMetricKey getOpKey() {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<test-data>
|
||||
|
||||
<code-fragment id="full-example"><![CDATA[
|
||||
|
||||
// Data class from ArgoUML
|
||||
/**
|
||||
* A property that can be displayed and edited within a PropertyTable.
|
||||
*
|
||||
* @author Jeremy Jones
|
||||
**/
|
||||
public class Property implements Comparable {
|
||||
|
||||
private String _name;
|
||||
private Class _valueType;
|
||||
private Object _initialValue;
|
||||
private Object _currentValue;
|
||||
private Object[] _availableValues;
|
||||
|
||||
public Property(String name, Class valueType, Object initialValue) {
|
||||
this(name, valueType, initialValue, null);
|
||||
}
|
||||
|
||||
|
||||
public Property(String name, Class valueType, Object initialValue, Object[] values) {
|
||||
_name = name;
|
||||
_valueType = valueType;
|
||||
_initialValue = initialValue;
|
||||
_availableValues = values;
|
||||
_currentValue = _initialValue;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return _name;
|
||||
}
|
||||
|
||||
public Class getValueType() {
|
||||
return _valueType;
|
||||
}
|
||||
|
||||
public Object getInitialValue() {
|
||||
return _initialValue;
|
||||
}
|
||||
|
||||
public Object[] getAvailableValues() {
|
||||
return _availableValues;
|
||||
}
|
||||
|
||||
public Object getCurrentValue() {
|
||||
return _currentValue;
|
||||
}
|
||||
|
||||
public void setCurrentValue(Object value) {
|
||||
_currentValue = value;
|
||||
}
|
||||
|
||||
public int compareTo(Object o) {
|
||||
return _name.compareTo(((Property) o)._name);
|
||||
}
|
||||
}
|
||||
]]></code-fragment>
|
||||
|
||||
|
||||
<test-code>
|
||||
<description>Full example</description>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-messages>
|
||||
<message>'.Property' has value 9.52%.</message>
|
||||
</expected-messages>
|
||||
<code-ref id="full-example"/>
|
||||
</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>TCC doesn't support interfaces or annotations</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
public interface Foo {
|
||||
|
||||
public final int h;
|
||||
|
||||
@interface Tag {
|
||||
public static final int num = 0;
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
|
||||
</test-data>
|
@ -57,4 +57,10 @@
|
||||
metrics="true">
|
||||
</rule>
|
||||
|
||||
<rule name="TccTest"
|
||||
message = "''{0}'' has value {1}."
|
||||
class="net.sourceforge.pmd.lang.java.metrics.impl.TccTestRule"
|
||||
metrics="true">
|
||||
</rule>
|
||||
|
||||
</ruleset>
|
||||
|
Reference in New Issue
Block a user