Simplify metrics framework

This commit is contained in:
Clément Fournier committed 2020-01-19 12:35:55 +01:00
1 parent 84b59c3350
commit 2123ab3d5d
49 files changed
+357 -543

No files matched your search

+6
View File
@@ -135,6 +135,10 @@ methods on {% jdoc apex::lang.apex.ast.ApexParserVisitor %} and its implementati
* Many methods on the {% jdoc core::lang.ast.Node %} interface
and {% jdoc core::lang.ast.AbstractNode %} base class. See their javadoc for details.
* {% jdoc !!core::lang.ast.Node#isFindBoundary() %} is deprecated for XPath queries.
* Many APIs of {% jdoc_package core::lang.metrics %}, though most of them were internal and
probably not used directly outside of PMD. Use {% jdoc core::lang.metrics.MetricsUtil %} as
a replacement for the language-specific façades too.
* {% jdoc core::lang.ast.QualifiableNode %}, {% jdoc core::lang.ast.QualifiedName %}
* pmd-java
* {% jdoc java::lang.java.AbstractJavaParser %}
* {% jdoc java::lang.java.AbstractJavaHandler %}
@@ -162,6 +166,8 @@ methods on {% jdoc apex::lang.apex.ast.ApexParserVisitor %} and its implementati
following nodes: WhileStatement, DoStatement, ForStatement, IfStatement, AssertStatement, ConditionalExpression.
* {% jdoc java::lang.java.ast.ASTYieldStatement %} will not implement {% jdoc java::lang.java.ast.TypeNode %}
anymore come 7.0.0. Test the type of the expression nested within it.
* {% jdoc core::lang.java.metrics.JavaMetrics %}, {% jdoc core::lang.java.metrics.JavaMetricsComputer %},
{% jdoc core::lang.java.metrics.JavaMetricsProvider %}
### External Contributions
@@ -4,11 +4,17 @@
package net.sourceforge.pmd.lang.apex.metrics;
import java.util.ArrayList;
import java.util.List;
import org.checkerframework.checker.nullness.qual.NonNull;
import net.sourceforge.pmd.lang.apex.ast.ASTMethod;
import net.sourceforge.pmd.lang.apex.ast.ASTUserClass;
import net.sourceforge.pmd.lang.apex.ast.ASTUserClassOrInterface;
import net.sourceforge.pmd.lang.metrics.MetricKey;
import net.sourceforge.pmd.lang.metrics.MetricOptions;
import net.sourceforge.pmd.lang.metrics.MetricsUtil;
import net.sourceforge.pmd.lang.metrics.ResultOption;
/**
@@ -16,7 +22,10 @@ import net.sourceforge.pmd.lang.metrics.ResultOption;
*
* @author Clément Fournier
* @since 6.0.0
*
* @deprecated Use {@link MetricsUtil}
*/
@Deprecated
public final class ApexMetrics {
private static final ApexMetricsFacade FACADE = new ApexMetricsFacade();
@@ -32,20 +41,12 @@ public final class ApexMetrics {
*
* @return The facade
*/
@Deprecated
public static ApexMetricsFacade getFacade() {
return FACADE;
}
/**
* Resets the entire data structure.
* This needs to be done in case PMD is executed multiple times within one JVM run.
*/
static void reset() {
FACADE.reset();
}
/**
* Computes the standard value of the metric identified by its code on a class AST node.
*
@@ -55,7 +56,7 @@ public final class ApexMetrics {
* @return The value of the metric, or {@code Double.NaN} if the value couldn't be computed
*/
public static double get(MetricKey<ASTUserClassOrInterface<?>> key, ASTUserClass node) {
return FACADE.computeForType(key, node, MetricOptions.emptyOptions());
return MetricsUtil.computeMetric(key, node, MetricOptions.emptyOptions());
}
@@ -70,7 +71,7 @@ public final class ApexMetrics {
* @return The value of the metric, or {@code Double.NaN} if the value couldn't be computed
*/
public static double get(MetricKey<ASTUserClassOrInterface<?>> key, ASTUserClass node, MetricOptions options) {
return FACADE.computeForType(key, node, options);
return MetricsUtil.computeMetric(key, node, options);
}
@@ -83,7 +84,7 @@ public final class ApexMetrics {
* @return The value of the metric, or {@code Double.NaN} if the value couldn't be computed
*/
public static double get(MetricKey<ASTMethod> key, ASTMethod node) {
return FACADE.computeForOperation(key, node, MetricOptions.emptyOptions());
return MetricsUtil.computeMetric(key, node, MetricOptions.emptyOptions());
}
@@ -98,7 +99,7 @@ public final class ApexMetrics {
* @return The value of the metric, or {@code Double.NaN} if the value couldn't be computed
*/
public static double get(MetricKey<ASTMethod> key, ASTMethod node, MetricOptions options) {
return FACADE.computeForOperation(key, node, options);
return MetricsUtil.computeMetric(key, node, options);
}
@@ -111,10 +112,10 @@ public final class ApexMetrics {
* @param resultOption The result option to use
*
* @return The value of the metric, or {@code Double.NaN} if the value couldn't be computed or {@code option} is
* {@code null}
* {@code null}
*/
public static double get(MetricKey<ASTMethod> key, ASTUserClassOrInterface<?> node, ResultOption resultOption) {
return FACADE.computeWithResultOption(key, node, MetricOptions.emptyOptions(), resultOption);
return MetricsUtil.computeAggregate(key, findOps(node), resultOption);
}
@@ -128,12 +129,23 @@ public final class ApexMetrics {
* @param resultOption The result option to use
*
* @return The value of the metric, or {@code Double.NaN} if the value couldn't be computed or {@code option} is
* {@code null}
* {@code null}
*/
public static double get(MetricKey<ASTMethod> key, ASTUserClassOrInterface<?> node, MetricOptions options,
ResultOption resultOption) {
return FACADE.computeWithResultOption(key, node, options, resultOption);
return MetricsUtil.computeAggregate(key, findOps(node), options, resultOption);
}
@NonNull
static List<ASTMethod> findOps(ASTUserClassOrInterface<?> node) {
List<ASTMethod> candidates = node.findChildrenOfType(ASTMethod.class);
List<ASTMethod> result = new ArrayList<>(candidates);
for (ASTMethod method : candidates) {
if (method.getImage().matches("(<clinit>|<init>|clone)")) {
result.remove(method);
}
}
return result;
}
}
@@ -4,7 +4,6 @@
package net.sourceforge.pmd.lang.apex.metrics;
import java.util.ArrayList;
import java.util.List;
import net.sourceforge.pmd.annotation.InternalApi;
@@ -21,22 +20,14 @@ public class ApexMetricsComputer extends AbstractMetricsComputer<ASTUserClassOrI
private static final ApexMetricsComputer INSTANCE = new ApexMetricsComputer();
@Override
protected List<ASTMethod> findOperations(ASTUserClassOrInterface<?> node) {
return ApexMetrics.findOps(node);
}
@InternalApi
public static ApexMetricsComputer getInstance() {
return INSTANCE;
}
@Override
protected List<ASTMethod> findOperations(ASTUserClassOrInterface<?> node) {
List<ASTMethod> candidates = node.findChildrenOfType(ASTMethod.class);
List<ASTMethod> result = new ArrayList<>(candidates);
for (ASTMethod method : candidates) {
if (method.getImage().matches("(<clinit>|<init>|clone)")) {
result.remove(method);
}
}
return result;
}
}
@@ -13,18 +13,14 @@ import net.sourceforge.pmd.lang.metrics.MetricsComputer;
* Backs the static façade.
*
* @author Clément Fournier
* @deprecated Not useful anymore
*/
@Deprecated
public class ApexMetricsFacade extends AbstractMetricsFacade<ASTUserClassOrInterface<?>, ASTMethod> {
private final ApexProjectMemoizer memoizer = new ApexProjectMemoizer();
/** Resets the entire project mirror. Used for tests. */
void reset() {
memoizer.reset();
}
@Override
protected MetricsComputer<ASTUserClassOrInterface<?>, ASTMethod> getLanguageSpecificComputer() {
return ApexMetricsComputer.getInstance();
@@ -18,12 +18,7 @@ public class ApexMetricsProvider extends AbstractLanguageMetricsProvider<ASTUser
@SuppressWarnings("unchecked")
public ApexMetricsProvider() {
// a wild double cast
super((Class<ASTUserClassOrInterface<?>>) (Object) ASTUserClassOrInterface.class, ASTMethod.class, ApexMetricsComputer.getInstance());
}
@Override
public void initialize() {
ApexMetrics.reset();
super((Class<ASTUserClassOrInterface<?>>) (Object) ASTUserClassOrInterface.class, ASTMethod.class);
}
@Override
@@ -32,6 +27,11 @@ public class ApexMetricsProvider extends AbstractLanguageMetricsProvider<ASTUser
}
@Override
protected List<ASTMethod> findOps(ASTUserClassOrInterface<?> astUserClassOrInterface) {
return ApexMetrics.findOps(astUserClassOrInterface);
}
@Override
public List<ApexOperationMetricKey> getAvailableOperationMetrics() {
return Arrays.asList(ApexOperationMetricKey.values());
@@ -16,6 +16,7 @@ import net.sourceforge.pmd.lang.apex.metrics.ApexMetrics;
import net.sourceforge.pmd.lang.apex.metrics.api.ApexClassMetricKey;
import net.sourceforge.pmd.lang.apex.metrics.api.ApexOperationMetricKey;
import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule;
import net.sourceforge.pmd.lang.metrics.MetricsUtil;
import net.sourceforge.pmd.lang.metrics.ResultOption;
import net.sourceforge.pmd.properties.PropertyDescriptor;
import net.sourceforge.pmd.properties.PropertyFactory;
@@ -23,7 +24,7 @@ import net.sourceforge.pmd.properties.PropertyFactory;
/**
* Cyclomatic complexity rule using metrics. Uses Wmc to report classes.
*
*
* @author Clément Fournier
*/
public class CyclomaticComplexityRule extends AbstractApexRule {
@@ -69,7 +70,7 @@ public class CyclomaticComplexityRule extends AbstractApexRule {
classNames.pop();
if (ApexClassMetricKey.WMC.supports(node)) {
int classWmc = (int) ApexMetrics.get(ApexClassMetricKey.WMC, node);
int classWmc = (int) MetricsUtil.computeMetric(ApexClassMetricKey.WMC, node);
if (classWmc >= getProperty(CLASS_LEVEL_DESCRIPTOR)) {
int classHighest = (int) ApexMetrics.get(ApexOperationMetricKey.CYCLO, node, ResultOption.HIGHEST);
@@ -89,7 +90,7 @@ public class CyclomaticComplexityRule extends AbstractApexRule {
@Override
public final Object visit(ASTMethod node, Object data) {
int cyclo = (int) ApexMetrics.get(ApexOperationMetricKey.CYCLO, node);
int cyclo = (int) MetricsUtil.computeMetric(ApexOperationMetricKey.CYCLO, node);
if (cyclo >= getProperty(METHOD_LEVEL_DESCRIPTOR)) {
String opType = inTrigger ? "trigger"
: node.getImage().equals(classNames.peek()) ? "constructor"
@@ -1,25 +0,0 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.apex.metrics;
/**
* Provides a hook into package-private methods of {@code apex.metrics}.
*
* @author Clément Fournier
*/
public class ApexMetricsHook {
private ApexMetricsHook() {
}
public static void reset() {
ApexMetrics.reset();
}
}
@@ -24,8 +24,8 @@ import net.sourceforge.pmd.lang.apex.metrics.impl.AbstractApexClassMetric;
import net.sourceforge.pmd.lang.apex.metrics.impl.AbstractApexOperationMetric;
import net.sourceforge.pmd.lang.metrics.MetricKey;
import net.sourceforge.pmd.lang.metrics.MetricKeyUtil;
import net.sourceforge.pmd.lang.metrics.MetricMemoizer;
import net.sourceforge.pmd.lang.metrics.MetricOptions;
import net.sourceforge.pmd.lang.metrics.MetricsUtil;
import apex.jorje.semantic.ast.compilation.Compilation;
@@ -71,25 +71,19 @@ public class ApexProjectMirrorTest extends ApexParserTestBase {
private List<Integer> visitWith(ApexNode<Compilation> acu, final boolean force) {
final ApexProjectMemoizer toplevel = ApexMetrics.getFacade().getLanguageSpecificProjectMemoizer();
final List<Integer> result = new ArrayList<>();
acu.jjtAccept(new ApexParserVisitorAdapter() {
@Override
public Object visit(ASTMethod node, Object data) {
MetricMemoizer<ASTMethod> op = toplevel.getOperationMemoizer(node.getQualifiedName());
result.add((int) ApexMetricsComputer.getInstance().computeForOperation(opMetricKey, node, force,
MetricOptions.emptyOptions(), op));
result.add((int) MetricsUtil.computeMetric(opMetricKey, node, MetricOptions.emptyOptions(), force));
return super.visit(node, data);
}
@Override
public Object visit(ASTUserClass node, Object data) {
MetricMemoizer<ASTUserClassOrInterface<?>> clazz = toplevel.getClassMemoizer(node.getQualifiedName());
result.add((int) ApexMetricsComputer.getInstance().computeForType(classMetricKey, node, force,
MetricOptions.emptyOptions(), clazz));
result.add((int) MetricsUtil.computeMetric(classMetricKey, node, MetricOptions.emptyOptions(), force));
return super.visit(node, data);
}
}, null);
@@ -4,8 +4,6 @@
package net.sourceforge.pmd.lang.apex.metrics.impl;
import net.sourceforge.pmd.Rule;
import net.sourceforge.pmd.lang.apex.metrics.ApexMetricsHook;
import net.sourceforge.pmd.testframework.SimpleAggregatorTst;
/**
@@ -18,14 +16,6 @@ public class AllMetricsTest extends SimpleAggregatorTst {
private static final String RULESET = "rulesets/apex/metrics_test.xml";
@Override
protected Rule reinitializeRule(Rule rule) {
ApexMetricsHook.reset();
return rule;
}
@Override
public void setUp() {
addRule(RULESET, "CycloTest");
@@ -4,14 +4,7 @@
package net.sourceforge.pmd.lang.apex.rule.design;
import net.sourceforge.pmd.Rule;
import net.sourceforge.pmd.lang.apex.metrics.ApexMetricsHook;
import net.sourceforge.pmd.testframework.PmdRuleTst;
public class CyclomaticComplexityTest extends PmdRuleTst {
@Override
protected Rule reinitializeRule(Rule rule) {
ApexMetricsHook.reset();
return super.reinitializeRule(rule);
}
}
@@ -373,6 +373,7 @@ public interface Node {
* unless it has been set via {@link #setUserData(Object)}.
*
* @return The user data set on this node.
* @deprecated Use {@link #getUserMap()}
*/
Object getUserData();
@@ -389,6 +390,7 @@ public interface Node {
*
* @param userData
* The data to set on this node.
* @deprecated Use {@link #getUserMap()}
*/
void setUserData(Object userData);
@@ -8,7 +8,10 @@ package net.sourceforge.pmd.lang.ast;
* Nodes that can be described with a qualified name.
*
* @author Clément Fournier
*
* @deprecated Not useful anymore
*/
@Deprecated
public interface QualifiableNode extends Node {
/**
@@ -12,7 +12,9 @@ package net.sourceforge.pmd.lang.ast;
* from QualifiedName to e.g. JavaQualifiedName.
*
* @author Clément Fournier
* @deprecated Not useful anymore
*/
@Deprecated
public interface QualifiedName {
@Override
@@ -4,7 +4,6 @@
package net.sourceforge.pmd.lang.metrics;
import java.util.ArrayList;
import java.util.List;
import net.sourceforge.pmd.lang.ast.QualifiableNode;
@@ -17,25 +16,16 @@ import net.sourceforge.pmd.lang.ast.QualifiableNode;
*
* @author Clément Fournier
* @since 6.0.0
* @deprecated See package description
*/
@Deprecated
public abstract class AbstractMetricsComputer<T extends QualifiableNode, O extends QualifiableNode>
implements MetricsComputer<T, O> {
@Override
public double computeForType(MetricKey<T> key, T node, boolean force,
MetricOptions options, MetricMemoizer<T> memoizer) {
ParameterizedMetricKey<T> paramKey = ParameterizedMetricKey.getInstance(key, options);
// 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, options);
memoizer.memoize(paramKey, val);
return val;
return MetricsUtil.computeMetric(key, node, options);
}
@@ -43,46 +33,14 @@ public abstract class AbstractMetricsComputer<T extends QualifiableNode, O exten
public double computeForOperation(MetricKey<O> key, O node, boolean force,
MetricOptions options, MetricMemoizer<O> memoizer) {
ParameterizedMetricKey<O> paramKey = ParameterizedMetricKey.getInstance(key, options);
Double prev = memoizer.getMemo(paramKey);
if (!force && prev != null) {
return prev;
}
double val = key.getCalculator().computeFor(node, options);
memoizer.memoize(paramKey, val);
return val;
return MetricsUtil.computeMetric(key, node, options);
}
@Override
public double computeWithResultOption(MetricKey<O> key, T node, boolean force, MetricOptions options,
ResultOption option, ProjectMemoizer<T, O> stats) {
List<O> ops = findOperations(node);
List<Double> values = new ArrayList<>();
for (O op : ops) {
if (key.supports(op)) {
MetricMemoizer<O> opStats = stats.getOperationMemoizer(op.getQualifiedName());
double val = this.computeForOperation(key, op, force, options, 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;
}
return MetricsUtil.computeAggregate(key, findOperations(node), options, option);
}
@@ -97,29 +55,4 @@ public abstract class AbstractMetricsComputer<T extends QualifiableNode, O exten
protected abstract List<O> findOperations(T node); // TODO:cf this one is computed every time
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();
}
}
@@ -4,8 +4,6 @@
package net.sourceforge.pmd.lang.metrics;
import java.util.Objects;
import net.sourceforge.pmd.lang.ast.QualifiableNode;
/**
@@ -17,19 +15,18 @@ import net.sourceforge.pmd.lang.ast.QualifiableNode;
*
* @author Clément Fournier
* @since 6.0.0
* @deprecated See package description
*/
@Deprecated
public abstract class AbstractMetricsFacade<T extends QualifiableNode, O extends QualifiableNode> {
private static final String NULL_KEY_MESSAGE = "The metric key must not be null";
private static final String NULL_OPTIONS_MESSAGE = "The metric options must not be null";
private static final String NULL_NODE_MESSAGE = "The node must not be null";
/**
* Gets the language specific metrics computer.
*
* @return The metrics computer
*/
@Deprecated
protected abstract MetricsComputer<T, O> getLanguageSpecificComputer();
@@ -38,6 +35,7 @@ public abstract class AbstractMetricsFacade<T extends QualifiableNode, O extends
*
* @return The project memoizer
*/
@Deprecated
protected abstract ProjectMemoizer<T, O> getLanguageSpecificProjectMemoizer();
@@ -52,20 +50,7 @@ public abstract class AbstractMetricsFacade<T extends QualifiableNode, O extends
* @return The value of the metric, or {@code Double.NaN} if the value couldn't be computed
*/
public double computeForType(MetricKey<T> key, T node, MetricOptions options) {
Objects.requireNonNull(key, NULL_KEY_MESSAGE);
Objects.requireNonNull(options, NULL_OPTIONS_MESSAGE);
Objects.requireNonNull(node, NULL_NODE_MESSAGE);
if (!key.supports(node)) {
return Double.NaN;
}
MetricMemoizer<T> memoizer = getLanguageSpecificProjectMemoizer().getClassMemoizer(node.getQualifiedName());
return memoizer == null ? Double.NaN
: getLanguageSpecificComputer().computeForType(key, node, false,
options, memoizer);
return MetricsUtil.computeMetric(key, node, options);
}
@@ -79,22 +64,7 @@ public abstract class AbstractMetricsFacade<T extends QualifiableNode, O extends
* @return The value of the metric, or {@code Double.NaN} if the value couldn't be computed
*/
public double computeForOperation(MetricKey<O> key, O node, MetricOptions options) {
Objects.requireNonNull(key, NULL_KEY_MESSAGE);
Objects.requireNonNull(options, NULL_OPTIONS_MESSAGE);
Objects.requireNonNull(node, NULL_NODE_MESSAGE);
if (!key.supports(node)) {
return Double.NaN;
}
MetricMemoizer<O> memoizer = getLanguageSpecificProjectMemoizer().getOperationMemoizer(node.getQualifiedName());
return memoizer == null ? Double.NaN
: getLanguageSpecificComputer().computeForOperation(key, node, false,
options, memoizer);
return MetricsUtil.computeMetric(key, node, options);
}
@@ -113,11 +83,6 @@ public abstract class AbstractMetricsFacade<T extends QualifiableNode, O extends
public double computeWithResultOption(MetricKey<O> key, T node,
MetricOptions options, ResultOption resultOption) {
Objects.requireNonNull(key, NULL_KEY_MESSAGE);
Objects.requireNonNull(options, NULL_OPTIONS_MESSAGE);
Objects.requireNonNull(node, NULL_NODE_MESSAGE);
Objects.requireNonNull(resultOption, "The result option must not be null");
return getLanguageSpecificComputer().computeWithResultOption(key, node, false, options,
resultOption, getLanguageSpecificProjectMemoizer());
}
@@ -16,7 +16,9 @@ import net.sourceforge.pmd.lang.ast.Node;
*
* @author Clément Fournier
* @since 6.0.0
* @deprecated See package description
*/
@Deprecated
public class BasicMetricMemoizer<N extends Node> implements MetricMemoizer<N> {
@@ -21,7 +21,10 @@ import net.sourceforge.pmd.lang.ast.QualifiedName;
*
* @author Clément Fournier
* @since 6.0.0
*
* @deprecated See package description
*/
@Deprecated
public abstract class BasicProjectMemoizer<T extends QualifiableNode, O extends QualifiableNode>
implements ProjectMemoizer<T, O> {
@@ -31,12 +31,6 @@ import net.sourceforge.pmd.lang.ast.QualifiableNode;
@Experimental
public interface LanguageMetricsProvider<T extends QualifiableNode, O extends QualifiableNode> {
/**
* Provides a hook to do any initializing before the first file is processed by PMD.
* This can be used by the metrics implementations to reset the cache.
*/
void initialize();
/**
* Returns a list of all supported type metric keys
* for the language.
@@ -5,6 +5,7 @@
package net.sourceforge.pmd.lang.metrics;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.util.DataMap.DataKey;
/**
* Key identifying a metric. Such keys <i>must</i> implement the hashCode method. Enums are well fitted to serve as
@@ -14,7 +15,7 @@ import net.sourceforge.pmd.lang.ast.Node;
* @author Clément Fournier
* @since 5.8.0
*/
public interface MetricKey<N extends Node> {
public interface MetricKey<N extends Node> extends DataKey<MetricKey<N>, Double> {
/**
* Returns the name of the metric.
@@ -14,7 +14,9 @@ import net.sourceforge.pmd.lang.ast.Node;
*
* @author Clément Fournier
* @since 6.0.0
* @deprecated See package description
*/
@Deprecated
public interface MetricMemoizer<N extends Node> {
@@ -16,7 +16,9 @@ import net.sourceforge.pmd.lang.ast.QualifiableNode;
*
* @author Clément Fournier
* @since 6.0.0
* @deprecated See package description
*/
@Deprecated
public interface MetricsComputer<T extends QualifiableNode, O extends QualifiableNode> {
@@ -0,0 +1,155 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.metrics;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import net.sourceforge.pmd.lang.ast.Node;
/**
* Utilities to use {@link Metric} instances.
*/
public final class MetricsUtil {
static final String NULL_KEY_MESSAGE = "The metric key must not be null";
static final String NULL_OPTIONS_MESSAGE = "The metric options must not be null";
static final String NULL_NODE_MESSAGE = "The node must not be null";
private MetricsUtil() {
// util class
}
public static <O extends Node> double computeAggregate(MetricKey<? super O> key, Iterable<? extends O> ops, ResultOption resultOption) {
return computeAggregate(key, ops, MetricOptions.emptyOptions(), resultOption);
}
/**
* Computes an aggregate result for a metric, identified with a {@link ResultOption}.
*
* @param key The metric to compute
* @param ops List of nodes for which to aggregate the metric
* @param options The options of the metric
* @param resultOption The type of aggregation to perform
*
* @return The result of the computation, or {@code Double.NaN} if it couldn't be performed
*/
public static <O extends Node> double computeAggregate(MetricKey<? super O> key, Iterable<? extends O> ops, MetricOptions options, ResultOption resultOption) {
Objects.requireNonNull(key, NULL_KEY_MESSAGE);
Objects.requireNonNull(options, NULL_OPTIONS_MESSAGE);
Objects.requireNonNull(ops, NULL_NODE_MESSAGE);
Objects.requireNonNull(resultOption, "The result option must not be null");
List<Double> values = new ArrayList<>();
for (O op : ops) {
if (key.supports(op)) {
double val = computeMetric(key, op, options);
if (!Double.isNaN(val)) {
values.add(val);
}
}
}
// FUTURE use streams to do that when we upgrade the compiler to 1.8
switch (resultOption) {
case SUM:
return sum(values);
case HIGHEST:
return highest(values);
case AVERAGE:
return average(values);
default:
return Double.NaN;
}
}
/**
* Computes a metric identified by its code on a node, with the default options.
*
* @param key The key identifying the metric to be computed
* @param node The node on which to compute the metric
*
* @return The value of the metric, or {@code Double.NaN} if the value couldn't be computed
*/
public static <N extends Node> double computeMetric(MetricKey<? super N> key, N node) {
return computeMetric(key, node, MetricOptions.emptyOptions());
}
/**
* Computes a metric identified by its code on a node, possibly
* selecting a variant with the {@code options} parameter.
*
* @param key The key identifying the metric to be computed
* @param node The node on which to compute the metric
* @param options The options of the metric
*
* @return The value of the metric, or {@code Double.NaN} if the value couldn't be computed
*/
public static <N extends Node> double computeMetric(MetricKey<? super N> key, N node, MetricOptions options) {
return computeMetric(key, node, options, false);
}
/**
* Computes a metric identified by its code on a node, possibly
* selecting a variant with the {@code options} parameter.
*
* @param key The key identifying the metric to be computed
* @param node The node on which to compute the metric
* @param options The options of the metric
* @param forceRecompute Force recomputation of the result
*
* @return The value of the metric, or {@code Double.NaN} if the value couldn't be computed
*/
public static <N extends Node> double computeMetric(MetricKey<? super N> key, N node, MetricOptions options, boolean forceRecompute) {
Objects.requireNonNull(key, NULL_KEY_MESSAGE);
Objects.requireNonNull(options, NULL_OPTIONS_MESSAGE);
Objects.requireNonNull(node, NULL_NODE_MESSAGE);
if (!key.supports(node)) {
return Double.NaN;
}
ParameterizedMetricKey<? super N> paramKey = ParameterizedMetricKey.getInstance(key, options);
Double prev = node.getUserMap().get(paramKey);
if (!forceRecompute && prev != null) {
return prev;
}
double val = key.getCalculator().computeFor(node, options);
node.getUserMap().set(paramKey, val);
return val;
}
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();
}
}
@@ -7,7 +7,9 @@ package net.sourceforge.pmd.lang.metrics;
import java.util.HashMap;
import java.util.Map;
import net.sourceforge.pmd.annotation.InternalApi;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.util.DataMap.DataKey;
/**
* Represents a key parameterized with its options. Used to index memoization maps.
@@ -16,8 +18,11 @@ import net.sourceforge.pmd.lang.ast.Node;
*
* @author Clément Fournier
* @since 5.8.0
* @deprecated Is internal API
*/
public final class ParameterizedMetricKey<N extends Node> {
@InternalApi
@Deprecated
public final class ParameterizedMetricKey<N extends Node> implements DataKey<ParameterizedMetricKey<N>, Double> {
private static final Map<ParameterizedMetricKey<?>, ParameterizedMetricKey<?>> POOL = new HashMap<>();
@@ -65,6 +70,7 @@ public final class ParameterizedMetricKey<N extends Node> {
*/
@SuppressWarnings("PMD.SingletonClassReturningNewInstance")
public static <N extends Node> ParameterizedMetricKey<N> getInstance(MetricKey<N> key, MetricOptions options) {
// sharing instances allows using DataMap, which uses reference identity
ParameterizedMetricKey<N> tmp = new ParameterizedMetricKey<>(key, options);
if (!POOL.containsKey(tmp)) {
POOL.put(tmp, tmp);
@@ -19,7 +19,9 @@ import net.sourceforge.pmd.lang.ast.QualifiedName;
*
* @author Clément Fournier
* @since 6.0.0
* @deprecated See package description
*/
@Deprecated
public interface ProjectMemoizer<T extends QualifiableNode, O extends QualifiableNode> {
/**
@@ -5,6 +5,7 @@
package net.sourceforge.pmd.lang.metrics.internal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.sourceforge.pmd.lang.ast.Node;
@@ -12,7 +13,7 @@ import net.sourceforge.pmd.lang.ast.QualifiableNode;
import net.sourceforge.pmd.lang.metrics.LanguageMetricsProvider;
import net.sourceforge.pmd.lang.metrics.MetricKey;
import net.sourceforge.pmd.lang.metrics.MetricOptions;
import net.sourceforge.pmd.lang.metrics.MetricsComputer;
import net.sourceforge.pmd.lang.metrics.MetricsUtil;
import net.sourceforge.pmd.lang.metrics.ResultOption;
@@ -26,15 +27,12 @@ public abstract class AbstractLanguageMetricsProvider<T extends QualifiableNode,
private final Class<T> tClass;
private final Class<O> oClass;
private final MetricsComputer<T, O> myComputer;
protected AbstractLanguageMetricsProvider(Class<T> tClass,
Class<O> oClass,
MetricsComputer<T, O> computer) {
Class<O> oClass) {
this.tClass = tClass;
this.oClass = oClass;
this.myComputer = computer;
}
@@ -52,21 +50,23 @@ public abstract class AbstractLanguageMetricsProvider<T extends QualifiableNode,
@Override
public double computeForType(MetricKey<T> key, T node, MetricOptions options) {
return myComputer.computeForType(key, node, true, options, DummyMetricMemoizer.<T>getInstance());
return MetricsUtil.computeMetric(key, node, options, true);
}
@Override
public double computeForOperation(MetricKey<O> key, O node, MetricOptions options) {
return myComputer.computeForOperation(key, node, true, options, DummyMetricMemoizer.<O>getInstance());
return MetricsUtil.computeMetric(key, node, options, true);
}
@Override
public double computeWithResultOption(MetricKey<O> key, T node, MetricOptions options, ResultOption option) {
return myComputer.computeWithResultOption(key, node, true, options, option, DummyProjectMemoizer.<T, O>getInstance());
public double computeWithResultOption(MetricKey<O> key, T node, MetricOptions options, ResultOption resultOption) {
return MetricsUtil.computeAggregate(key, findOps(node), options, resultOption);
}
protected abstract List<O> findOps(T t);
@Override
public Map<MetricKey<?>, Double> computeAllMetricsFor(Node node) {
@@ -1,44 +0,0 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.metrics.internal;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.metrics.MetricMemoizer;
import net.sourceforge.pmd.lang.metrics.ParameterizedMetricKey;
/**
* Memoizes nothing.
*
* @author Clément Fournier
* @since 6.11.0
*/
public final class DummyMetricMemoizer<N extends Node> implements MetricMemoizer<N> {
private static final DummyMetricMemoizer<Node> INSTANCE = new DummyMetricMemoizer<>();
private DummyMetricMemoizer() {
}
@Override
public Double getMemo(ParameterizedMetricKey<N> key) {
return null;
}
@Override
public void memoize(ParameterizedMetricKey<N> key, double value) {
// do nothing
}
@SuppressWarnings("unchecked")
public static <N extends Node> DummyMetricMemoizer<N> getInstance() {
return (DummyMetricMemoizer<N>) INSTANCE;
}
}
@@ -1,45 +0,0 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.metrics.internal;
import net.sourceforge.pmd.lang.ast.QualifiableNode;
import net.sourceforge.pmd.lang.ast.QualifiedName;
import net.sourceforge.pmd.lang.metrics.MetricMemoizer;
import net.sourceforge.pmd.lang.metrics.ProjectMemoizer;
/**
* Memoizes nothing.
*
* @author Clément Fournier
* @since 6.11.0
*/
public final class DummyProjectMemoizer<T extends QualifiableNode, O extends QualifiableNode> implements ProjectMemoizer<T, O> {
private static final DummyProjectMemoizer<? extends QualifiableNode, ? extends QualifiableNode> INSTANCE = new DummyProjectMemoizer<>();
private DummyProjectMemoizer() {
}
@Override
public MetricMemoizer<O> getOperationMemoizer(QualifiedName qname) {
return DummyMetricMemoizer.getInstance();
}
@Override
public MetricMemoizer<T> getClassMemoizer(QualifiedName qname) {
return DummyMetricMemoizer.getInstance();
}
@SuppressWarnings("unchecked")
public static <T extends QualifiableNode, O extends QualifiableNode> DummyProjectMemoizer<T, O> getInstance() {
return (DummyProjectMemoizer<T, O>) INSTANCE;
}
}
@@ -0,0 +1,16 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
/**
* Language-independent framework to represent code metrics. If you want
* to compute code metrics in your rules, then you should find the language-specific
* enums containing {@link net.sourceforge.pmd.lang.metrics.MetricKey}s
* in the relevant language modules.
*
* <p>Metrics are cached by default on the nodes they're computed on.
* Many APIs here are deprecated, this is because metrics were previously
* cached in big static maps, which is replaced by caching on nodes.
*
*/
package net.sourceforge.pmd.lang.metrics;
@@ -24,9 +24,6 @@ import net.sourceforge.pmd.SourceCodeProcessor;
import net.sourceforge.pmd.benchmark.TimeTracker;
import net.sourceforge.pmd.benchmark.TimedOperation;
import net.sourceforge.pmd.benchmark.TimedOperationCategory;
import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.lang.metrics.LanguageMetricsProvider;
import net.sourceforge.pmd.renderers.Renderer;
import net.sourceforge.pmd.util.datasource.DataSource;
@@ -37,7 +34,7 @@ import net.sourceforge.pmd.util.datasource.DataSource;
public abstract class AbstractPMDProcessor {
private static final Logger LOG = Logger.getLogger(AbstractPMDProcessor.class.getName());
protected final PMDConfiguration configuration;
public AbstractPMDProcessor(PMDConfiguration configuration) {
@@ -56,7 +53,7 @@ public abstract class AbstractPMDProcessor {
}
/**
*
*
* @deprecated this method will be removed. It was once used to determine a short filename
* for the file being analyzed, so that shortnames can be reported. But the logic has
* been moved to the renderers.
@@ -78,15 +75,15 @@ public abstract class AbstractPMDProcessor {
*/
protected RuleSets createRuleSets(RuleSetFactory factory, Report report) {
final RuleSets rs = RulesetsFactoryUtils.getRuleSets(configuration.getRuleSets(), factory);
final Set<Rule> brokenRules = removeBrokenRules(rs);
for (final Rule rule : brokenRules) {
report.addConfigError(new Report.ConfigurationError(rule, rule.dysfunctionReason()));
}
return rs;
}
/**
* Remove and return the misconfigured rules from the rulesets and log them
* for good measure.
@@ -117,8 +114,6 @@ public abstract class AbstractPMDProcessor {
configuration.getAnalysisCache().checkValidity(rs, configuration.getClassLoader());
final SourceCodeProcessor processor = new SourceCodeProcessor(configuration);
resetMetrics();
for (final DataSource dataSource : files) {
// this is the real, canonical and absolute filename (not shortened)
String realFileName = dataSource.getNiceFileName(false, null);
@@ -128,7 +123,7 @@ public abstract class AbstractPMDProcessor {
// render base report first - general errors
renderReports(renderers, ctx.getReport());
// then add analysis results per file
collectReports(renderers);
@@ -139,15 +134,6 @@ public abstract class AbstractPMDProcessor {
}
}
private void resetMetrics() {
for (Language language : LanguageRegistry.getLanguages()) {
LanguageMetricsProvider<?, ?> languageMetricsProvider = language.getDefaultVersion().getLanguageVersionHandler().getLanguageMetricsProvider();
if (languageMetricsProvider != null) {
languageMetricsProvider.initialize();
}
}
}
protected abstract void runAnalysis(PmdRunnable runnable);
protected abstract void collectReports(List<Renderer> renderers);
@@ -8,11 +8,14 @@ import java.util.IdentityHashMap;
import java.util.Map;
/**
* An opaque, strongly typed heterogeneous data container.
* An opaque, strongly typed heterogeneous data container. Data maps can
* be set to accept only a certain type of key, with the type parameter.
* The key can itself constrain the type of values, using its own type
* parameter {@code T}.
*
* @param <K> Type of keys in this map
* @param <K> Type of keys in this map.
*/
public class DataMap<K> {
public final class DataMap<K> {
private final Map<DataKey<? extends K, ?>, Object> map = new IdentityHashMap<>();
Loaded 30 of 49 files, more files were not shown because too many files have changed in this diff. Show more