Abstracted Computer, Memoizer, ProjectMirror

This commit is contained in:
oowekyala
2017-07-28 20:00:02 +02:00
parent 05f2ffce4c
commit 63e51152f5
11 changed files with 317 additions and 193 deletions

View File

@ -9,11 +9,13 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
import net.sourceforge.pmd.lang.java.ast.JavaQualifiedName;
import net.sourceforge.pmd.lang.java.metrics.signature.FieldSigMask;
import net.sourceforge.pmd.lang.java.metrics.signature.JavaFieldSignature;
import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSignature;
import net.sourceforge.pmd.lang.java.metrics.signature.OperationSigMask;
import net.sourceforge.pmd.lang.metrics.MetricMemoizer;
/**
* Statistics about a class, enum, interface, or annotation. Stores information about the contained members and their
@ -26,7 +28,7 @@ import net.sourceforge.pmd.lang.java.metrics.signature.OperationSigMask;
*
* @author Clément Fournier
*/
/* default */ class ClassStats extends Memoizer {
/* default */ class ClassStats extends MetricMemoizer<ASTAnyTypeDeclaration> {
private Map<JavaOperationSignature, Map<String, OperationStats>> operations = new HashMap<>();
private Map<JavaFieldSignature, Set<String>> fields = new HashMap<>();

View File

@ -10,131 +10,25 @@ import java.util.List;
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration;
import net.sourceforge.pmd.lang.metrics.ParameterizedMetricKey;
import net.sourceforge.pmd.lang.metrics.api.MetricKey;
import net.sourceforge.pmd.lang.metrics.api.MetricVersion;
import net.sourceforge.pmd.lang.metrics.api.ResultOption;
import net.sourceforge.pmd.lang.metrics.AbstractMetricsComputer;
/**
* Computes a metric. This relieves ClassStats and OperationStats from that responsibility.
*
* @author Clément Fournier
*/
public class JavaMetricsComputer {
public class JavaMetricsComputer extends AbstractMetricsComputer<ASTAnyTypeDeclaration, ASTMethodOrConstructorDeclaration> {
static final JavaMetricsComputer INSTANCE = new JavaMetricsComputer();
private JavaMetricsComputer() {
}
/**
* Computes the value of a metric for a class and stores the result in the ClassStats object.
*
* @param key The class metric to compute
* @param node The AST node of the class
* @param force Force the recomputation; if unset, we'll first check for a memoized result
* @param version The version of the metric to compute
* @param memoizer The object memoizing the results
*
* @return The result of the computation, or {@code Double.NaN} if it couldn't be performed
*/
/* default */ double compute(MetricKey<ASTAnyTypeDeclaration> key, ASTAnyTypeDeclaration node, boolean force,
MetricVersion version, ClassStats memoizer) {
ParameterizedMetricKey paramKey = ParameterizedMetricKey.getInstance(key, version);
// if memo.get(key) == null then the metric has never been computed. NaN is a valid value.
Double prev = memoizer.getMemo(paramKey);
if (!force && prev != null) {
return prev;
}
double val = key.getCalculator().computeFor(node, version);
memoizer.memoize(paramKey, val);
return val;
}
/**
* Computes the value of a metric for an operation and stores the result in the OperationStats object.
*
* @param key The operation metric to compute
* @param node The AST node of the operation
* @param force Force the recomputation; if unset, we'll first check for a memoized result
* @param version The version of the metric to compute
* @param memoizer The object memoizing the results
*
* @return The result of the computation, or {@code Double.NaN} if it couldn't be performed
*/
/* default */ double compute(MetricKey<ASTMethodOrConstructorDeclaration> key, ASTMethodOrConstructorDeclaration node,
boolean force, MetricVersion version, OperationStats memoizer) {
ParameterizedMetricKey paramKey = ParameterizedMetricKey.getInstance(key, version);
Double prev = memoizer.getMemo(paramKey);
if (!force && prev != null) {
return prev;
}
double val = key.getCalculator().computeFor(node, version);
memoizer.memoize(paramKey, val);
return val;
}
/**
* Computes an aggregate result using a ResultOption.
*
* @param key The class metric to compute
* @param node The AST node of the class
* @param force Force the recomputation; if unset, we'll first check for a memoized result
* @param version The version of the metric
* @param option The type of result to compute
* @param stats The ClassStats storing info about the class
*
* @return The result of the computation, or {@code Double.NaN} if it couldn't be performed
*/
/* default */ double computeWithResultOption(MetricKey<ASTMethodOrConstructorDeclaration> key, ASTAnyTypeDeclaration node,
boolean force, MetricVersion version, ResultOption option, ClassStats stats) {
List<ASTMethodOrConstructorDeclaration> ops = findOperations(node);
List<Double> values = new ArrayList<>();
for (ASTMethodOrConstructorDeclaration op : ops) {
if (key.supports(op)) {
OperationStats opStats = stats.getOperationStats(op.getQualifiedName().getOperation(),
op.getSignature());
double val = this.compute(key, op, force, version, opStats);
if (val != Double.NaN) {
values.add(val);
}
}
}
// FUTURE use streams to do that when we upgrade the compiler to 1.8
switch (option) {
case SUM:
return sum(values);
case HIGHEST:
return highest(values);
case AVERAGE:
return average(values);
default:
return Double.NaN;
}
}
/**
* Finds the declaration nodes of all methods or constructors that are declared inside a class.
*
* @param node The class in which to look for.
*
* @return The list of all operations declared inside the specified class.
*
* TODO:cf this one is computed every time
*/
private static List<ASTMethodOrConstructorDeclaration> findOperations(ASTAnyTypeDeclaration node) {
@Override
protected List<ASTMethodOrConstructorDeclaration> findOperations(ASTAnyTypeDeclaration node) {
List<ASTMethodOrConstructorDeclaration> operations = new ArrayList<>();
@ -146,30 +40,4 @@ public class JavaMetricsComputer {
return operations;
}
private static double sum(List<Double> values) {
double sum = 0;
for (double val : values) {
sum += val;
}
return sum;
}
private static double highest(List<Double> values) {
double highest = Double.NEGATIVE_INFINITY;
for (double val : values) {
if (val > highest) {
highest = val;
}
}
return highest == Double.NEGATIVE_INFINITY ? 0 : highest;
}
private static double average(List<Double> values) {
return sum(values) / values.size();
}
}

View File

@ -13,7 +13,7 @@ import net.sourceforge.pmd.lang.metrics.api.MetricVersion;
import net.sourceforge.pmd.lang.metrics.api.ResultOption;
/**
* Facade of the Java metrics framework.
* Inner façade of the Java metrics framework. The static façade delegates to an instance of this class.
*
* @author Clément Fournier
*/
@ -48,7 +48,9 @@ class JavaMetricsFacade {
*
* @return The value of the metric, or {@code Double.NaN} if the value couln't be computed
*/
public double computeForType(MetricKey<ASTAnyTypeDeclaration> key, ASTAnyTypeDeclaration node, MetricVersion version) {
double computeForType(MetricKey<ASTAnyTypeDeclaration> key, ASTAnyTypeDeclaration node, MetricVersion version) {
checkKeyNotNull(key);
if (!key.supports(node)) {
return Double.NaN;
@ -58,7 +60,7 @@ class JavaMetricsFacade {
ClassStats memoizer = topLevelPackageStats.getClassStats(node.getQualifiedName(), false);
return memoizer == null ? Double.NaN
: JavaMetricsComputer.INSTANCE.compute(key, node, false, safeVersion, memoizer);
: JavaMetricsComputer.INSTANCE.computeForType(key, node, false, safeVersion, memoizer);
}
@ -71,8 +73,10 @@ class JavaMetricsFacade {
*
* @return The value of the metric, or {@code Double.NaN} if the value couln't be computed
*/
public double computeForOperation(MetricKey<ASTMethodOrConstructorDeclaration> key, ASTMethodOrConstructorDeclaration node,
MetricVersion version) {
double computeForOperation(MetricKey<ASTMethodOrConstructorDeclaration> key, ASTMethodOrConstructorDeclaration node,
MetricVersion version) {
checkKeyNotNull(key);
if (!key.supports(node)) {
return Double.NaN;
@ -86,7 +90,8 @@ class JavaMetricsFacade {
: container.getOperationStats(qname.getOperation(), node.getSignature());
return memoizer == null ? Double.NaN
: JavaMetricsComputer.INSTANCE.compute(key, node, false, safeVersion, memoizer);
: JavaMetricsComputer.INSTANCE.computeForOperation(key, node, false, safeVersion,
memoizer);
}
@ -103,18 +108,25 @@ class JavaMetricsFacade {
* @return The value of the metric, or {@code Double.NaN} if the value couln't be computed or {@code option} is
* {@code null}
*/
public double computeWithResultOption(MetricKey<ASTMethodOrConstructorDeclaration> key, ASTAnyTypeDeclaration node,
MetricVersion version, ResultOption option) {
double computeWithResultOption(MetricKey<ASTMethodOrConstructorDeclaration> key, ASTAnyTypeDeclaration node,
MetricVersion version, ResultOption option) {
checkKeyNotNull(key);
if (option == null) {
throw new IllegalArgumentException("The result option may not be null");
}
MetricVersion safeVersion = (version == null) ? Version.STANDARD : version;
ClassStats memoizer = topLevelPackageStats.getClassStats(node.getQualifiedName(), false);
return memoizer == null ? Double.NaN
: JavaMetricsComputer.INSTANCE.computeWithResultOption(key, node, false, safeVersion,
option, memoizer);
return JavaMetricsComputer.INSTANCE.computeWithResultOption(key, node, false, safeVersion,
option, topLevelPackageStats);
}
private void checkKeyNotNull(MetricKey<?> key) {
if (key == null) {
throw new IllegalArgumentException("The metric key may not be null");
}
}
}

View File

@ -1,29 +0,0 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.metrics;
import java.util.HashMap;
import java.util.Map;
import net.sourceforge.pmd.lang.metrics.ParameterizedMetricKey;
/**
* @author Clément Fournier
*/
public abstract class Memoizer {
private final Map<ParameterizedMetricKey, Double> memo = new HashMap<>();
Double getMemo(ParameterizedMetricKey key) {
return memo.get(key);
}
void memoize(ParameterizedMetricKey key, double value) {
memo.put(key, value);
}
}

View File

@ -4,12 +4,15 @@
package net.sourceforge.pmd.lang.java.metrics;
import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration;
import net.sourceforge.pmd.lang.metrics.MetricMemoizer;
/**
* Statistics for an operation. Keeps a map of all memoized metrics results.
*
* @author Clément Fournier
*/
class OperationStats extends Memoizer {
class OperationStats extends MetricMemoizer<ASTMethodOrConstructorDeclaration> {
private final String name;

View File

@ -7,10 +7,15 @@ package net.sourceforge.pmd.lang.java.metrics;
import java.util.HashMap;
import java.util.Map;
import net.sourceforge.pmd.lang.ast.QualifiedName;
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.FieldSigMask;
import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSignature;
import net.sourceforge.pmd.lang.java.metrics.signature.OperationSigMask;
import net.sourceforge.pmd.lang.metrics.MetricMemoizer;
import net.sourceforge.pmd.lang.metrics.ProjectMirror;
/**
@ -20,12 +25,13 @@ import net.sourceforge.pmd.lang.java.metrics.signature.OperationSigMask;
* @author Clément Fournier
* @see ClassStats
*/
public final class PackageStats {
public final class PackageStats implements ProjectMirror<ASTAnyTypeDeclaration, ASTMethodOrConstructorDeclaration> {
private final Map<String, PackageStats> subPackages = new HashMap<>();
private final Map<String, ClassStats> classes = new HashMap<>();
/**
* Default constructor.
*/
@ -138,6 +144,7 @@ public final class PackageStats {
return next;
}
/**
* Returns true if the signature of the operation designated by the qualified name is covered by the mask.
*
@ -152,6 +159,7 @@ public final class PackageStats {
return clazz != null && clazz.hasMatchingSig(qname.getOperation(), sigMask);
}
/**
* Returns true if the signature of the field designated by its name and the qualified name of its class is covered
* by the mask.
@ -167,4 +175,16 @@ public final class PackageStats {
return clazz != null && clazz.hasMatchingSig(fieldName, sigMask);
}
@Override
public MetricMemoizer<ASTMethodOrConstructorDeclaration> getOperationStats(QualifiedName qname) {
return getOperationStats((JavaQualifiedName) qname, null, false);
}
@Override
public MetricMemoizer<ASTAnyTypeDeclaration> getClassStats(QualifiedName qname) {
return getClassStats((JavaQualifiedName) qname, false);
}
}

View File

@ -143,7 +143,7 @@ public class DataStructureTest extends ParserTst {
@Override
public Object visit(ASTMethodOrConstructorDeclaration node, Object data) {
OperationStats op = toplevel.getOperationStats(node.getQualifiedName(), node.getSignature(), false);
result.add((int) JavaMetricsComputer.INSTANCE.compute(opMetricKey, node, force, Version.STANDARD, op));
result.add((int) JavaMetricsComputer.INSTANCE.computeForOperation(opMetricKey, node, force, Version.STANDARD, op));
return super.visit(node, data);
}
@ -151,7 +151,7 @@ public class DataStructureTest extends ParserTst {
@Override
public Object visit(ASTAnyTypeDeclaration node, Object data) {
ClassStats clazz = toplevel.getClassStats(node.getQualifiedName(), false);
result.add((int) JavaMetricsComputer.INSTANCE.compute(classMetricKey, node, force, Version.STANDARD, clazz));
result.add((int) JavaMetricsComputer.INSTANCE.computeForType(classMetricKey, node, force, Version.STANDARD, clazz));
return super.visit(node, data);
}
}, null);