diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/ClassStats.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/ClassStats.java
index f5c83c722e..11e87b8d0d 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/ClassStats.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/ClassStats.java
@@ -4,44 +4,35 @@
package net.sourceforge.pmd.lang.java.metrics;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.List;
import java.util.Map;
import java.util.Set;
-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.java.ast.QualifiedName;
import net.sourceforge.pmd.lang.java.metrics.signature.FieldSigMask;
import net.sourceforge.pmd.lang.java.metrics.signature.FieldSignature;
import net.sourceforge.pmd.lang.java.metrics.signature.OperationSigMask;
import net.sourceforge.pmd.lang.java.metrics.signature.OperationSignature;
-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;
/**
- * Statistics about a class, enum, interface, or annotation. Gathers information about the contained members and their
- * signatures. This class does not provide methods to operate directly on its nested classes, but only on itself. To
- * operate on a nested class, retrieve the correct ClassStats with
- * {@link PackageStats#getClassStats(QualifiedName, boolean)} then use the methods of ClassStats.
+ * Statistics about a class, enum, interface, or annotation. Stores information about the contained members and their
+ * signatures, and memoizes the results of the class metrics computed on the corresponding node.
*
- *
Note that at this level, entities of the data structure do not manipulate QualifiedNames anymore, only Strings.
+ *
This class does not provide methods to operate directly on its nested classes, but only on itself. To operate on a
+ * nested class, retrieve the correct ClassStats with {@link PackageStats#getClassStats(QualifiedName, boolean)} then
+ * use the methods of ClassStats. Note that at this level, entities of the data structure do not manipulate
+ * QualifiedNames anymore, only Strings.
*
* @author Clément Fournier
*/
-/* default */ class ClassStats {
+/* default */ class ClassStats extends Memoizer {
private Map> operations = new HashMap<>();
private Map> fields = new HashMap<>();
private Map nestedClasses = new HashMap<>();
- private Map memo = new HashMap<>();
-
// References to the hierarchy
// TODO:cf useful?
// private String superclass;
@@ -49,8 +40,8 @@ import net.sourceforge.pmd.lang.metrics.api.ResultOption;
/**
- * Finds a ClassStats in the direct children of this class. This can only be a directly nested class, for example
- * in the following snippet, A can get B and B can get C but A cannot get C without asking B.
+ * Finds a ClassStats in the direct children of this class. This can only be a directly nested class, for example in
+ * the following snippet, A can get B and B can get C but A cannot get C without asking B.
*
* {@code
* class MyClass { // ClassStats A
@@ -75,6 +66,28 @@ import net.sourceforge.pmd.lang.metrics.api.ResultOption;
}
+ OperationStats getOperationStats(String operationName) {
+ for (Map map : operations.values()) {
+ OperationStats stats = map.get(operationName);
+ if (stats != null) {
+ return stats;
+ }
+ }
+ return null;
+ }
+
+
+ OperationStats getOperationStats(String operationName, ASTMethodOrConstructorDeclaration node) {
+ Map sigMap = operations.get(OperationSignature.buildFor(node));
+
+ if (sigMap == null) {
+ return null;
+ }
+
+ return sigMap.get(operationName);
+ }
+
+
/**
* Adds an operation to the class.
*
@@ -146,149 +159,4 @@ import net.sourceforge.pmd.lang.metrics.api.ResultOption;
return false;
}
-
- /**
- * Computes the value of a metric for a class.
- *
- * @param key The class metric for which to find a memoized result
- * @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 Version of the metric
- *
- * @return The result of the computation, or {@code Double.NaN} if it couldn't be performed
- */
- /* default */ double compute(MetricKey key, ASTAnyTypeDeclaration node, boolean force,
- MetricVersion version) {
-
- 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 = memo.get(paramKey);
- if (!force && prev != null) {
- return prev;
- }
-
- double val = key.getCalculator().computeFor(node, version);
- memo.put(paramKey, val);
-
- return val;
- }
-
-
- /**
- * Computes the value of a metric for an operation.
- *
- * @param key The operation metric for which to find a memoized result
- * @param node The AST node of the operation
- * @param name The name of the operation
- * @param force Force the recomputation; if unset, we'll first check for a memoized result
- * @param version Version of the metric
- *
- * @return The result of the computation, or {@code Double.NaN} if it couldn't be performed
- */
- /* default */ double compute(MetricKey key, ASTMethodOrConstructorDeclaration node,
- String name, boolean force, MetricVersion version) {
-
- // TODO:cf the operation signature might be built many times, consider storing it in the node
- Map sigMap = operations.get(OperationSignature.buildFor(node));
-
- if (sigMap == null) {
- return Double.NaN;
- }
-
- OperationStats stats = sigMap.get(name);
- return stats == null ? Double.NaN : stats.compute(key, node, force, version);
- }
-
-
- /**
- * 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
- *
- * @return The result of the computation, or {@code Double.NaN} if it couldn't be performed
- */
- /* default */ double computeWithResultOption(MetricKey key, ASTAnyTypeDeclaration node,
- boolean force, MetricVersion version, ResultOption option) {
-
- List ops = findOperations(node, false);
-
- List values = new ArrayList<>();
- for (ASTMethodOrConstructorDeclaration op : ops) {
- if (key.supports(op)) {
- double val = this.compute(key, op, op.getQualifiedName().getOperation(), force, version);
- 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.
- * @param includeNested Include operations found in nested classes?
- *
- * @return The list of all operations declared inside the specified class.
- *
- * TODO:cf this one is computed every time
- */
- private static List findOperations(ASTAnyTypeDeclaration node,
- boolean includeNested) {
-
- if (includeNested) {
- return node.findDescendantsOfType(ASTMethodOrConstructorDeclaration.class);
- }
-
- List operations = new ArrayList<>();
-
- for (ASTAnyTypeBodyDeclaration decl : node.getDeclarations()) {
- if (decl.jjtGetNumChildren() > 0 && decl.jjtGetChild(0) instanceof ASTMethodOrConstructorDeclaration) {
- operations.add((ASTMethodOrConstructorDeclaration) decl.jjtGetChild(0));
- }
- }
- return operations;
- }
-
-
- private static double sum(List values) {
- double sum = 0;
- for (double val : values) {
- sum += val;
- }
- return sum;
- }
-
-
- private static double highest(List 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 values) {
- return sum(values) / values.size();
- }
}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/Memoizer.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/Memoizer.java
new file mode 100644
index 0000000000..548974aa3f
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/Memoizer.java
@@ -0,0 +1,29 @@
+/**
+ * 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 memo = new HashMap<>();
+
+
+ Double getMemo(ParameterizedMetricKey key) {
+ return memo.get(key);
+ }
+
+
+ void memoize(ParameterizedMetricKey key, double value) {
+ memo.put(key, value);
+ }
+}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/MetricsComputer.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/MetricsComputer.java
new file mode 100644
index 0000000000..cd736c131f
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/MetricsComputer.java
@@ -0,0 +1,171 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.metrics;
+
+import java.util.ArrayList;
+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;
+
+/**
+ * Computes a metric. This relieves ClassStats and OperationStats from that responsibility.
+ *
+ * @author Clément Fournier
+ */
+public class MetricsComputer {
+
+ static final MetricsComputer INSTANCE = new MetricsComputer();
+
+
+ /**
+ * 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 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 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 key, ASTAnyTypeDeclaration node,
+ boolean force, MetricVersion version, ResultOption option, ClassStats stats) {
+
+ List ops = findOperations(node);
+
+ List values = new ArrayList<>();
+ for (ASTMethodOrConstructorDeclaration op : ops) {
+ if (key.supports(op)) {
+ OperationStats opStats = stats.getOperationStats(op.getQualifiedName().getOperation(), op);
+ 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 findOperations(ASTAnyTypeDeclaration node) {
+
+ List operations = new ArrayList<>();
+
+ for (ASTAnyTypeBodyDeclaration decl : node.getDeclarations()) {
+ if (decl.jjtGetNumChildren() > 0 && decl.jjtGetChild(0) instanceof ASTMethodOrConstructorDeclaration) {
+ operations.add((ASTMethodOrConstructorDeclaration) decl.jjtGetChild(0));
+ }
+ }
+ return operations;
+ }
+
+
+ private static double sum(List values) {
+ double sum = 0;
+ for (double val : values) {
+ sum += val;
+ }
+ return sum;
+ }
+
+
+ private static double highest(List 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 values) {
+ return sum(values) / values.size();
+ }
+
+
+}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/OperationStats.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/OperationStats.java
index f860c5540e..96410dc748 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/OperationStats.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/OperationStats.java
@@ -4,60 +4,26 @@
package net.sourceforge.pmd.lang.java.metrics;
-import java.util.HashMap;
-import java.util.Map;
-
-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;
-
-
/**
* Statistics for an operation. Keeps a map of all memoized metrics results.
*
* @author Clément Fournier
*/
-/* default */ class OperationStats {
+class OperationStats extends Memoizer {
private final String name;
- private final Map memo = new HashMap<>();
- /* default */ OperationStats(String name) {
+ OperationStats(String name) {
this.name = name;
}
- /* default */ String getName() {
+ String getName() {
return name;
}
- /**
- * Computes the value of a metric for an operation.
- *
- * @param key The operation metric for which to find a memoized result
- * @param node The AST node of the operation
- * @param force Force the recomputation; if unset, we'll first check for a memoized result
- *
- * @return The result of the computation, or {@code Double.NaN} if it couldn't be performed
- */
- /* default */ double compute(MetricKey key, ASTMethodOrConstructorDeclaration node,
- boolean force, MetricVersion version) {
-
- ParameterizedMetricKey paramKey = ParameterizedMetricKey.getInstance(key, version);
- Double prev = memo.get(paramKey);
- if (!force && prev != null) {
- return prev;
- }
-
- double val = key.getCalculator().computeFor(node, version);
- memo.put(paramKey, val);
- return val;
- }
-
-
@Override
public boolean equals(Object o) {
if (this == o) {
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/PackageStats.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/PackageStats.java
index 632c01602e..bbfa5907b4 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/PackageStats.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/PackageStats.java
@@ -18,8 +18,8 @@ import net.sourceforge.pmd.lang.metrics.api.ResultOption;
/**
- * Statistics about a package. This recursive data structure mirrors the package structure of the analysed
- * project and stores information about the classes and subpackages it contains.
+ * Statistics about a package. This recursive data structure mirrors the package structure of the analysed project and
+ * stores information about the classes and subpackages it contains.
*
* @author Clément Fournier
* @see ClassStats
@@ -48,8 +48,7 @@ public final class PackageStats {
/**
- * Returns true if the signature of the operation designated by the qualified name is covered by
- * the mask.
+ * Returns true if the signature of the operation designated by the qualified name is covered by the mask.
*
* @param qname The operation to test
* @param sigMask The signature mask to use
@@ -64,8 +63,8 @@ public final class PackageStats {
/**
- * Gets the ClassStats corresponding to the named resource. The class can be nested. If the
- * createIfNotFound parameter is set, the method also creates the hierarchy if it doesn't exist.
+ * Gets the ClassStats corresponding to the named resource. The class can be nested. If the createIfNotFound
+ * parameter is set, the method also creates the hierarchy if it doesn't exist.
*
* @param qname The qualified name of the class
* @param createIfNotFound Create hierarchy if missing
@@ -102,8 +101,8 @@ public final class PackageStats {
/**
- * Returns the deepest PackageStats that contains the named resource. If the second parameter is
- * set, creates the missing PackageStats along the way.
+ * Returns the deepest PackageStats that contains the named resource. If the second parameter is set, creates the
+ * missing PackageStats along the way.
*
* @param qname The qualified name of the resource
* @param createIfNotFound If set to true, the hierarch is created if non existent
@@ -131,8 +130,8 @@ public final class PackageStats {
/**
- * Returns true if the signature of the field designated by its name and the qualified name of its class is
- * covered by the mask.
+ * Returns true if the signature of the field designated by its name and the qualified name of its class is covered
+ * by the mask.
*
* @param qname The class of the field
* @param fieldName The name of the field
@@ -162,7 +161,7 @@ public final class PackageStats {
ClassStats container = getClassStats(node.getQualifiedName(), false);
return container == null ? Double.NaN
- : container.compute(key, node, force, version);
+ : MetricsComputer.INSTANCE.compute(key, node, force, version, container);
}
@@ -180,9 +179,10 @@ public final class PackageStats {
boolean force, MetricVersion version) {
QualifiedName qname = node.getQualifiedName();
ClassStats container = getClassStats(qname, false);
+ OperationStats memoizer = container == null ? null : container.getOperationStats(qname.getOperation(), node);
- return container == null ? Double.NaN
- : container.compute(key, node, qname.getOperation(), force, version);
+ return memoizer == null ? Double.NaN
+ : MetricsComputer.INSTANCE.compute(key, node, force, version, memoizer);
}
@@ -202,6 +202,7 @@ public final class PackageStats {
ClassStats container = getClassStats(node.getQualifiedName(), false);
return container == null ? Double.NaN
- : container.computeWithResultOption(key, node, force, version, option);
+ : MetricsComputer.INSTANCE.computeWithResultOption(key, node, force, version,
+ option, container);
}
}