From 5e035b8e57aa38c9052f40a65df2eacd247ac899 Mon Sep 17 00:00:00 2001 From: oowekyala Date: Sat, 29 Jul 2017 16:50:06 +0200 Subject: [PATCH 01/19] Flattened the packages in pmd-core --- .../java/net/sourceforge/pmd/lang/ast/SignedNode.java | 2 +- .../pmd/lang/metrics/AbstractMetricsComputer.java | 3 --- .../pmd/lang/metrics/AbstractMetricsFacade.java | 5 +---- .../sourceforge/pmd/lang/metrics/{api => }/Metric.java | 2 +- .../sourceforge/pmd/lang/metrics/{api => }/MetricKey.java | 3 ++- .../pmd/lang/metrics/{api => }/MetricVersion.java | 2 +- .../net/sourceforge/pmd/lang/metrics/MetricsComputer.java | 3 --- .../pmd/lang/metrics/ParameterizedMetricKey.java | 2 -- .../pmd/lang/metrics/{api => }/ResultOption.java | 2 +- .../pmd/lang/metrics/{signature => }/Signature.java | 2 +- .../pmd/lang/java/metrics/AbstractJavaMetric.java | 2 +- .../sourceforge/pmd/lang/java/metrics/JavaMetrics.java | 8 ++++---- .../pmd/lang/java/metrics/JavaProjectMirror.java | 3 ++- .../pmd/lang/java/metrics/api/JavaClassMetric.java | 2 +- .../pmd/lang/java/metrics/api/JavaClassMetricKey.java | 4 ++-- .../pmd/lang/java/metrics/api/JavaOperationMetric.java | 2 +- .../pmd/lang/java/metrics/api/JavaOperationMetricKey.java | 4 ++-- .../pmd/lang/java/metrics/impl/AtfdMetric.java | 2 +- .../pmd/lang/java/metrics/impl/CycloMetric.java | 4 ++-- .../sourceforge/pmd/lang/java/metrics/impl/LocMetric.java | 2 +- .../pmd/lang/java/metrics/impl/NcssMetric.java | 2 +- .../sourceforge/pmd/lang/java/metrics/impl/WmcMetric.java | 4 ++-- .../lang/java/metrics/rule/CyclomaticComplexityRule.java | 6 +++--- .../pmd/lang/java/metrics/rule/NcssCountRule.java | 6 +++--- .../lang/java/metrics/signature/JavaFieldSignature.java | 2 +- .../java/metrics/signature/JavaOperationSignature.java | 2 +- .../pmd/lang/java/metrics/DataStructureTest.java | 6 +++--- .../pmd/lang/java/metrics/ParameterizedMetricKeyTest.java | 4 ++-- .../lang/java/metrics/impl/AbstractMetricTestRule.java | 6 +++--- .../pmd/lang/java/metrics/impl/CycloTestRule.java | 2 +- .../pmd/lang/java/metrics/impl/NcssTestRule.java | 2 +- 31 files changed, 46 insertions(+), 55 deletions(-) rename pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/{api => }/Metric.java (95%) rename pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/{api => }/MetricKey.java (90%) rename pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/{api => }/MetricVersion.java (91%) rename pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/{api => }/ResultOption.java (94%) rename pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/{signature => }/Signature.java (85%) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/SignedNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/SignedNode.java index 6ccf536c9..6d29939d1 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/SignedNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/SignedNode.java @@ -5,7 +5,7 @@ package net.sourceforge.pmd.lang.ast; -import net.sourceforge.pmd.lang.metrics.signature.Signature; +import net.sourceforge.pmd.lang.metrics.Signature; /** * Nodes that can be described by a signature. diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/AbstractMetricsComputer.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/AbstractMetricsComputer.java index ebd0b99b5..accf8c253 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/AbstractMetricsComputer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/AbstractMetricsComputer.java @@ -9,9 +9,6 @@ import net.sourceforge.pmd.lang.ast.QualifiableNode; import net.sourceforge.pmd.lang.ast.SignedNode; -import net.sourceforge.pmd.lang.metrics.api.MetricKey; -import net.sourceforge.pmd.lang.metrics.api.MetricVersion; -import net.sourceforge.pmd.lang.metrics.api.ResultOption; /** * Base class for metrics computers. These objects compute a metric and memoize it. diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/AbstractMetricsFacade.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/AbstractMetricsFacade.java index 6c61a8f9e..f6f7984ef 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/AbstractMetricsFacade.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/AbstractMetricsFacade.java @@ -6,10 +6,7 @@ import net.sourceforge.pmd.lang.ast.QualifiableNode; import net.sourceforge.pmd.lang.ast.SignedNode; -import net.sourceforge.pmd.lang.metrics.api.Metric.Version; -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.Metric.Version; /** * Base class for a façade that can compute metrics for types, operations and compute aggregate results with a result diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/api/Metric.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/Metric.java similarity index 95% rename from pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/api/Metric.java rename to pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/Metric.java index 7a0596935..5ae9b36fd 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/api/Metric.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/Metric.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.metrics.api; +package net.sourceforge.pmd.lang.metrics; import net.sourceforge.pmd.lang.ast.Node; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/api/MetricKey.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricKey.java similarity index 90% rename from pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/api/MetricKey.java rename to pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricKey.java index 3a6d83c16..f142d5654 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/api/MetricKey.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricKey.java @@ -2,9 +2,10 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.metrics.api; +package net.sourceforge.pmd.lang.metrics; import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.metrics.Metric; /** * Key identifying a metric. Such keys must implement the hashCode method. Enums are well fitted to serve as diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/api/MetricVersion.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricVersion.java similarity index 91% rename from pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/api/MetricVersion.java rename to pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricVersion.java index c8b8fd3b4..cc6fa2a68 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/api/MetricVersion.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricVersion.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.metrics.api; +package net.sourceforge.pmd.lang.metrics; /** * Version of a metric. Only one version can be active on a metric. Versions should typically be defined in an enum diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricsComputer.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricsComputer.java index abc337d02..e05edb996 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricsComputer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricsComputer.java @@ -6,9 +6,6 @@ import net.sourceforge.pmd.lang.ast.QualifiableNode; import net.sourceforge.pmd.lang.ast.SignedNode; -import net.sourceforge.pmd.lang.metrics.api.MetricKey; -import net.sourceforge.pmd.lang.metrics.api.MetricVersion; -import net.sourceforge.pmd.lang.metrics.api.ResultOption; /** * Basic interface for metrics computers that can compute metrics for types, operations and compute aggregate results diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/ParameterizedMetricKey.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/ParameterizedMetricKey.java index ab18ab979..50c6e8eb6 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/ParameterizedMetricKey.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/ParameterizedMetricKey.java @@ -8,8 +8,6 @@ import java.util.Map; import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.metrics.api.MetricKey; -import net.sourceforge.pmd.lang.metrics.api.MetricVersion; /** * Represents a key parameterized with its version. Used to index memoization maps. diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/api/ResultOption.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/ResultOption.java similarity index 94% rename from pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/api/ResultOption.java rename to pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/ResultOption.java index b28c86e42..93d7b69bc 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/api/ResultOption.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/ResultOption.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.metrics.api; +package net.sourceforge.pmd.lang.metrics; /** * Options to calculate a result aggregated on the operations of a class. ResultOptions allow us to return the sum, or diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/signature/Signature.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/Signature.java similarity index 85% rename from pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/signature/Signature.java rename to pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/Signature.java index faa7b6b7a..bda0044c3 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/signature/Signature.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/Signature.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.metrics.signature; +package net.sourceforge.pmd.lang.metrics; import net.sourceforge.pmd.lang.ast.SignedNode; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/AbstractJavaMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/AbstractJavaMetric.java index 1451e8c06..768ecde59 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/AbstractJavaMetric.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/AbstractJavaMetric.java @@ -10,7 +10,7 @@ import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.JavaQualifiedName; -import net.sourceforge.pmd.lang.metrics.api.Metric; +import net.sourceforge.pmd.lang.metrics.Metric; /** diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetrics.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetrics.java index a773c30d1..4e9b4e628 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetrics.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetrics.java @@ -7,10 +7,10 @@ import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; -import net.sourceforge.pmd.lang.metrics.api.Metric.Version; -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.Metric.Version; +import net.sourceforge.pmd.lang.metrics.MetricKey; +import net.sourceforge.pmd.lang.metrics.MetricVersion; +import net.sourceforge.pmd.lang.metrics.ResultOption; /** diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaProjectMirror.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaProjectMirror.java index c856e1911..74f21e418 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaProjectMirror.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaProjectMirror.java @@ -13,5 +13,6 @@ * * @author Clément Fournier */ -public interface JavaProjectMirror extends ProjectMirror { +interface JavaProjectMirror extends ProjectMirror { + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaClassMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaClassMetric.java index 19ca6ab8f..51c7159e1 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaClassMetric.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaClassMetric.java @@ -5,7 +5,7 @@ package net.sourceforge.pmd.lang.java.metrics.api; import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; -import net.sourceforge.pmd.lang.metrics.api.Metric; +import net.sourceforge.pmd.lang.metrics.Metric; /** * Metric that can be computed on a class node. diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaClassMetricKey.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaClassMetricKey.java index 668d6e66d..a7230cca1 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaClassMetricKey.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaClassMetricKey.java @@ -10,8 +10,8 @@ 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.WmcMetric; -import net.sourceforge.pmd.lang.metrics.api.Metric; -import net.sourceforge.pmd.lang.metrics.api.MetricKey; +import net.sourceforge.pmd.lang.metrics.Metric; +import net.sourceforge.pmd.lang.metrics.MetricKey; /** * Keys identifying standard class metrics. diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetric.java index 7907f542d..202608557 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetric.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetric.java @@ -5,7 +5,7 @@ package net.sourceforge.pmd.lang.java.metrics.api; import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; -import net.sourceforge.pmd.lang.metrics.api.Metric; +import net.sourceforge.pmd.lang.metrics.Metric; /** * Metric that can be computed on an operation. diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetricKey.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetricKey.java index 61b8a76b8..92b92e2fa 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetricKey.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetricKey.java @@ -9,8 +9,8 @@ import net.sourceforge.pmd.lang.java.metrics.impl.CycloMetric.CycloOperationMetric; import net.sourceforge.pmd.lang.java.metrics.impl.LocMetric.LocOperationMetric; import net.sourceforge.pmd.lang.java.metrics.impl.NcssMetric.NcssOperationMetric; -import net.sourceforge.pmd.lang.metrics.api.Metric; -import net.sourceforge.pmd.lang.metrics.api.MetricKey; +import net.sourceforge.pmd.lang.metrics.Metric; +import net.sourceforge.pmd.lang.metrics.MetricKey; /** * Keys identifying standard operation metrics. diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AtfdMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AtfdMetric.java index 5165ec451..37640e9a9 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AtfdMetric.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AtfdMetric.java @@ -12,7 +12,7 @@ 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.signature.OperationSigMask; -import net.sourceforge.pmd.lang.metrics.api.MetricVersion; +import net.sourceforge.pmd.lang.metrics.MetricVersion; /** * Access to Foreign Data. Quantifies the number of foreign fields accessed directly or via accessors. diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/CycloMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/CycloMetric.java index 8fc18826c..63db78c19 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/CycloMetric.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/CycloMetric.java @@ -18,8 +18,8 @@ import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey; import net.sourceforge.pmd.lang.java.metrics.impl.visitors.CycloPathUnawareOperationVisitor; import net.sourceforge.pmd.lang.java.metrics.impl.visitors.StandardCycloVisitor; -import net.sourceforge.pmd.lang.metrics.api.MetricVersion; -import net.sourceforge.pmd.lang.metrics.api.ResultOption; +import net.sourceforge.pmd.lang.metrics.MetricVersion; +import net.sourceforge.pmd.lang.metrics.ResultOption; /** * McCabe's Cyclomatic Complexity. Number of independent paths through a block of code [1, 2]. Formally, given that the diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/LocMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/LocMetric.java index 0839cec59..005af3636 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/LocMetric.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/LocMetric.java @@ -6,7 +6,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; -import net.sourceforge.pmd.lang.metrics.api.MetricVersion; +import net.sourceforge.pmd.lang.metrics.MetricVersion; /** * Lines of Code. Equates the length in lines of code of the measured entity, counting everything including blank lines diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NcssMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NcssMetric.java index 290c9bfd4..986c113f3 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NcssMetric.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NcssMetric.java @@ -11,7 +11,7 @@ import net.sourceforge.pmd.lang.java.ast.JavaParserVisitor; import net.sourceforge.pmd.lang.java.metrics.impl.visitors.DefaultNcssVisitor; import net.sourceforge.pmd.lang.java.metrics.impl.visitors.JavaNcssVisitor; -import net.sourceforge.pmd.lang.metrics.api.MetricVersion; +import net.sourceforge.pmd.lang.metrics.MetricVersion; /** * Non Commenting Source Statements. Similar to LOC but only counts statements, which is roughly equivalent to counting diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/WmcMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/WmcMetric.java index 871de0e8e..a411f9879 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/WmcMetric.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/WmcMetric.java @@ -8,8 +8,8 @@ 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.CycloMetric.CycloVersion; -import net.sourceforge.pmd.lang.metrics.api.MetricVersion; -import net.sourceforge.pmd.lang.metrics.api.ResultOption; +import net.sourceforge.pmd.lang.metrics.MetricVersion; +import net.sourceforge.pmd.lang.metrics.ResultOption; /** * Weighed Method Count. It is the sum of the statical complexity of all operations of a class. We use {@link diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/CyclomaticComplexityRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/CyclomaticComplexityRule.java index 195fea228..5b00403dd 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/CyclomaticComplexityRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/CyclomaticComplexityRule.java @@ -16,9 +16,9 @@ import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey; import net.sourceforge.pmd.lang.java.metrics.impl.CycloMetric.CycloVersion; import net.sourceforge.pmd.lang.java.rule.AbstractJavaMetricsRule; -import net.sourceforge.pmd.lang.metrics.api.Metric.Version; -import net.sourceforge.pmd.lang.metrics.api.MetricVersion; -import net.sourceforge.pmd.lang.metrics.api.ResultOption; +import net.sourceforge.pmd.lang.metrics.Metric.Version; +import net.sourceforge.pmd.lang.metrics.MetricVersion; +import net.sourceforge.pmd.lang.metrics.ResultOption; import net.sourceforge.pmd.lang.rule.properties.BooleanProperty; import net.sourceforge.pmd.lang.rule.properties.EnumeratedProperty; import net.sourceforge.pmd.lang.rule.properties.IntegerProperty; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/NcssCountRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/NcssCountRule.java index c9bb78ea2..e0b5934e6 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/NcssCountRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/NcssCountRule.java @@ -16,9 +16,9 @@ import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey; import net.sourceforge.pmd.lang.java.metrics.impl.NcssMetric.NcssVersion; import net.sourceforge.pmd.lang.java.rule.AbstractJavaMetricsRule; -import net.sourceforge.pmd.lang.metrics.api.Metric; -import net.sourceforge.pmd.lang.metrics.api.MetricVersion; -import net.sourceforge.pmd.lang.metrics.api.ResultOption; +import net.sourceforge.pmd.lang.metrics.Metric; +import net.sourceforge.pmd.lang.metrics.MetricVersion; +import net.sourceforge.pmd.lang.metrics.ResultOption; import net.sourceforge.pmd.lang.rule.properties.EnumeratedProperty; import net.sourceforge.pmd.lang.rule.properties.IntegerProperty; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaFieldSignature.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaFieldSignature.java index 37e62e434..e2c9f01e1 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaFieldSignature.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaFieldSignature.java @@ -8,7 +8,7 @@ import java.util.Map; import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; -import net.sourceforge.pmd.lang.metrics.signature.Signature; +import net.sourceforge.pmd.lang.metrics.Signature; /** * Signature for a field. diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaOperationSignature.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaOperationSignature.java index 2011b498e..bd17703cc 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaOperationSignature.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaOperationSignature.java @@ -18,7 +18,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTType; import net.sourceforge.pmd.lang.java.symboltable.ClassScope; import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; -import net.sourceforge.pmd.lang.metrics.signature.Signature; +import net.sourceforge.pmd.lang.metrics.Signature; import net.sourceforge.pmd.lang.symboltable.NameOccurrence; /** diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/DataStructureTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/DataStructureTest.java index 3b669d51d..512a67824 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/DataStructureTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/DataStructureTest.java @@ -37,9 +37,9 @@ import net.sourceforge.pmd.lang.java.metrics.signature.OperationSigMask; import net.sourceforge.pmd.lang.java.metrics.testdata.MetricsVisitorTestData; import net.sourceforge.pmd.lang.metrics.MetricMemoizer; -import net.sourceforge.pmd.lang.metrics.api.Metric.Version; -import net.sourceforge.pmd.lang.metrics.api.MetricKey; -import net.sourceforge.pmd.lang.metrics.api.MetricVersion; +import net.sourceforge.pmd.lang.metrics.Metric.Version; +import net.sourceforge.pmd.lang.metrics.MetricKey; +import net.sourceforge.pmd.lang.metrics.MetricVersion; /** * Tests functionality of the whole data structure (PackageStats, ClassStats, OperationStats). The behaviour of the diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/ParameterizedMetricKeyTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/ParameterizedMetricKeyTest.java index c075a4643..07d62a809 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/ParameterizedMetricKeyTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/ParameterizedMetricKeyTest.java @@ -16,8 +16,8 @@ import net.sourceforge.pmd.lang.java.metrics.api.JavaClassMetricKey; import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey; 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.MetricKey; +import net.sourceforge.pmd.lang.metrics.MetricVersion; /** * @author Clément Fournier diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractMetricTestRule.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractMetricTestRule.java index 277cf2c94..a0b0b112d 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractMetricTestRule.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractMetricTestRule.java @@ -14,9 +14,9 @@ import net.sourceforge.pmd.lang.java.metrics.api.JavaClassMetricKey; import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey; import net.sourceforge.pmd.lang.java.rule.AbstractJavaMetricsRule; -import net.sourceforge.pmd.lang.metrics.api.Metric.Version; -import net.sourceforge.pmd.lang.metrics.api.MetricVersion; -import net.sourceforge.pmd.lang.metrics.api.ResultOption; +import net.sourceforge.pmd.lang.metrics.Metric.Version; +import net.sourceforge.pmd.lang.metrics.MetricVersion; +import net.sourceforge.pmd.lang.metrics.ResultOption; import net.sourceforge.pmd.lang.rule.properties.BooleanProperty; import net.sourceforge.pmd.lang.rule.properties.DoubleProperty; import net.sourceforge.pmd.lang.rule.properties.EnumeratedProperty; diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/CycloTestRule.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/CycloTestRule.java index eb66139ab..fa3ece822 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/CycloTestRule.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/CycloTestRule.java @@ -9,7 +9,7 @@ import net.sourceforge.pmd.lang.java.metrics.api.JavaClassMetricKey; import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey; import net.sourceforge.pmd.lang.java.metrics.impl.CycloMetric.CycloVersion; -import net.sourceforge.pmd.lang.metrics.api.MetricVersion; +import net.sourceforge.pmd.lang.metrics.MetricVersion; /** * Tests standard cyclo. diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/NcssTestRule.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/NcssTestRule.java index 5fe4b05e6..283043c58 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/NcssTestRule.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/NcssTestRule.java @@ -9,7 +9,7 @@ import net.sourceforge.pmd.lang.java.metrics.api.JavaClassMetricKey; import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey; import net.sourceforge.pmd.lang.java.metrics.impl.NcssMetric.NcssVersion; -import net.sourceforge.pmd.lang.metrics.api.MetricVersion; +import net.sourceforge.pmd.lang.metrics.MetricVersion; /** * @author Clément Fournier From 7fd85f77a3bf56054cfcdd10ab1873fe42a06ed6 Mon Sep 17 00:00:00 2001 From: oowekyala Date: Sun, 30 Jul 2017 00:13:56 +0200 Subject: [PATCH 02/19] Groundwork: qualifiednames --- .../adding_metrics_framework_for_language.md | 22 +++++ .../pmd/lang/apex/ast/ASTMethod.java | 18 +++- .../pmd/lang/apex/ast/ASTUserClass.java | 24 +++++- .../lang/apex/ast/ApexQualifiableNode.java | 16 ++++ .../pmd/lang/apex/ast/ApexQualifiedName.java | 84 +++++++++++++++++++ .../pmd/lang/apex/metrics/ApexClassStats.java | 14 ++++ .../lang/apex/metrics/ApexOperationStats.java | 16 ++++ .../lang/apex/metrics/ApexProjectMirror.java | 36 ++++++++ .../signature/ApexOperationSignature.java | 29 +++++++ .../apex/metrics/signature/ApexSignature.java | 11 +++ .../pmd/lang/ast/QualifiedName.java | 17 ++++ .../pmd/lang/java/ast/JavaQualifiedName.java | 12 +-- .../lang/java/metrics/impl/NpathMetric.java | 3 +- 13 files changed, 289 insertions(+), 13 deletions(-) create mode 100644 docs/pages/pmd/devdocs/adding_metrics_framework_for_language.md create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiableNode.java create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexClassStats.java create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexOperationStats.java create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSignature.java create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexSignature.java diff --git a/docs/pages/pmd/devdocs/adding_metrics_framework_for_language.md b/docs/pages/pmd/devdocs/adding_metrics_framework_for_language.md new file mode 100644 index 000000000..119a9b210 --- /dev/null +++ b/docs/pages/pmd/devdocs/adding_metrics_framework_for_language.md @@ -0,0 +1,22 @@ +--- +title: How to implement a metrics framework for an existing language +short_title: Implement a metrics framework +tags: [customizing] +summary: "PMD's Java module has an extensive framework for the calculation of metrics, which allows rule developers +to implement and use new code metrics very simply. Most of the functionality of this framework is abstracted in such +a way that any PMD supported language can implement such a framework without too much trouble. Here's how." +last_updated: July 3, 2016 +sidebar: pmd_sidebar +permalink: pmd_devdocs_adding_new_cpd_language.html +folder: pmd/devdocs +--- + +## Basic steps +* Implement the interface `QualifiedName` in a class. This implementation must be tailored to the target language so +that it can indentify unambiguously any class and operation in the analysed project (see JavaQualifiedName). +* Determine the AST nodes that correspond to class and method declaration in your language. Both these types must +implement the interface `QualifiableNode`, which means they must provide a `getQualifiedName` method to find their +qualified name. +* Implement the interface `Signature`, parameterized with the type of the method AST nodes. Method signatures +describe basic information about a method, which typically includes most of the modifiers they declare (eg +visibility, abstract or virtual, etc.). It's up to you to define a \ No newline at end of file diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethod.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethod.java index 6445aab16..176d2819d 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethod.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethod.java @@ -4,9 +4,13 @@ package net.sourceforge.pmd.lang.apex.ast; +import net.sourceforge.pmd.lang.apex.metrics.signature.ApexOperationSignature; +import net.sourceforge.pmd.lang.ast.SignedNode; +import net.sourceforge.pmd.lang.metrics.Signature; + import apex.jorje.semantic.ast.member.Method; -public class ASTMethod extends AbstractApexNode { +public class ASTMethod extends AbstractApexNode implements ApexQualifiableNode, SignedNode { public ASTMethod(Method method) { super(method); @@ -40,4 +44,16 @@ public int getEndColumn() { return super.getEndColumn(); } + + + @Override + public ApexQualifiedName getQualifiedName() { + return ApexQualifiedName.ofMethod(this); + } + + + @Override + public ApexOperationSignature getSignature() { + return ApexOperationSignature.of(this); + } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClass.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClass.java index b0c83a2fe..06d8460ea 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClass.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClass.java @@ -9,16 +9,21 @@ import apex.jorje.data.ast.Identifier; import apex.jorje.semantic.ast.compilation.UserClass; -public class ASTUserClass extends ApexRootNode { +public class ASTUserClass extends ApexRootNode implements ApexQualifiableNode { + + private ApexQualifiedName qname; + public ASTUserClass(UserClass userClass) { super(userClass); } + public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } + @Override public String getImage() { try { @@ -31,4 +36,21 @@ public String getImage() { } return super.getImage(); } + + + @Override + public ApexQualifiedName getQualifiedName() { + if (qname == null) { + + ASTUserClass parent = this.getFirstParentOfType(ASTUserClass.class); + + if (parent != null) { + qname = ApexQualifiedName.ofNestedClass(parent.getQualifiedName(), this); + } else { + qname = ApexQualifiedName.ofOuterClass(this); + } + } + + return qname; + } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiableNode.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiableNode.java new file mode 100644 index 000000000..13cfb5134 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiableNode.java @@ -0,0 +1,16 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.ast; + +import net.sourceforge.pmd.lang.ast.QualifiableNode; + +/** + * @author Clément Fournier + */ +public interface ApexQualifiableNode extends QualifiableNode { + + @Override + ApexQualifiedName getQualifiedName(); +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java new file mode 100644 index 000000000..89c84078e --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java @@ -0,0 +1,84 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.ast; + +import java.util.Arrays; + +import net.sourceforge.pmd.lang.ast.QualifiedName; + +/** + * Qualified name of an apex class or method. + * + * @author Clément Fournier + */ +public class ApexQualifiedName implements QualifiedName { + + private final String nameSpace; + private final String[] classes; + private final String operation; + + + private ApexQualifiedName(String nameSpace, String[] classes, String operation) { + this.nameSpace = nameSpace; + this.operation = operation; + this.classes = classes; + } + + + @Override + public String getOperation() { + return operation; + } + + + @Override + public String[] getClasses() { + return Arrays.copyOf(classes, classes.length); + } + + + /** + * Gets the namespace prefix of this resource. + * + * @return The namespace prefix + */ + public String getNameSpace() { + return nameSpace; + } + + + @Override + public boolean isClass() { + return operation == null; + } + + + @Override + public boolean isOperation() { + return operation != null; + } + + + static ApexQualifiedName ofOuterClass(ASTUserClass astUserClass) { + String ns = astUserClass.node.getDefiningType().getNamespace().toString(); + String[] classes = {astUserClass.getImage()}; + return new ApexQualifiedName(ns, classes, null); + } + + + static ApexQualifiedName ofNestedClass(ApexQualifiedName parent, ASTUserClass astUserClass) { + + String[] classes = Arrays.copyOf(parent.classes, parent.classes.length); + classes[classes.length - 1] = astUserClass.getImage(); + return new ApexQualifiedName(parent.nameSpace, classes, null); + } + + + static ApexQualifiedName ofMethod(ASTMethod node) { + ApexQualifiedName parent = node.getFirstParentOfType(ASTUserClass.class).getQualifiedName(); + + return new ApexQualifiedName(parent.nameSpace, parent.classes, node.getImage()); + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexClassStats.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexClassStats.java new file mode 100644 index 000000000..c6201c72f --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexClassStats.java @@ -0,0 +1,14 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics; + +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.metrics.AbstractMetricMemoizer; + +/** + * @author Clément Fournier + */ +public class ApexClassStats extends AbstractMetricMemoizer { +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexOperationStats.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexOperationStats.java new file mode 100644 index 000000000..7c12089b2 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexOperationStats.java @@ -0,0 +1,16 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics; + +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.metrics.AbstractMetricMemoizer; + +/** + * Apex operation stats. + * + * @author Clément Fournier + */ +public class ApexOperationStats extends AbstractMetricMemoizer { +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java new file mode 100644 index 000000000..ae3b98162 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java @@ -0,0 +1,36 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics; + +import java.util.HashMap; +import java.util.Map; + +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.ast.ApexQualifiedName; +import net.sourceforge.pmd.lang.ast.QualifiedName; +import net.sourceforge.pmd.lang.metrics.MetricMemoizer; +import net.sourceforge.pmd.lang.metrics.ProjectMirror; + +/** + * @author Clément Fournier + */ +public class ApexProjectMirror implements ProjectMirror { + + private final Map operations = new HashMap<>(); + private final Map classes = new HashMap<>(); + + @Override + public MetricMemoizer getOperationStats(QualifiedName qname) { + return null; + } + + + @Override + public MetricMemoizer getClassStats(QualifiedName qname) { + return null; + } + +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSignature.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSignature.java new file mode 100644 index 000000000..a43bf8e1b --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSignature.java @@ -0,0 +1,29 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics.signature; + +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.metrics.Signature; + +/** + * @author Clément Fournier + */ +public class ApexOperationSignature implements Signature { + + private String foo; + + + /** + * Builds the signature of this node. + * + * @param node The method node + * + * @return The signature of the node + */ + public static ApexOperationSignature of(ASTMethod node) { + return null; + } + +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexSignature.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexSignature.java new file mode 100644 index 000000000..06847fb31 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexSignature.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics.signature; + +/** + * @author Clément Fournier + */ +public class ApexSignature { +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/QualifiedName.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/QualifiedName.java index 7b5f82fb1..b431fc326 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/QualifiedName.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/QualifiedName.java @@ -33,4 +33,21 @@ public interface QualifiedName { */ String[] getClasses(); + + /** + * Returns true if the resource addressed by this qualified name is a class. + * + * @return true if the resource addressed by this qualified name is a class. + */ + boolean isClass(); + + + /** + * Returns true if the resource addressed by this qualified name is an operation. + * + * @return true if the resource addressed by this qualified name is an operation. + */ + boolean isOperation(); + + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedName.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedName.java index 40637441e..edce3ce72 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedName.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedName.java @@ -180,20 +180,12 @@ private static String getOperationName(String methodName, ASTFormalParameters pa return sb.toString(); } - /** - * Returns true if the resource addressed by this qualified name is a class. - * - * @return true if the resource addressed by this qualified name is a class. - */ + @Override public boolean isClass() { return classes[0] != null && operation == null; } - /** - * Returns true if the resource addressed by this qualified name is an operation. - * - * @return true if the resource addressed by this qualified name is an operation. - */ + @Override public boolean isOperation() { return operation != null; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NpathMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NpathMetric.java index d9e2a649a..0eb77ad03 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NpathMetric.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NpathMetric.java @@ -7,7 +7,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; import net.sourceforge.pmd.lang.java.metrics.impl.visitors.DefaultNpathVisitor; -import net.sourceforge.pmd.lang.metrics.api.MetricVersion; +import net.sourceforge.pmd.lang.metrics.MetricVersion; /** * NPath complexity is a measurement of the acyclic execution paths through a function. See Nejmeh, Communications of @@ -21,4 +21,5 @@ public class NpathMetric extends AbstractJavaOperationMetric { public double computeFor(ASTMethodOrConstructorDeclaration node, MetricVersion version) { return (Integer) node.jjtAccept(new DefaultNpathVisitor(), null); } + } From e9fab949b7faf021ef18edc5f542391517769358 Mon Sep 17 00:00:00 2001 From: oowekyala Date: Sun, 30 Jul 2017 15:02:03 +0200 Subject: [PATCH 03/19] Operation signatures --- .../adding_metrics_framework_for_language.md | 35 +++++++++++++--- .../lang/apex/metrics/ApexProjectMirror.java | 26 +++++++++++- .../signature/ApexOperationSignature.java | 42 +++++++++++++++++-- .../apex/metrics/signature/ApexSignature.java | 37 +++++++++++++++- 4 files changed, 129 insertions(+), 11 deletions(-) diff --git a/docs/pages/pmd/devdocs/adding_metrics_framework_for_language.md b/docs/pages/pmd/devdocs/adding_metrics_framework_for_language.md index 119a9b210..b35166599 100644 --- a/docs/pages/pmd/devdocs/adding_metrics_framework_for_language.md +++ b/docs/pages/pmd/devdocs/adding_metrics_framework_for_language.md @@ -14,9 +14,34 @@ folder: pmd/devdocs ## Basic steps * Implement the interface `QualifiedName` in a class. This implementation must be tailored to the target language so that it can indentify unambiguously any class and operation in the analysed project (see JavaQualifiedName). -* Determine the AST nodes that correspond to class and method declaration in your language. Both these types must -implement the interface `QualifiableNode`, which means they must provide a `getQualifiedName` method to find their -qualified name. -* Implement the interface `Signature`, parameterized with the type of the method AST nodes. Method signatures +* Determine the AST nodes that correspond to class and method declaration in your language. These types are +referred hereafter as `T` and `O`, respectively. Both these types must implement the interface `QualifiableNode`, which +means they must provide a `getQualifiedName` method to give access to their qualified name. +* Implement the interface `Signature`, parameterized with the type of the method AST nodes. Method signatures describe basic information about a method, which typically includes most of the modifiers they declare (eg -visibility, abstract or virtual, etc.). It's up to you to define a \ No newline at end of file +visibility, abstract or virtual, etc.). It's up to you to define the right level of detail, depending on the accuracy + of the pattern matching required. +* Make type `O` implement `SignedNode`. This makes the node capable of giving its signature. +* Create a class implementing `Memoizer` and one `Memoizer`. An abstract base class is available. Instances of +these classes each represent a class or operation, respectively. They are used to store the results of metrics that +are already computed. +* Create a class implementing `ProjectMirror`. This class will store the memoizers for all the classes and +interfaces of the analysed project. This class must be able to fetch and return a memoizer given the qualified name +of the resource it represents. As it stores the memoizers, it's a good idea to implement some signature matching +utilities in this class. What's signature matching? (See write custom metrics -- TODO) +* Create a class extending `AbstractMetricsComputer`. This object will be responsible for calculating metrics +given a memoizer, a node and info about the metric. Typically, this object is stateless so you might as well make it +a singleton. +* Create a class extending `AbstractMetricsFacade`. This class needs a reference to your `ProjectMirror` and +your `MetricsComputer`. It backs the real end user façade, and handles user provided parameters before delegating to +your `MetricsComputer`. +* Create the static façade of your framework. This one has an instance of your `MetricsFaçade` object and delegates +static methods to that instance. It should be able to give back +* If you want to implement signature matching, create an `AbstractMetric` class, which gives access to a +`SignatureMatcher` to your metrics. +* Create classes `AbstractOperationMetric` and `AbstractClassMetric`. These must implement `Metric` and +`Metric`, respectively. They typically provide defaults for the `supports` method of each metric. +* Create enums `ClassMetricKey` and `OperationMetricKey`. These must implement `MetricKey` and `MetricKey`. The + enums list all available metric keys for your language. +* Create metrics by extending your base classes, reference them in your enums, and you can start using them with your + façade! \ No newline at end of file diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java index ae3b98162..fd63bff9a 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java @@ -10,6 +10,7 @@ import net.sourceforge.pmd.lang.apex.ast.ASTMethod; import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; import net.sourceforge.pmd.lang.apex.ast.ApexQualifiedName; +import net.sourceforge.pmd.lang.apex.metrics.signature.ApexOperationSignature; import net.sourceforge.pmd.lang.ast.QualifiedName; import net.sourceforge.pmd.lang.metrics.MetricMemoizer; import net.sourceforge.pmd.lang.metrics.ProjectMirror; @@ -19,18 +20,39 @@ */ public class ApexProjectMirror implements ProjectMirror { - private final Map operations = new HashMap<>(); + private final Map> operations = new HashMap<>(); private final Map classes = new HashMap<>(); + + void addOperation(ApexQualifiedName qname, ApexOperationSignature sig) { + if (!operations.containsKey(sig)) { + operations.put(sig, new HashMap<>()); + } + + operations.get(sig).put(qname, new ApexOperationStats()); + } + + + void addClass(ApexQualifiedName qname) { + classes.put(qname, new ApexClassStats()); + } + + @Override public MetricMemoizer getOperationStats(QualifiedName qname) { + for (Map map : operations.values()) { + ApexOperationStats stats = map.get(qname); + if (stats != null) { + return stats; + } + } return null; } @Override public MetricMemoizer getClassStats(QualifiedName qname) { - return null; + return classes.get(qname); } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSignature.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSignature.java index a43bf8e1b..3d2206f8a 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSignature.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSignature.java @@ -4,15 +4,45 @@ package net.sourceforge.pmd.lang.apex.metrics.signature; +import java.util.HashMap; +import java.util.Map; + import net.sourceforge.pmd.lang.apex.ast.ASTMethod; import net.sourceforge.pmd.lang.metrics.Signature; /** * @author Clément Fournier */ -public class ApexOperationSignature implements Signature { +public final class ApexOperationSignature extends ApexSignature implements Signature { - private String foo; + private static final Map POOL = new HashMap<>(); + + + /** + * Create a signature using its visibility. + * + * @param visibility The visibility + */ + private ApexOperationSignature(Visibility visibility) { + super(visibility); + } + + + @Override + public int hashCode() { + return code(visibility); + } + + + @Override + public boolean equals(Object obj) { + return obj == this; + } + + + private static int code(Visibility visibility) { + return visibility.hashCode(); + } /** @@ -23,7 +53,13 @@ public class ApexOperationSignature implements Signature { * @return The signature of the node */ public static ApexOperationSignature of(ASTMethod node) { - return null; + Visibility visibility = Visibility.get(node); + int code = code(visibility); + if (!POOL.containsKey(code)) { + POOL.put(code, new ApexOperationSignature(visibility)); + } + return POOL.get(code); } + } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexSignature.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexSignature.java index 06847fb31..e90a386ba 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexSignature.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexSignature.java @@ -4,8 +4,43 @@ package net.sourceforge.pmd.lang.apex.metrics.signature; +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTModifierNode; + /** + * Base class for apex field or method signatures. + * * @author Clément Fournier */ -public class ApexSignature { +public abstract class ApexSignature { + + /** Visibility of the field or method. */ + public final Visibility visibility; + + + /** Create a signature using its visibility. */ + protected ApexSignature(Visibility visibility) { + this.visibility = visibility; + } + + + /** Visibility of a field or method. */ + public enum Visibility { + PRIVATE, PUBLIC, PROTECTED, GLOBAL; + + + public static Visibility get(ASTMethod method) { + ASTModifierNode modifierNode = method.getFirstChildOfType(ASTModifierNode.class); + if (modifierNode.isPublic()) { + return PUBLIC; + } else if (modifierNode.isPrivate()) { + return PRIVATE; + } else if (modifierNode.isProtected()) { + return PROTECTED; + } else { + return GLOBAL; + } + } + } + } From e02dfae30032eed675e7dbc581816e299b918cb8 Mon Sep 17 00:00:00 2001 From: oowekyala Date: Sun, 30 Jul 2017 15:14:55 +0200 Subject: [PATCH 04/19] Visitor --- .../lang/apex/metrics/ApexMetricsVisitor.java | 29 +++++++++ .../lang/apex/metrics/ApexProjectMirror.java | 28 +++++++-- .../apex/metrics/ApexSignatureMatcher.java | 24 ++++++++ .../signature/ApexOperationSigMask.java | 61 +++++++++++++++++++ 4 files changed, 137 insertions(+), 5 deletions(-) create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitor.java create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexSignatureMatcher.java create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSigMask.java diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitor.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitor.java new file mode 100644 index 000000000..d1037c4a4 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitor.java @@ -0,0 +1,29 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics; + +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.ast.ApexParserVisitorAdapter; + +/** + * @author Clément Fournier + */ +public class ApexMetricsVisitor extends ApexParserVisitorAdapter { + + @Override + public Object visit(ASTUserClass node, Object data) { + ((ApexProjectMirror) data).addClass(node.getQualifiedName()); + return super.visit(node, data); + } + + + @Override + public Object visit(ASTMethod node, Object data) { + ((ApexProjectMirror) data).addOperation(node.getQualifiedName(), node.getSignature()); + return data; + } + +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java index fd63bff9a..941fab511 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java @@ -10,6 +10,7 @@ import net.sourceforge.pmd.lang.apex.ast.ASTMethod; import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; import net.sourceforge.pmd.lang.apex.ast.ApexQualifiedName; +import net.sourceforge.pmd.lang.apex.metrics.signature.ApexOperationSigMask; import net.sourceforge.pmd.lang.apex.metrics.signature.ApexOperationSignature; import net.sourceforge.pmd.lang.ast.QualifiedName; import net.sourceforge.pmd.lang.metrics.MetricMemoizer; @@ -18,23 +19,40 @@ /** * @author Clément Fournier */ -public class ApexProjectMirror implements ProjectMirror { +public class ApexProjectMirror implements ProjectMirror, ApexSignatureMatcher { private final Map> operations = new HashMap<>(); private final Map classes = new HashMap<>(); - void addOperation(ApexQualifiedName qname, ApexOperationSignature sig) { + ApexOperationStats addOperation(ApexQualifiedName qname, ApexOperationSignature sig) { if (!operations.containsKey(sig)) { operations.put(sig, new HashMap<>()); } - operations.get(sig).put(qname, new ApexOperationStats()); + ApexOperationStats stats = new ApexOperationStats(); + operations.get(sig).put(qname, stats); + return stats; } - void addClass(ApexQualifiedName qname) { - classes.put(qname, new ApexClassStats()); + ApexClassStats addClass(ApexQualifiedName qname) { + ApexClassStats stats = new ApexClassStats(); + classes.put(qname, stats); + return stats; + } + + + @Override + public boolean hasMatchingSig(ApexQualifiedName qname, ApexOperationSigMask mask) { + for (ApexOperationSignature sig : operations.keySet()) { + if (mask.covers(sig)) { + if (operations.get(sig).containsKey(qname)) { + return true; + } + } + } + return false; } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexSignatureMatcher.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexSignatureMatcher.java new file mode 100644 index 000000000..aa45ea9c2 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexSignatureMatcher.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics; + +import net.sourceforge.pmd.lang.apex.ast.ApexQualifiedName; +import net.sourceforge.pmd.lang.apex.metrics.signature.ApexOperationSigMask; + +/** + * @author Clément Fournier + */ +public interface ApexSignatureMatcher { + + /** + * 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 + * + * @return True if the signature of the operation designated by the qualified name is covered by the mask + */ + boolean hasMatchingSig(ApexQualifiedName qname, ApexOperationSigMask sigMask); +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSigMask.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSigMask.java new file mode 100644 index 000000000..5ff22e4f0 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSigMask.java @@ -0,0 +1,61 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics.signature; + +import java.util.Arrays; +import java.util.Set; + +import net.sourceforge.pmd.lang.apex.metrics.signature.ApexSignature.Visibility; + +/** + * @author Clément Fournier + */ +public class ApexOperationSigMask { + + private Set visMask; + + + /** + * Sets the mask to cover all visibilities. + */ + public void coverAllVisibilities() { + visMask.addAll(Arrays.asList(Visibility.values())); + } + + + /** + * Restricts the visibilities covered by the mask to the parameters. + * + * @param visibilities The visibilities to cover + */ + public void restrictVisibilitiesTo(Visibility... visibilities) { + visMask.clear(); + visMask.addAll(Arrays.asList(visibilities)); + } + + + /** + * Forbid all mentioned visibilities. + * + * @param visibilities The visibilities to forbid + */ + public void forbid(Visibility... visibilities) { + visMask.removeAll(Arrays.asList(visibilities)); + } + + + /** + * Returns true if the parameter is covered by this mask. + * + * @param sig The signature to test. + * + * @return True if the parameter is covered by this mask + */ + public boolean covers(ApexOperationSignature sig) { + return visMask.contains(sig.visibility); + } + + +} From d9273add4f906fe1c2b7ffcaa91685818504cd8f Mon Sep 17 00:00:00 2001 From: oowekyala Date: Sun, 30 Jul 2017 15:25:58 +0200 Subject: [PATCH 05/19] =?UTF-8?q?Fa=C3=A7ade=20and=20Computer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adding_metrics_framework_for_language.md | 5 +- .../pmd/lang/apex/metrics/ApexMetrics.java | 132 ++++++++++++++++++ .../apex/metrics/ApexMetricsComputer.java | 24 ++++ .../lang/apex/metrics/ApexMetricsFacade.java | 36 +++++ .../lang/apex/metrics/ApexProjectMirror.java | 6 + 5 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsComputer.java create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsFacade.java diff --git a/docs/pages/pmd/devdocs/adding_metrics_framework_for_language.md b/docs/pages/pmd/devdocs/adding_metrics_framework_for_language.md index b35166599..9811676ba 100644 --- a/docs/pages/pmd/devdocs/adding_metrics_framework_for_language.md +++ b/docs/pages/pmd/devdocs/adding_metrics_framework_for_language.md @@ -36,9 +36,10 @@ a singleton. your `MetricsComputer`. It backs the real end user façade, and handles user provided parameters before delegating to your `MetricsComputer`. * Create the static façade of your framework. This one has an instance of your `MetricsFaçade` object and delegates -static methods to that instance. It should be able to give back +static methods to that instance. * If you want to implement signature matching, create an `AbstractMetric` class, which gives access to a -`SignatureMatcher` to your metrics. +`SignatureMatcher` to your metrics. Typically, your implementation of `ProjectMirror` implements a +custom `SignatureMatcher` interface, and your façade can give back its instance of the project mirror. * Create classes `AbstractOperationMetric` and `AbstractClassMetric`. These must implement `Metric` and `Metric`, respectively. They typically provide defaults for the `supports` method of each metric. * Create enums `ClassMetricKey` and `OperationMetricKey`. These must implement `MetricKey` and `MetricKey`. The diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java new file mode 100644 index 000000000..88e6ed1ce --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java @@ -0,0 +1,132 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics; + +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.metrics.Metric.Version; +import net.sourceforge.pmd.lang.metrics.MetricKey; +import net.sourceforge.pmd.lang.metrics.MetricVersion; +import net.sourceforge.pmd.lang.metrics.ResultOption; + +/** + * @author Clément Fournier + */ +public final class ApexMetrics { + + private static final ApexMetricsFacade FACADE = new ApexMetricsFacade(); + + + private ApexMetrics() { // Cannot be instantiated + + } + + + /** + * Returns the project mirror of the analysed project. + * + * @return The project mirror + */ + static ApexProjectMirror getTopLevelPackageStats() { + return FACADE.getLanguageSpecificProjectMirror(); + } + + + /** Resets the entire data structure. Used for tests. */ + static void reset() { + FACADE.reset(); + } + + + /** + * Computes the standard value of the metric identified by its code on a class AST node. + * + * @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 couln't be computed + */ + public static double get(MetricKey key, ASTUserClass node) { + return FACADE.computeForType(key, node, Version.STANDARD); + } + + + /** + * Computes a metric identified by its code on a class AST node, possibly selecting a variant with the {@code + * MetricVersion} parameter. + * + * @param key The key identifying the metric to be computed + * @param node The node on which to compute the metric + * @param version The version of the metric + * + * @return The value of the metric, or {@code Double.NaN} if the value couln't be computed + */ + public static double get(MetricKey key, ASTUserClass node, MetricVersion version) { + return FACADE.computeForType(key, node, version); + } + + + /** + * Computes the standard version of the metric identified by the key on a operation AST node. + * + * @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 couln't be computed + */ + public static double get(MetricKey key, ASTMethod node) { + return FACADE.computeForOperation(key, node, Version.STANDARD); + } + + + /** + * Computes a metric identified by its key on a operation AST node. + * + * @param key The key identifying the metric to be computed + * @param node The node on which to compute the metric + * @param version The version of the metric + * + * @return The value of the metric, or {@code Double.NaN} if the value couln't be computed + */ + public static double get(MetricKey key, ASTMethod node, MetricVersion version) { + return FACADE.computeForOperation(key, node, version); + } + + + /** + * Compute the sum, average, or highest value of the standard operation metric on all operations of the class node. + * The type of operation is specified by the {@link ResultOption} parameter. + * + * @param key The key identifying the metric to be computed + * @param node The node on which to compute the metric + * @param option The result option to use + * + * @return The value of the metric, or {@code Double.NaN} if the value couln't be computed or {@code option} is + * {@code null} + */ + public static double get(MetricKey key, ASTUserClass node, ResultOption option) { + return FACADE.computeWithResultOption(key, node, Version.STANDARD, option); + } + + + /** + * Compute the sum, average, or highest value of the operation metric on all operations of the class node. The type + * of operation is specified by the {@link ResultOption} parameter. + * + * @param key The key identifying the metric to be computed + * @param node The node on which to compute the metric + * @param version The version of the metric + * @param option The result option to use + * + * @return The value of the metric, or {@code Double.NaN} if the value couln't be computed or {@code option} is + * {@code null} + */ + public static double get(MetricKey key, ASTUserClass node, MetricVersion version, + ResultOption option) { + return FACADE.computeWithResultOption(key, node, version, option); + } + + +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsComputer.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsComputer.java new file mode 100644 index 000000000..7329455e5 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsComputer.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics; + +import java.util.List; + +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.metrics.AbstractMetricsComputer; + +/** + * @author Clément Fournier + */ +public class ApexMetricsComputer extends AbstractMetricsComputer { + + public static final ApexMetricsComputer INSTANCE = new ApexMetricsComputer(); + + @Override + protected List findOperations(ASTUserClass node) { + return node.findChildrenOfType(ASTMethod.class); + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsFacade.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsFacade.java new file mode 100644 index 000000000..a82c68864 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsFacade.java @@ -0,0 +1,36 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics; + +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.metrics.AbstractMetricsFacade; +import net.sourceforge.pmd.lang.metrics.MetricsComputer; + +/** + * @author Clément Fournier + */ +public class ApexMetricsFacade extends AbstractMetricsFacade { + + private final ApexProjectMirror projectMirror = new ApexProjectMirror(); + + + /** Resets the entire project mirror. Used for tests. */ + void reset() { + projectMirror.reset(); + } + + + @Override + protected MetricsComputer getLanguageSpecificComputer() { + return ApexMetricsComputer.INSTANCE; + } + + + @Override + protected ApexProjectMirror getLanguageSpecificProjectMirror() { + return projectMirror; + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java index 941fab511..586417005 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java @@ -25,6 +25,12 @@ public class ApexProjectMirror implements ProjectMirror private final Map classes = new HashMap<>(); + void reset() { + operations.clear(); + classes.clear(); + } + + ApexOperationStats addOperation(ApexQualifiedName qname, ApexOperationSignature sig) { if (!operations.containsKey(sig)) { operations.put(sig, new HashMap<>()); From 8f8e129e4f1f86861c8883af44c58c1f458eed07 Mon Sep 17 00:00:00 2001 From: oowekyala Date: Sun, 30 Jul 2017 16:44:17 +0200 Subject: [PATCH 06/19] Metric keys --- .../lang/apex/metrics/AbstractApexMetric.java | 16 +++++ .../pmd/lang/apex/metrics/ApexMetrics.java | 2 +- .../apex/metrics/api/ApexClassMetric.java | 14 ++++ .../apex/metrics/api/ApexClassMetricKey.java | 36 ++++++++++ .../apex/metrics/api/ApexOperationMetric.java | 14 ++++ .../metrics/api/ApexOperationMetricKey.java | 34 ++++++++++ .../metrics/impl/AbstractApexClassMetric.java | 14 ++++ .../impl/AbstractApexOperationMetric.java | 14 ++++ .../sourceforge/pmd/lang/MetricKeyUtil.java | 66 +++++++++++++++++++ .../java/metrics/api/JavaClassMetricKey.java | 47 ------------- .../metrics/api/JavaOperationMetricKey.java | 52 --------------- .../lang/java/metrics/DataStructureTest.java | 5 +- .../metrics/ParameterizedMetricKeyTest.java | 3 +- 13 files changed, 214 insertions(+), 103 deletions(-) create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/AbstractApexMetric.java create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexClassMetric.java create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexClassMetricKey.java create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexOperationMetric.java create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexOperationMetricKey.java create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexClassMetric.java create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexOperationMetric.java create mode 100644 pmd-core/src/main/java/net/sourceforge/pmd/lang/MetricKeyUtil.java diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/AbstractApexMetric.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/AbstractApexMetric.java new file mode 100644 index 000000000..dc852897d --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/AbstractApexMetric.java @@ -0,0 +1,16 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics; + +/** + * @author Clément Fournier + */ +public class AbstractApexMetric { + + protected ApexSignatureMatcher getSignatureMatcher() { + return ApexMetrics.getApexProjectMirror(); + } + +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java index 88e6ed1ce..aadc10ebc 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java @@ -29,7 +29,7 @@ private ApexMetrics() { // Cannot be instantiated * * @return The project mirror */ - static ApexProjectMirror getTopLevelPackageStats() { + static ApexProjectMirror getApexProjectMirror() { return FACADE.getLanguageSpecificProjectMirror(); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexClassMetric.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexClassMetric.java new file mode 100644 index 000000000..1c002b87e --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexClassMetric.java @@ -0,0 +1,14 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics.api; + +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.metrics.Metric; + +/** + * @author Clément Fournier + */ +public interface ApexClassMetric extends Metric { +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexClassMetricKey.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexClassMetricKey.java new file mode 100644 index 000000000..3f0253dd4 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexClassMetricKey.java @@ -0,0 +1,36 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics.api; + +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.metrics.MetricKey; + +/** + * @author Clément Fournier + */ +public enum ApexClassMetricKey implements MetricKey { + ; + + + private final ApexClassMetric calculator; + + + ApexClassMetricKey(ApexClassMetric m) { + calculator = m; + } + + + @Override + public ApexClassMetric getCalculator() { + return calculator; + } + + + @Override + public boolean supports(ASTUserClass node) { + return calculator.supports(node); + } + +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexOperationMetric.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexOperationMetric.java new file mode 100644 index 000000000..79ed8323c --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexOperationMetric.java @@ -0,0 +1,14 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics.api; + +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.metrics.Metric; + +/** + * @author Clément Fournier + */ +public interface ApexOperationMetric extends Metric { +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexOperationMetricKey.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexOperationMetricKey.java new file mode 100644 index 000000000..7c4c01cf0 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexOperationMetricKey.java @@ -0,0 +1,34 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics.api; + +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.metrics.MetricKey; + +/** + * @author Clément Fournier + */ +public enum ApexOperationMetricKey implements MetricKey { + ; + + private final ApexOperationMetric calculator; + + + ApexOperationMetricKey(ApexOperationMetric m) { + calculator = m; + } + + + @Override + public ApexOperationMetric getCalculator() { + return calculator; + } + + + @Override + public boolean supports(ASTMethod node) { + return calculator.supports(node); + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexClassMetric.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexClassMetric.java new file mode 100644 index 000000000..d5f7f01c1 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexClassMetric.java @@ -0,0 +1,14 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics.impl; + +import net.sourceforge.pmd.lang.apex.metrics.AbstractApexMetric; +import net.sourceforge.pmd.lang.apex.metrics.api.ApexClassMetric; + +/** + * @author Clément Fournier + */ +public abstract class AbstractApexClassMetric extends AbstractApexMetric implements ApexClassMetric { +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexOperationMetric.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexOperationMetric.java new file mode 100644 index 000000000..4050e8b87 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexOperationMetric.java @@ -0,0 +1,14 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics.impl; + +import net.sourceforge.pmd.lang.apex.metrics.AbstractApexMetric; +import net.sourceforge.pmd.lang.apex.metrics.api.ApexOperationMetric; + +/** + * @author Clément Fournier + */ +public abstract class AbstractApexOperationMetric extends AbstractApexMetric implements ApexOperationMetric { +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/MetricKeyUtil.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/MetricKeyUtil.java new file mode 100644 index 000000000..1684e91ed --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/MetricKeyUtil.java @@ -0,0 +1,66 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang; + +import java.util.Objects; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.metrics.Metric; +import net.sourceforge.pmd.lang.metrics.MetricKey; + +/** + * @author Clément Fournier + */ +public class MetricKeyUtil { + + private MetricKeyUtil() { + + } + + /** + * Creates a new metric key holding a metric which can be computed on a class. + * + * TODO:cf Move that to the MetricKey interface once we upgrade the compiler + * + * @param metric The metric to use + * @param name The name of the metric + * + * @return The metric key + */ + public static MetricKey of(final Metric metric, final String name) { + return new MetricKey() { + @Override + public String name() { + return name; + } + + + @Override + public Metric getCalculator() { + return metric; + } + + + @Override + public boolean supports(T node) { + return metric.supports(node); + } + + + @Override + public boolean equals(Object obj) { + return obj != null && getClass() == obj.getClass() + && Objects.equals(name(), ((MetricKey) obj).name()) + && Objects.equals(getCalculator(), ((MetricKey) obj).getCalculator()); + } + + + @Override + public int hashCode() { + return (metric != null ? metric.hashCode() * 31 : 0) + (name != null ? name.hashCode() : 0); + } + }; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaClassMetricKey.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaClassMetricKey.java index 664b6faa3..211f2824d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaClassMetricKey.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaClassMetricKey.java @@ -4,15 +4,12 @@ package net.sourceforge.pmd.lang.java.metrics.api; -import java.util.Objects; - import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; import net.sourceforge.pmd.lang.java.metrics.impl.AtfdMetric.AtfdClassMetric; import net.sourceforge.pmd.lang.java.metrics.impl.CycloMetric.CycloClassMetric; 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.WmcMetric; -import net.sourceforge.pmd.lang.metrics.Metric; import net.sourceforge.pmd.lang.metrics.MetricKey; /** @@ -75,48 +72,4 @@ public boolean supports(ASTAnyTypeDeclaration node) { } - /** - * Creates a new metric key holding a metric which can be computed on a class. - * - * TODO:cf Generify and move to MetricKey after upgrading compiler to 1.8 - * - * @param metric The metric to use - * @param name The name of the metric - * - * @return The metric key - */ - public static MetricKey of(final Metric metric, final String name) { - return new MetricKey() { - @Override - public String name() { - return name; - } - - - @Override - public Metric getCalculator() { - return metric; - } - - - @Override - public boolean supports(ASTAnyTypeDeclaration node) { - return metric.supports(node); - } - - - @Override - public boolean equals(Object obj) { - return obj != null && getClass() == obj.getClass() - && Objects.equals(name(), getClass().cast(obj).name()) - && Objects.equals(getCalculator(), getClass().cast(obj).getCalculator()); - } - - - @Override - public int hashCode() { - return (metric != null ? metric.hashCode() * 31 : 0) + (name != null ? name.hashCode() : 0); - } - }; - } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetricKey.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetricKey.java index 5419fa109..a6236e6cd 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetricKey.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetricKey.java @@ -4,15 +4,12 @@ package net.sourceforge.pmd.lang.java.metrics.api; -import java.util.Objects; - import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; import net.sourceforge.pmd.lang.java.metrics.impl.AtfdMetric.AtfdOperationMetric; import net.sourceforge.pmd.lang.java.metrics.impl.CycloMetric.CycloOperationMetric; import net.sourceforge.pmd.lang.java.metrics.impl.LocMetric.LocOperationMetric; import net.sourceforge.pmd.lang.java.metrics.impl.NcssMetric.NcssOperationMetric; import net.sourceforge.pmd.lang.java.metrics.impl.NpathMetric; -import net.sourceforge.pmd.lang.metrics.Metric; import net.sourceforge.pmd.lang.metrics.MetricKey; /** @@ -57,8 +54,6 @@ public enum JavaOperationMetricKey implements MetricKey of(final Metric metric, final String name) { - return new MetricKey() { - @Override - public String name() { - return name; - } - - - @Override - public Metric getCalculator() { - return metric; - } - - - @Override - public boolean supports(ASTMethodOrConstructorDeclaration node) { - return metric.supports(node); - } - - - @Override - public boolean equals(Object obj) { - return obj != null && getClass() == obj.getClass() - && Objects.equals(name(), getClass().cast(obj).name()) - && Objects.equals(getCalculator(), getClass().cast(obj).getCalculator()); - } - - - @Override - public int hashCode() { - return (metric != null ? metric.hashCode() * 31 : 0) + (name != null ? name.hashCode() : 0); - } - - }; - } - - } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/DataStructureTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/DataStructureTest.java index 512a67824..d1f1a2249 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/DataStructureTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/DataStructureTest.java @@ -19,6 +19,7 @@ import org.junit.Before; import org.junit.Test; +import net.sourceforge.pmd.lang.MetricKeyUtil; import net.sourceforge.pmd.lang.java.ParserTst; import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; @@ -49,8 +50,8 @@ */ public class DataStructureTest extends ParserTst { - private MetricKey classMetricKey = JavaClassMetricKey.of(new RandomClassMetric(), null); - private MetricKey opMetricKey = JavaOperationMetricKey.of(new RandomOperationMetric(), null); + private MetricKey classMetricKey = MetricKeyUtil.of(new RandomClassMetric(), null); + private MetricKey opMetricKey = MetricKeyUtil.of(new RandomOperationMetric(), null); private PackageStats pack; diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/ParameterizedMetricKeyTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/ParameterizedMetricKeyTest.java index 07d62a809..956085444 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/ParameterizedMetricKeyTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/ParameterizedMetricKeyTest.java @@ -12,6 +12,7 @@ import org.junit.Test; +import net.sourceforge.pmd.lang.MetricKeyUtil; import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; import net.sourceforge.pmd.lang.java.metrics.api.JavaClassMetricKey; import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey; @@ -73,7 +74,7 @@ public void testToString() { @Test public void testAdHocMetricKey() { - MetricKey adHocKey = JavaClassMetricKey.of(null, "metric"); + MetricKey adHocKey = MetricKeyUtil.of(null, "metric"); MetricVersion adHocVersion = new MetricVersion() { @Override From 1bee5d7e3941995fea6623dcc392e4cf2ceff5ff Mon Sep 17 00:00:00 2001 From: oowekyala Date: Sun, 30 Jul 2017 18:56:00 +0200 Subject: [PATCH 07/19] Minimal tests --- .../pmd/lang/apex/ApexHandler.java | 11 ++ .../pmd/lang/apex/ast/ApexQualifiedName.java | 76 ++++++++++++- .../metrics/ApexMetricsVisitorFacade.java | 21 ++++ .../signature/ApexOperationSigMask.java | 8 +- .../lang/apex/ast/ApexQualifiedNameTest.java | 86 +++++++++++++++ .../apex/metrics/ApexMetricsVisitorTest.java | 102 ++++++++++++++++++ .../metrics/signature/OperationSigMask.java | 12 +-- .../lang/java/metrics/signature/SigMask.java | 11 +- 8 files changed, 309 insertions(+), 18 deletions(-) create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorFacade.java create mode 100644 pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedNameTest.java create mode 100644 pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorTest.java diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexHandler.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexHandler.java index 7a3df258f..92dd3d030 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexHandler.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexHandler.java @@ -13,6 +13,7 @@ import net.sourceforge.pmd.lang.XPathHandler; import net.sourceforge.pmd.lang.apex.ast.ApexNode; import net.sourceforge.pmd.lang.apex.ast.DumpFacade; +import net.sourceforge.pmd.lang.apex.metrics.ApexMetricsVisitorFacade; import net.sourceforge.pmd.lang.apex.rule.ApexRuleViolationFactory; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.xpath.AbstractASTXPathHandler; @@ -54,4 +55,14 @@ public void start(Node rootNode) { } }; } + + @Override + public VisitorStarter getMetricsVisitorFacade() { + return new VisitorStarter() { + @Override + public void start(Node rootNode) { + new ApexMetricsVisitorFacade().initializeWith((ApexNode) rootNode); + } + }; + } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java index 89c84078e..45e5c9723 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java @@ -5,9 +5,14 @@ package net.sourceforge.pmd.lang.apex.ast; import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.regex.Pattern; import net.sourceforge.pmd.lang.ast.QualifiedName; +import apex.jorje.semantic.symbol.type.TypeInfo; + /** * Qualified name of an apex class or method. * @@ -15,6 +20,8 @@ */ public class ApexQualifiedName implements QualifiedName { + private static final Pattern FORMAT = Pattern.compile("(\\w+)__(\\w+)(.(\\w+))?(#(\\w+))?"); + private final String nameSpace; private final String[] classes; private final String operation; @@ -61,6 +68,48 @@ public boolean isOperation() { } + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(nameSpace).append("__"); + sb.append(classes[0]); + + if (classes.length > 1) { + sb.append('.').append(classes[1]); + } + + if (isOperation()) { + sb.append("#").append(operation); + } + + return sb.toString(); + } + + + public static ApexQualifiedName ofString(String toParse) { + return null; + } + + + @Override + public int hashCode() { + int result = nameSpace.hashCode(); + result = 31 * result + Arrays.hashCode(classes); + result = 31 * result + (operation != null ? operation.hashCode() : 0); + return result; + } + + + @Override + public boolean equals(Object obj) { + return obj instanceof ApexQualifiedName + && Objects.deepEquals(classes, ((ApexQualifiedName) obj).classes) + && Objects.equals(operation, ((ApexQualifiedName) obj).operation) + && Objects.equals(nameSpace, ((ApexQualifiedName) obj).nameSpace); + + } + + static ApexQualifiedName ofOuterClass(ASTUserClass astUserClass) { String ns = astUserClass.node.getDefiningType().getNamespace().toString(); String[] classes = {astUserClass.getImage()}; @@ -70,15 +119,38 @@ static ApexQualifiedName ofOuterClass(ASTUserClass astUserClass) { static ApexQualifiedName ofNestedClass(ApexQualifiedName parent, ASTUserClass astUserClass) { - String[] classes = Arrays.copyOf(parent.classes, parent.classes.length); + String[] classes = Arrays.copyOf(parent.classes, parent.classes.length + 1); classes[classes.length - 1] = astUserClass.getImage(); return new ApexQualifiedName(parent.nameSpace, classes, null); } + private static String getOperationString(ASTMethod node) { + StringBuilder sb = new StringBuilder(); + sb.append(node.getImage()).append('('); + + + List paramTypes = node.getNode().getMethodInfo().getParameterTypes(); + + if (paramTypes.size() > 0) { + sb.append(paramTypes.get(0).getApexName()); + + for (int i = 1; i < paramTypes.size(); i++) { + sb.append(",").append(paramTypes.get(i).getTypeSignature()); + } + + } + + sb.append(')'); + + return sb.toString(); + } + + static ApexQualifiedName ofMethod(ASTMethod node) { ApexQualifiedName parent = node.getFirstParentOfType(ASTUserClass.class).getQualifiedName(); - return new ApexQualifiedName(parent.nameSpace, parent.classes, node.getImage()); + + return new ApexQualifiedName(parent.nameSpace, parent.classes, getOperationString(node)); } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorFacade.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorFacade.java new file mode 100644 index 000000000..d01a9968f --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorFacade.java @@ -0,0 +1,21 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics; + +import net.sourceforge.pmd.lang.apex.ast.ApexNode; +import net.sourceforge.pmd.lang.apex.ast.ApexParserVisitorAdapter; + +/** + * @author Clément Fournier + */ +public class ApexMetricsVisitorFacade extends ApexParserVisitorAdapter { + + + public void initializeWith(ApexNode rootNode) { + ApexMetricsVisitor visitor = new ApexMetricsVisitor(); + rootNode.jjtAccept(visitor, ApexMetrics.getApexProjectMirror()); + } + +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSigMask.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSigMask.java index 5ff22e4f0..71389ae16 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSigMask.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSigMask.java @@ -5,6 +5,7 @@ package net.sourceforge.pmd.lang.apex.metrics.signature; import java.util.Arrays; +import java.util.EnumSet; import java.util.Set; import net.sourceforge.pmd.lang.apex.metrics.signature.ApexSignature.Visibility; @@ -14,7 +15,12 @@ */ public class ApexOperationSigMask { - private Set visMask; + private Set visMask = EnumSet.allOf(Visibility.class); + + + public ApexOperationSigMask() { + coverAllVisibilities(); + } /** diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedNameTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedNameTest.java new file mode 100644 index 000000000..0e97f5084 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedNameTest.java @@ -0,0 +1,86 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.ast; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.util.List; + +import org.junit.Test; + +import apex.jorje.semantic.ast.compilation.Compilation; + +/** + * @author Clément Fournier + */ +public class ApexQualifiedNameTest { + + @Test + public void testClass() { + ApexNode root = ApexParserTestHelpers.parse("public class Foo {}"); + + ApexQualifiedName qname = ASTUserClass.class.cast(root).getQualifiedName(); + assertEquals("__Foo", qname.toString()); + assertEquals(1, qname.getClasses().length); + assertNotNull(qname.getNameSpace()); + assertNull(qname.getOperation()); + } + + + @Test + public void testNestedClass() { + ApexNode root = ApexParserTestHelpers.parse("public class Foo { class Bar {}}"); + + ApexQualifiedName qname = root.getFirstDescendantOfType(ASTUserClass.class).getQualifiedName(); + assertEquals("__Foo.Bar", qname.toString()); + assertEquals(2, qname.getClasses().length); + assertNotNull(qname.getNameSpace()); + assertNull(qname.getOperation()); + } + + + @Test + public void testSimpleMethod() { + ApexNode root = ApexParserTestHelpers.parse("public class Foo { String foo() {}}"); + ApexQualifiedName qname = root.getFirstDescendantOfType(ASTMethod.class).getQualifiedName(); + assertEquals("__Foo#foo()", qname.toString()); + assertEquals(1, qname.getClasses().length); + assertNotNull(qname.getNameSpace()); + assertEquals("foo()", qname.getOperation()); + } + + + @Test + public void testMethodWithArguments() { + ApexNode root = ApexParserTestHelpers.parse("public class Foo { String foo(String h, Foo g) {}}"); + ApexQualifiedName qname = root.getFirstDescendantOfType(ASTMethod.class).getQualifiedName(); + assertEquals("__Foo#foo(String,LFoo;)", qname.toString()); + assertEquals(1, qname.getClasses().length); + assertNotNull(qname.getNameSpace()); + assertEquals("foo(String,LFoo;)", qname.getOperation()); + } + + + @Test + public void testOverLoads() { + ApexNode root = ApexParserTestHelpers.parse("public class Foo { " + + "String foo(String h) {} " + + "String foo(int c) {}" + + "String foo(Foo c) {}}"); + + List methods = root.findDescendantsOfType(ASTMethod.class); + + for (ASTMethod m1 : methods) { + for (ASTMethod m2 : methods) { + if (m1 != m2) { + assertNotEquals(m1.getQualifiedName(), m2.getQualifiedName()); + } + } + } + } +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorTest.java new file mode 100644 index 000000000..f316fdf72 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorTest.java @@ -0,0 +1,102 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersionHandler; +import net.sourceforge.pmd.lang.apex.ApexLanguageModule; +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ApexNode; +import net.sourceforge.pmd.lang.apex.ast.ApexParserTest; +import net.sourceforge.pmd.lang.apex.ast.ApexParserTestHelpers; +import net.sourceforge.pmd.lang.apex.ast.ApexParserVisitorAdapter; +import net.sourceforge.pmd.lang.apex.metrics.signature.ApexOperationSigMask; + +import apex.jorje.semantic.ast.compilation.Compilation; + +/** + * @author Clément Fournier + */ +public class ApexMetricsVisitorTest extends ApexParserTest { + + @Test + public void testProjectMirrorNotNull() { + assertNotNull(ApexMetrics.getApexProjectMirror()); + } + + + @Test + public void testOperationsAreThere() { + ApexNode acu = parseAndVisitForString("public with sharing class MetadataDeployController \n" + + "{\n" + + "\tprivate class Foo {\n" + + "}\n" + + "\n" + + "\tglobal String ZipData { get; set; }\t\n" + + "\t\n" + + "\tpublic MetadataService.AsyncResult AsyncResult {get; private set;}\n" + + "\t\n" + + "\tpublic String getPackageXml(String page)\n" + + "\t{\n" + + "\t\treturn '' + \n" + + "\t\t\t'' + \n" + + " \t\t\t'' + \n" + + " \t\t\t'HelloWorld' +\n" + + " \t\t\t'ApexClass' + \n" + + " \t\t\t'' + \n" + + " \t\t\t'26.0' + \n" + + "\t\t\t'';\t\t\n" + + "\t}\n" + + "\t\n" + + "\tpublic String getHelloWorldMetadata()\n" + + "\t{\n" + + "\t\treturn '' +\n" + + "\t\t\t'' +\n" + + "\t\t\t '28.0' + \n" + + "\t\t\t 'Active' +\n" + + "\t\t\t'';\t\t\n" + + "\t}\n" + + "\t\n" + + "\tpublic String getHelloWorld()\t\n" + + "\t{\n" + + "\t\treturn 'public class HelloWorld' + \n" + + "\t\t\t'{' + \n" + + "\t\t\t\t'public static void helloWorld()' +\n" + + "\t\t\t\t'{' + \n" + + "\t\t\t\t\t'System.debug(\\' Hello World\\');' +\n" + + "\t\t\t\t'}' +\n" + + "\t\t\t'}';\n" + + "\t}" + + "}"); + + final ApexSignatureMatcher toplevel = ApexMetrics.getApexProjectMirror(); + + final ApexOperationSigMask opMask = new ApexOperationSigMask(); + + // We could parse qnames from string but probably simpler to do that + acu.jjtAccept(new ApexParserVisitorAdapter() { + @Override + public Object visit(ASTMethod node, Object data) { + assertTrue(toplevel.hasMatchingSig(node.getQualifiedName(), opMask)); + return data; + } + }, null); + } + + + private static ApexNode parseAndVisitForString(String source) { + LanguageVersionHandler languageVersionHandler = LanguageRegistry.getLanguage(ApexLanguageModule.NAME) + .getDefaultVersion().getLanguageVersionHandler(); + ApexNode acu = ApexParserTestHelpers.parse(source); + languageVersionHandler.getSymbolFacade().start(acu); + languageVersionHandler.getMetricsVisitorFacade().start(acu); + return acu; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/OperationSigMask.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/OperationSigMask.java index b8af32666..f74ada37f 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/OperationSigMask.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/OperationSigMask.java @@ -5,9 +5,11 @@ package net.sourceforge.pmd.lang.java.metrics.signature; import java.util.Arrays; -import java.util.HashSet; +import java.util.EnumSet; import java.util.Set; +import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSignature.Role; + /** * Signature mask for an operation. Newly created masks cover any operation that is not abstract. * @@ -15,16 +17,10 @@ */ public final class OperationSigMask extends SigMask { - private Set roleMask = new HashSet<>(); + private Set roleMask = EnumSet.allOf(Role.class); private boolean coverAbstract = false; - public OperationSigMask() { - super(); - coverAllRoles(); - } - - /** * Sets the mask to cover all roles. */ diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/SigMask.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/SigMask.java index 7ec82e671..b427db2fe 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/SigMask.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/SigMask.java @@ -5,9 +5,11 @@ package net.sourceforge.pmd.lang.java.metrics.signature; import java.util.Arrays; -import java.util.HashSet; +import java.util.EnumSet; import java.util.Set; +import net.sourceforge.pmd.lang.java.metrics.signature.JavaSignature.Visibility; + /** * Generic signature mask. * @@ -18,12 +20,7 @@ public abstract class SigMask { /** Visibility mask. */ - private Set visMask = new HashSet<>(); - - - public SigMask() { - coverAllVisibilities(); - } + private Set visMask = EnumSet.allOf(Visibility.class); /** From 162f4bc3f06d7edf8382edf904b7b975774db57d Mon Sep 17 00:00:00 2001 From: oowekyala Date: Sun, 30 Jul 2017 22:56:56 +0200 Subject: [PATCH 08/19] Memoization tests ok --- 1 | 0 .../pmd/lang/apex/ast/ASTMethod.java | 1 - .../apex/metrics/api/ApexClassMetricKey.java | 2 +- .../metrics/api/ApexOperationMetricKey.java | 3 +- .../metrics/impl/AbstractApexClassMetric.java | 6 + .../impl/AbstractApexOperationMetric.java | 6 + .../apex/metrics/ApexMetricsVisitorTest.java | 2 +- .../apex/metrics/ApexProjectMirrorTest.java | 156 ++++++++++++++++++ .../pmd/lang/metrics/MetricKey.java | 1 - ...reTest.java => JavaProjectMirrorTest.java} | 6 +- .../metrics/ParameterizedMetricKeyTest.java | 2 +- 11 files changed, 175 insertions(+), 10 deletions(-) create mode 100644 1 create mode 100644 pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirrorTest.java rename pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/{DataStructureTest.java => JavaProjectMirrorTest.java} (97%) diff --git a/1 b/1 new file mode 100644 index 000000000..e69de29bb diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethod.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethod.java index 176d2819d..500191566 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethod.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethod.java @@ -6,7 +6,6 @@ import net.sourceforge.pmd.lang.apex.metrics.signature.ApexOperationSignature; import net.sourceforge.pmd.lang.ast.SignedNode; -import net.sourceforge.pmd.lang.metrics.Signature; import apex.jorje.semantic.ast.member.Method; diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexClassMetricKey.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexClassMetricKey.java index 3f0253dd4..23705e97d 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexClassMetricKey.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexClassMetricKey.java @@ -11,7 +11,7 @@ * @author Clément Fournier */ public enum ApexClassMetricKey implements MetricKey { - ; + DUMMY(null); private final ApexClassMetric calculator; diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexOperationMetricKey.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexOperationMetricKey.java index 7c4c01cf0..7d55fec5f 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexOperationMetricKey.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexOperationMetricKey.java @@ -11,7 +11,8 @@ * @author Clément Fournier */ public enum ApexOperationMetricKey implements MetricKey { - ; + DUMMY(null); + private final ApexOperationMetric calculator; diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexClassMetric.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexClassMetric.java index d5f7f01c1..af6aeeee6 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexClassMetric.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexClassMetric.java @@ -4,6 +4,7 @@ package net.sourceforge.pmd.lang.apex.metrics.impl; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; import net.sourceforge.pmd.lang.apex.metrics.AbstractApexMetric; import net.sourceforge.pmd.lang.apex.metrics.api.ApexClassMetric; @@ -11,4 +12,9 @@ * @author Clément Fournier */ public abstract class AbstractApexClassMetric extends AbstractApexMetric implements ApexClassMetric { + + @Override + public boolean supports(ASTUserClass node) { + return true; + } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexOperationMetric.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexOperationMetric.java index 4050e8b87..1e85bc531 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexOperationMetric.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexOperationMetric.java @@ -4,6 +4,7 @@ package net.sourceforge.pmd.lang.apex.metrics.impl; +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; import net.sourceforge.pmd.lang.apex.metrics.AbstractApexMetric; import net.sourceforge.pmd.lang.apex.metrics.api.ApexOperationMetric; @@ -11,4 +12,9 @@ * @author Clément Fournier */ public abstract class AbstractApexOperationMetric extends AbstractApexMetric implements ApexOperationMetric { + + @Override + public boolean supports(ASTMethod node) { + return true; + } } diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorTest.java index f316fdf72..a1b06a874 100644 --- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorTest.java +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorTest.java @@ -91,7 +91,7 @@ public Object visit(ASTMethod node, Object data) { } - private static ApexNode parseAndVisitForString(String source) { + static ApexNode parseAndVisitForString(String source) { LanguageVersionHandler languageVersionHandler = LanguageRegistry.getLanguage(ApexLanguageModule.NAME) .getDefaultVersion().getLanguageVersionHandler(); ApexNode acu = ApexParserTestHelpers.parse(source); diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirrorTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirrorTest.java new file mode 100644 index 000000000..797ecf07a --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirrorTest.java @@ -0,0 +1,156 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics; + +import static net.sourceforge.pmd.lang.apex.metrics.ApexMetricsVisitorTest.parseAndVisitForString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import org.junit.Test; + +import net.sourceforge.pmd.lang.MetricKeyUtil; +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.ast.ApexNode; +import net.sourceforge.pmd.lang.apex.ast.ApexParserVisitorAdapter; +import net.sourceforge.pmd.lang.apex.metrics.impl.AbstractApexClassMetric; +import net.sourceforge.pmd.lang.apex.metrics.impl.AbstractApexOperationMetric; +import net.sourceforge.pmd.lang.metrics.Metric.Version; +import net.sourceforge.pmd.lang.metrics.MetricKey; +import net.sourceforge.pmd.lang.metrics.MetricMemoizer; +import net.sourceforge.pmd.lang.metrics.MetricVersion; + +import apex.jorje.semantic.ast.compilation.Compilation; + +/** + * @author Clément Fournier + */ +public class ApexProjectMirrorTest { + + private static ApexNode acu + = parseAndVisitForString("public with sharing class MetadataDeployController \n" + + "{\n" + + "\tprivate class Foo {\n" + + "}\n" + + "\n" + + "\tglobal String ZipData { get; set; }\t\n" + + "\t\n" + + "\tpublic MetadataService.AsyncResult AsyncResult {get; private set;}\n" + + "\t\n" + + "\tpublic String getPackageXml(String page)\n" + + "\t{\n" + + "\t\treturn '' + \n" + + "\t\t\t'' + \n" + + " \t\t\t'' + \n" + + " \t\t\t'HelloWorld' +\n" + + " \t\t\t'ApexClass' + \n" + + " \t\t\t'' + \n" + + " \t\t\t'26.0' + \n" + + "\t\t\t'';\t\t\n" + + "\t}\n" + + "\t\n" + + "\tpublic String getHelloWorldMetadata()\n" + + "\t{\n" + + "\t\treturn '' +\n" + + "\t\t\t'' +\n" + + "\t\t\t '28.0' + \n" + + "\t\t\t 'Active' +\n" + + "\t\t\t'';\t\t\n" + + "\t}\n" + + "\t\n" + + "\tpublic String getHelloWorld()\t\n" + + "\t{\n" + + "\t\treturn 'public class HelloWorld' + \n" + + "\t\t\t'{' + \n" + + "\t\t\t\t'public static void helloWorld()' +\n" + + "\t\t\t\t'{' + \n" + + "\t\t\t\t\t'System.debug(\\' Hello World\\');' +\n" + + "\t\t\t\t'}' +\n" + + "\t\t\t'}';\n" + + "\t}" + + "}"); + private MetricKey classMetricKey = MetricKeyUtil.of(new RandomClassMetric(), null); + private MetricKey opMetricKey = MetricKeyUtil.of(new RandomOperationMetric(), null); + + + @Test + public void memoizationTest() { + + + List expected = visitWith(acu, true); + List real = visitWith(acu, false); + + assertEquals(expected, real); + } + + + @Test + public void forceMemoizationTest() { + + List reference = visitWith(acu, true); + List real = visitWith(acu, true); + + assertEquals(reference.size(), real.size()); + + // we force recomputation so each result should be different + for (int i = 0; i < reference.size(); i++) { + assertNotEquals(reference.get(i), real.get(i)); + } + } + + + private List visitWith(ApexNode acu, final boolean force) { + final ApexProjectMirror toplevel = ApexMetrics.getApexProjectMirror(); + + final List result = new ArrayList<>(); + + acu.jjtAccept(new ApexParserVisitorAdapter() { + @Override + public Object visit(ASTMethod node, Object data) { + MetricMemoizer op = toplevel.getOperationStats(node.getQualifiedName()); + result.add((int) ApexMetricsComputer.INSTANCE.computeForOperation(opMetricKey, node, force, Version.STANDARD, op)); + return super.visit(node, data); + } + + + @Override + public Object visit(ASTUserClass node, Object data) { + MetricMemoizer clazz = toplevel.getClassStats(node.getQualifiedName()); + result.add((int) ApexMetricsComputer.INSTANCE.computeForType(classMetricKey, node, force, Version.STANDARD, clazz)); + return super.visit(node, data); + } + }, null); + + return result; + } + + + private class RandomOperationMetric extends AbstractApexOperationMetric { + + private Random random = new Random(); + + + @Override + public double computeFor(ASTMethod node, MetricVersion version) { + return random.nextInt(); + } + } + + private class RandomClassMetric extends AbstractApexClassMetric { + + private Random random = new Random(); + + + @Override + public double computeFor(ASTUserClass node, MetricVersion version) { + return random.nextInt(); + } + } + +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricKey.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricKey.java index f142d5654..03cc817ae 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricKey.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricKey.java @@ -5,7 +5,6 @@ package net.sourceforge.pmd.lang.metrics; import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.metrics.Metric; /** * Key identifying a metric. Such keys must implement the hashCode method. Enums are well fitted to serve as diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/DataStructureTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/JavaProjectMirrorTest.java similarity index 97% rename from pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/DataStructureTest.java rename to pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/JavaProjectMirrorTest.java index d1f1a2249..923ba1068 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/DataStructureTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/JavaProjectMirrorTest.java @@ -28,8 +28,6 @@ import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorReducedAdapter; import net.sourceforge.pmd.lang.java.ast.JavaQualifiedName; -import net.sourceforge.pmd.lang.java.metrics.api.JavaClassMetricKey; -import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey; import net.sourceforge.pmd.lang.java.metrics.impl.AbstractJavaClassMetric; import net.sourceforge.pmd.lang.java.metrics.impl.AbstractJavaOperationMetric; import net.sourceforge.pmd.lang.java.metrics.signature.FieldSigMask; @@ -37,9 +35,9 @@ import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSignature; import net.sourceforge.pmd.lang.java.metrics.signature.OperationSigMask; import net.sourceforge.pmd.lang.java.metrics.testdata.MetricsVisitorTestData; -import net.sourceforge.pmd.lang.metrics.MetricMemoizer; import net.sourceforge.pmd.lang.metrics.Metric.Version; import net.sourceforge.pmd.lang.metrics.MetricKey; +import net.sourceforge.pmd.lang.metrics.MetricMemoizer; import net.sourceforge.pmd.lang.metrics.MetricVersion; /** @@ -48,7 +46,7 @@ * * @author Clément Fournier */ -public class DataStructureTest extends ParserTst { +public class JavaProjectMirrorTest extends ParserTst { private MetricKey classMetricKey = MetricKeyUtil.of(new RandomClassMetric(), null); private MetricKey opMetricKey = MetricKeyUtil.of(new RandomOperationMetric(), null); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/ParameterizedMetricKeyTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/ParameterizedMetricKeyTest.java index 956085444..e433861d1 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/ParameterizedMetricKeyTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/ParameterizedMetricKeyTest.java @@ -16,9 +16,9 @@ import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; import net.sourceforge.pmd.lang.java.metrics.api.JavaClassMetricKey; import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey; -import net.sourceforge.pmd.lang.metrics.ParameterizedMetricKey; import net.sourceforge.pmd.lang.metrics.MetricKey; import net.sourceforge.pmd.lang.metrics.MetricVersion; +import net.sourceforge.pmd.lang.metrics.ParameterizedMetricKey; /** * @author Clément Fournier From e4e3fd33d81394c40f30d2e65ac1066f4526c174 Mon Sep 17 00:00:00 2001 From: oowekyala Date: Tue, 1 Aug 2017 16:55:30 +0200 Subject: [PATCH 09/19] Marker interface for class and interface --- .../pmd/lang/apex/ast/ASTUserClass.java | 8 ++++- .../apex/ast/ASTUserClassOrInterface.java | 30 +++++++++++++++++++ .../pmd/lang/apex/ast/ASTUserInterface.java | 27 ++++++++++++++++- .../apex/ast/ApexParserVisitorAdapter.java | 3 +- .../pmd/lang/apex/ast/ApexQualifiedName.java | 8 ++--- 5 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClassOrInterface.java diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClass.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClass.java index 06d8460ea..c506ce13c 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClass.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClass.java @@ -9,7 +9,7 @@ import apex.jorje.data.ast.Identifier; import apex.jorje.semantic.ast.compilation.UserClass; -public class ASTUserClass extends ApexRootNode implements ApexQualifiableNode { +public class ASTUserClass extends ApexRootNode implements ASTUserClassOrInterface { private ApexQualifiedName qname; @@ -53,4 +53,10 @@ public ApexQualifiedName getQualifiedName() { return qname; } + + + @Override + public TypeKind getTypeKind() { + return TypeKind.CLASS; + } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClassOrInterface.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClassOrInterface.java new file mode 100644 index 000000000..467656d13 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClassOrInterface.java @@ -0,0 +1,30 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.ast; + +import apex.jorje.semantic.ast.AstNode; + +/** + * @author Clément Fournier + */ +public interface ASTUserClassOrInterface extends ApexQualifiableNode, ApexNode { + + /** + * Finds the type kind of this declaration. + * + * @return The type kind of this declaration. + */ + TypeKind getTypeKind(); + + + /** + * The kind of type this node declares. + */ + enum TypeKind { + CLASS, INTERFACE + } + + +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserInterface.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserInterface.java index 4e15e7426..f9b8a181b 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserInterface.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserInterface.java @@ -9,7 +9,9 @@ import apex.jorje.data.ast.Identifier; import apex.jorje.semantic.ast.compilation.UserInterface; -public class ASTUserInterface extends ApexRootNode { +public class ASTUserInterface extends ApexRootNode implements ASTUserClassOrInterface { + + private ApexQualifiedName qname; public ASTUserInterface(UserInterface userInterface) { super(userInterface); @@ -31,4 +33,27 @@ public String getImage() { } return super.getImage(); } + + + @Override + public TypeKind getTypeKind() { + return TypeKind.INTERFACE; + } + + + @Override + public ApexQualifiedName getQualifiedName() { + if (qname == null) { + + ASTUserClass parent = this.getFirstParentOfType(ASTUserClass.class); + + if (parent != null) { + qname = ApexQualifiedName.ofNestedClass(parent.getQualifiedName(), this); + } else { + qname = ApexQualifiedName.ofOuterClass(this); + } + } + + return qname; + } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParserVisitorAdapter.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParserVisitorAdapter.java index fcbb799aa..d17eff076 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParserVisitorAdapter.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParserVisitorAdapter.java @@ -7,8 +7,7 @@ public class ApexParserVisitorAdapter implements ApexParserVisitor { @Override public Object visit(ApexNode node, Object data) { - node.childrenAccept(this, data); - return null; + return node.childrenAccept(this, data); } @Override diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java index 45e5c9723..e682927aa 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java @@ -110,14 +110,14 @@ public boolean equals(Object obj) { } - static ApexQualifiedName ofOuterClass(ASTUserClass astUserClass) { - String ns = astUserClass.node.getDefiningType().getNamespace().toString(); + static ApexQualifiedName ofOuterClass(ASTUserClassOrInterface astUserClass) { + String ns = astUserClass.getNode().getDefiningType().getNamespace().toString(); String[] classes = {astUserClass.getImage()}; return new ApexQualifiedName(ns, classes, null); } - static ApexQualifiedName ofNestedClass(ApexQualifiedName parent, ASTUserClass astUserClass) { + static ApexQualifiedName ofNestedClass(ApexQualifiedName parent, ASTUserClassOrInterface astUserClass) { String[] classes = Arrays.copyOf(parent.classes, parent.classes.length + 1); classes[classes.length - 1] = astUserClass.getImage(); @@ -148,7 +148,7 @@ private static String getOperationString(ASTMethod node) { static ApexQualifiedName ofMethod(ASTMethod node) { - ApexQualifiedName parent = node.getFirstParentOfType(ASTUserClass.class).getQualifiedName(); + ApexQualifiedName parent = node.getFirstParentOfType(ASTUserClassOrInterface.class).getQualifiedName(); return new ApexQualifiedName(parent.nameSpace, parent.classes, getOperationString(node)); From e1c985c7d5aeab64fd05b4a6de1f8d39aff2df85 Mon Sep 17 00:00:00 2001 From: oowekyala Date: Tue, 1 Aug 2017 16:56:21 +0200 Subject: [PATCH 10/19] Adapted components to AST --- .../pmd/lang/apex/metrics/ApexClassStats.java | 4 ++-- .../sourceforge/pmd/lang/apex/metrics/ApexMetrics.java | 9 +++++---- .../pmd/lang/apex/metrics/ApexMetricsComputer.java | 6 +++--- .../pmd/lang/apex/metrics/ApexMetricsFacade.java | 6 +++--- .../pmd/lang/apex/metrics/ApexProjectMirror.java | 6 +++--- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexClassStats.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexClassStats.java index c6201c72f..a65dc76d3 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexClassStats.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexClassStats.java @@ -4,11 +4,11 @@ package net.sourceforge.pmd.lang.apex.metrics; -import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClassOrInterface; import net.sourceforge.pmd.lang.metrics.AbstractMetricMemoizer; /** * @author Clément Fournier */ -public class ApexClassStats extends AbstractMetricMemoizer { +public class ApexClassStats extends AbstractMetricMemoizer> { } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java index aadc10ebc..5ea43129d 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java @@ -6,6 +6,7 @@ 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.Metric.Version; import net.sourceforge.pmd.lang.metrics.MetricKey; import net.sourceforge.pmd.lang.metrics.MetricVersion; @@ -48,7 +49,7 @@ static void reset() { * * @return The value of the metric, or {@code Double.NaN} if the value couln't be computed */ - public static double get(MetricKey key, ASTUserClass node) { + public static double get(MetricKey> key, ASTUserClass node) { return FACADE.computeForType(key, node, Version.STANDARD); } @@ -63,7 +64,7 @@ public static double get(MetricKey key, ASTUserClass node) { * * @return The value of the metric, or {@code Double.NaN} if the value couln't be computed */ - public static double get(MetricKey key, ASTUserClass node, MetricVersion version) { + public static double get(MetricKey> key, ASTUserClass node, MetricVersion version) { return FACADE.computeForType(key, node, version); } @@ -106,7 +107,7 @@ public static double get(MetricKey key, ASTMethod node, MetricVersion * @return The value of the metric, or {@code Double.NaN} if the value couln't be computed or {@code option} is * {@code null} */ - public static double get(MetricKey key, ASTUserClass node, ResultOption option) { + public static double get(MetricKey key, ASTUserClassOrInterface node, ResultOption option) { return FACADE.computeWithResultOption(key, node, Version.STANDARD, option); } @@ -123,7 +124,7 @@ public static double get(MetricKey key, ASTUserClass node, ResultOpti * @return The value of the metric, or {@code Double.NaN} if the value couln't be computed or {@code option} is * {@code null} */ - public static double get(MetricKey key, ASTUserClass node, MetricVersion version, + public static double get(MetricKey key, ASTUserClassOrInterface node, MetricVersion version, ResultOption option) { return FACADE.computeWithResultOption(key, node, version, option); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsComputer.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsComputer.java index 7329455e5..cc20d48ce 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsComputer.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsComputer.java @@ -7,18 +7,18 @@ import java.util.List; 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.AbstractMetricsComputer; /** * @author Clément Fournier */ -public class ApexMetricsComputer extends AbstractMetricsComputer { +public class ApexMetricsComputer extends AbstractMetricsComputer, ASTMethod> { public static final ApexMetricsComputer INSTANCE = new ApexMetricsComputer(); @Override - protected List findOperations(ASTUserClass node) { + protected List findOperations(ASTUserClassOrInterface node) { return node.findChildrenOfType(ASTMethod.class); } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsFacade.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsFacade.java index a82c68864..ae8790cd3 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsFacade.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsFacade.java @@ -5,14 +5,14 @@ package net.sourceforge.pmd.lang.apex.metrics; 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.AbstractMetricsFacade; import net.sourceforge.pmd.lang.metrics.MetricsComputer; /** * @author Clément Fournier */ -public class ApexMetricsFacade extends AbstractMetricsFacade { +public class ApexMetricsFacade extends AbstractMetricsFacade, ASTMethod> { private final ApexProjectMirror projectMirror = new ApexProjectMirror(); @@ -24,7 +24,7 @@ void reset() { @Override - protected MetricsComputer getLanguageSpecificComputer() { + protected MetricsComputer, ASTMethod> getLanguageSpecificComputer() { return ApexMetricsComputer.INSTANCE; } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java index 586417005..117488437 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java @@ -8,7 +8,7 @@ import java.util.Map; 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.apex.ast.ApexQualifiedName; import net.sourceforge.pmd.lang.apex.metrics.signature.ApexOperationSigMask; import net.sourceforge.pmd.lang.apex.metrics.signature.ApexOperationSignature; @@ -19,7 +19,7 @@ /** * @author Clément Fournier */ -public class ApexProjectMirror implements ProjectMirror, ApexSignatureMatcher { +public class ApexProjectMirror implements ProjectMirror, ASTMethod>, ApexSignatureMatcher { private final Map> operations = new HashMap<>(); private final Map classes = new HashMap<>(); @@ -75,7 +75,7 @@ public MetricMemoizer getOperationStats(QualifiedName qname) { @Override - public MetricMemoizer getClassStats(QualifiedName qname) { + public MetricMemoizer> getClassStats(QualifiedName qname) { return classes.get(qname); } From 68452939abe8f46d07f3bd019d9c49b787253fc1 Mon Sep 17 00:00:00 2001 From: oowekyala Date: Tue, 1 Aug 2017 17:01:10 +0200 Subject: [PATCH 11/19] More work --- .../apex/metrics/api/ApexClassMetric.java | 4 +- .../apex/metrics/api/ApexClassMetricKey.java | 9 +- .../metrics/api/ApexOperationMetricKey.java | 3 +- .../metrics/impl/AbstractApexClassMetric.java | 7 +- .../impl/AbstractApexOperationMetric.java | 4 +- .../apex/metrics/signature/ApexSignature.java | 7 + .../lang/apex/metrics/ApexMetricsHook.java | 25 +++ .../apex/metrics/ApexProjectMirrorTest.java | 7 +- .../impl/AbstractApexMetricTestRule.java | 153 ++++++++++++++++++ .../apex/metrics/impl/AllMetricsTest.java | 35 ++++ .../lang/apex/metrics/impl/CycloTestRule.java | 28 ++++ .../lang/apex/metrics/impl/WmcTestRule.java | 31 ++++ 12 files changed, 299 insertions(+), 14 deletions(-) create mode 100644 pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsHook.java create mode 100644 pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexMetricTestRule.java create mode 100644 pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/impl/AllMetricsTest.java create mode 100644 pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/impl/CycloTestRule.java create mode 100644 pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/impl/WmcTestRule.java diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexClassMetric.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexClassMetric.java index 1c002b87e..83bb14286 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexClassMetric.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexClassMetric.java @@ -4,11 +4,11 @@ package net.sourceforge.pmd.lang.apex.metrics.api; -import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClassOrInterface; import net.sourceforge.pmd.lang.metrics.Metric; /** * @author Clément Fournier */ -public interface ApexClassMetric extends Metric { +public interface ApexClassMetric extends Metric> { } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexClassMetricKey.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexClassMetricKey.java index 23705e97d..2ffae23a4 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexClassMetricKey.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexClassMetricKey.java @@ -4,14 +4,15 @@ package net.sourceforge.pmd.lang.apex.metrics.api; -import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClassOrInterface; +import net.sourceforge.pmd.lang.apex.metrics.impl.WmcMetric; import net.sourceforge.pmd.lang.metrics.MetricKey; /** * @author Clément Fournier */ -public enum ApexClassMetricKey implements MetricKey { - DUMMY(null); +public enum ApexClassMetricKey implements MetricKey> { + WMC(new WmcMetric()); private final ApexClassMetric calculator; @@ -29,7 +30,7 @@ public ApexClassMetric getCalculator() { @Override - public boolean supports(ASTUserClass node) { + public boolean supports(ASTUserClassOrInterface node) { return calculator.supports(node); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexOperationMetricKey.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexOperationMetricKey.java index 7d55fec5f..502dbbd06 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexOperationMetricKey.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/api/ApexOperationMetricKey.java @@ -5,13 +5,14 @@ package net.sourceforge.pmd.lang.apex.metrics.api; import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.metrics.impl.CycloMetric; import net.sourceforge.pmd.lang.metrics.MetricKey; /** * @author Clément Fournier */ public enum ApexOperationMetricKey implements MetricKey { - DUMMY(null); + CYCLO(new CycloMetric()); private final ApexOperationMetric calculator; diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexClassMetric.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexClassMetric.java index af6aeeee6..876a65585 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexClassMetric.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexClassMetric.java @@ -4,7 +4,8 @@ package net.sourceforge.pmd.lang.apex.metrics.impl; -import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClassOrInterface; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClassOrInterface.TypeKind; import net.sourceforge.pmd.lang.apex.metrics.AbstractApexMetric; import net.sourceforge.pmd.lang.apex.metrics.api.ApexClassMetric; @@ -14,7 +15,7 @@ public abstract class AbstractApexClassMetric extends AbstractApexMetric implements ApexClassMetric { @Override - public boolean supports(ASTUserClass node) { - return true; + public boolean supports(ASTUserClassOrInterface node) { + return node.getTypeKind() == TypeKind.CLASS; } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexOperationMetric.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexOperationMetric.java index 1e85bc531..cdbc31b46 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexOperationMetric.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexOperationMetric.java @@ -5,6 +5,7 @@ package net.sourceforge.pmd.lang.apex.metrics.impl; import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTModifierNode; import net.sourceforge.pmd.lang.apex.metrics.AbstractApexMetric; import net.sourceforge.pmd.lang.apex.metrics.api.ApexOperationMetric; @@ -15,6 +16,7 @@ public abstract class AbstractApexOperationMetric extends AbstractApexMetric imp @Override public boolean supports(ASTMethod node) { - return true; + return !node.getImage().matches("(||clone)") + && !node.getFirstChildOfType(ASTModifierNode.class).isAbstract(); } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexSignature.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexSignature.java index e90a386ba..d780e5d75 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexSignature.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexSignature.java @@ -29,6 +29,13 @@ public enum Visibility { PRIVATE, PUBLIC, PROTECTED, GLOBAL; + /** + * Finds out the visibility of a method node. + * + * @param method The method node + * + * @return The visibility of the method + */ public static Visibility get(ASTMethod method) { ASTModifierNode modifierNode = method.getFirstChildOfType(ASTModifierNode.class); if (modifierNode.isPublic()) { diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsHook.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsHook.java new file mode 100644 index 000000000..c376addaa --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsHook.java @@ -0,0 +1,25 @@ +/** + * 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(); + } + + +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirrorTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirrorTest.java index 797ecf07a..5202e851f 100644 --- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirrorTest.java +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirrorTest.java @@ -17,6 +17,7 @@ import net.sourceforge.pmd.lang.MetricKeyUtil; 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.apex.ast.ApexNode; import net.sourceforge.pmd.lang.apex.ast.ApexParserVisitorAdapter; import net.sourceforge.pmd.lang.apex.metrics.impl.AbstractApexClassMetric; @@ -75,7 +76,7 @@ public class ApexProjectMirrorTest { + "\t\t\t'}';\n" + "\t}" + "}"); - private MetricKey classMetricKey = MetricKeyUtil.of(new RandomClassMetric(), null); + private MetricKey> classMetricKey = MetricKeyUtil.of(new RandomClassMetric(), null); private MetricKey opMetricKey = MetricKeyUtil.of(new RandomOperationMetric(), null); @@ -121,7 +122,7 @@ public Object visit(ASTMethod node, Object data) { @Override public Object visit(ASTUserClass node, Object data) { - MetricMemoizer clazz = toplevel.getClassStats(node.getQualifiedName()); + MetricMemoizer> clazz = toplevel.getClassStats(node.getQualifiedName()); result.add((int) ApexMetricsComputer.INSTANCE.computeForType(classMetricKey, node, force, Version.STANDARD, clazz)); return super.visit(node, data); } @@ -148,7 +149,7 @@ private class RandomClassMetric extends AbstractApexClassMetric { @Override - public double computeFor(ASTUserClass node, MetricVersion version) { + public double computeFor(ASTUserClassOrInterface node, MetricVersion version) { return random.nextInt(); } } diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexMetricTestRule.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexMetricTestRule.java new file mode 100644 index 000000000..5e8fc045f --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/impl/AbstractApexMetricTestRule.java @@ -0,0 +1,153 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics.impl; + +import java.util.HashMap; +import java.util.Map; + +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +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.Metric.Version; +import net.sourceforge.pmd.lang.metrics.MetricVersion; +import net.sourceforge.pmd.lang.metrics.ResultOption; +import net.sourceforge.pmd.lang.rule.properties.BooleanProperty; +import net.sourceforge.pmd.lang.rule.properties.DoubleProperty; +import net.sourceforge.pmd.lang.rule.properties.EnumeratedProperty; + +/** + * Abstract test rule for a metric. Tests of metrics use the standard framework for rule testing, using one dummy rule + * per metric. Default parameters can be overriden by overriding the protected methods of this class. + * + * @author Clément Fournier + */ +public abstract class AbstractApexMetricTestRule extends AbstractApexRule { + + private final EnumeratedProperty versionDescriptor = new EnumeratedProperty<>( + "metricVersion", "Choose a variant of the metric or the standard", + versionMappings(), Version.STANDARD, MetricVersion.class, 3.0f); + private final BooleanProperty reportClassesDescriptor = new BooleanProperty( + "reportClasses", "Add class violations to the report", isReportClasses(), 2.0f); + private final BooleanProperty reportMethodsDescriptor = new BooleanProperty( + "reportMethods", "Add method violations to the report", isReportMethods(), 3.0f); + private final DoubleProperty reportLevelDescriptor = new DoubleProperty( + "reportLevel", "Minimum value required to report", -1., Double.POSITIVE_INFINITY, defaultReportLevel(), 3.0f); + + private MetricVersion metricVersion; + private boolean reportClasses; + private boolean reportMethods; + private double reportLevel; + private ApexClassMetricKey classKey; + private ApexOperationMetricKey opKey; + + + public AbstractApexMetricTestRule() { + classKey = getClassKey(); + opKey = getOpKey(); + + definePropertyDescriptor(reportClassesDescriptor); + definePropertyDescriptor(reportMethodsDescriptor); + definePropertyDescriptor(reportLevelDescriptor); + definePropertyDescriptor(versionDescriptor); + } + + + /** + * Returns the class metric key to test, or null if we shouldn't test classes. + * + * @return The class metric key to test. + */ + protected abstract ApexClassMetricKey getClassKey(); + + + /** + * Returns the class metric key to test, or null if we shouldn't test classes. + * + * @return The class metric key to test. + */ + protected abstract ApexOperationMetricKey getOpKey(); + + + /** + * Sets the default for reportClasses descriptor. + * + * @return The default for reportClasses descriptor + */ + protected boolean isReportClasses() { + return true; + } + + + /** + * Sets the default for reportMethods descriptor. + * + * @return The default for reportMethods descriptor + */ + protected boolean isReportMethods() { + return true; + } + + + /** + * Mappings of labels to versions for use in the version property. + * + * @return A map of labels to versions + */ + protected Map versionMappings() { + Map mappings = new HashMap<>(); + mappings.put("standard", Version.STANDARD); + return mappings; + } + + + /** + * Default report level, which is 0. + * + * @return The default report level. + */ + protected double defaultReportLevel() { + return 0.; + } + + + @Override + public Object visit(ASTUserClass node, Object data) { + reportClasses = getProperty(reportClassesDescriptor); + reportMethods = getProperty(reportMethodsDescriptor); + reportLevel = getProperty(reportLevelDescriptor); + metricVersion = getProperty(versionDescriptor); + + + if (classKey != null && reportClasses && classKey.supports(node)) { + int classValue = (int) ApexMetrics.get(classKey, node, metricVersion); + + String valueReport = String.valueOf(classValue); + + if (opKey != null) { + int highest = (int) ApexMetrics.get(opKey, node, metricVersion, ResultOption.HIGHEST); + valueReport += " highest " + highest; + } + if (classValue >= reportLevel) { + addViolation(data, node, new String[] {node.getQualifiedName().toString(), valueReport}); + } + } + return super.visit(node, data); + } + + + @Override + public Object visit(ASTMethod node, Object data) { + if (opKey != null && reportMethods && opKey.supports(node)) { + int methodValue = (int) ApexMetrics.get(opKey, node, metricVersion); + if (methodValue >= reportLevel) { + addViolation(data, node, new String[] {node.getQualifiedName().toString(), "" + methodValue}); + } + } + return data; + } +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/impl/AllMetricsTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/impl/AllMetricsTest.java new file mode 100644 index 000000000..b11423671 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/impl/AllMetricsTest.java @@ -0,0 +1,35 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +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; + +/** + * Executes the metrics testing rules. + * + * @author Clément Fournier + */ +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"); + addRule(RULESET, "WmcTest"); + } + +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/impl/CycloTestRule.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/impl/CycloTestRule.java new file mode 100644 index 000000000..dc2f1d128 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/impl/CycloTestRule.java @@ -0,0 +1,28 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics.impl; + +import net.sourceforge.pmd.lang.apex.metrics.api.ApexClassMetricKey; +import net.sourceforge.pmd.lang.apex.metrics.api.ApexOperationMetricKey; + +/** + * Tests standard cyclo. + * + * @author Clément Fournier + */ +public class CycloTestRule extends AbstractApexMetricTestRule { + + @Override + protected ApexClassMetricKey getClassKey() { + return null; + } + + + @Override + protected ApexOperationMetricKey getOpKey() { + return ApexOperationMetricKey.CYCLO; + } + +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/impl/WmcTestRule.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/impl/WmcTestRule.java new file mode 100644 index 000000000..a5d71e5be --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/impl/WmcTestRule.java @@ -0,0 +1,31 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics.impl; + +import net.sourceforge.pmd.lang.apex.metrics.api.ApexClassMetricKey; +import net.sourceforge.pmd.lang.apex.metrics.api.ApexOperationMetricKey; + +/** + * @author Clément Fournier + */ +public class WmcTestRule extends AbstractApexMetricTestRule { + + @Override + protected boolean isReportMethods() { + return false; + } + + + @Override + protected ApexClassMetricKey getClassKey() { + return ApexClassMetricKey.WMC; + } + + + @Override + protected ApexOperationMetricKey getOpKey() { + return null; + } +} From a0c6437c0e32c2ed91be5745b60644b1aaf739eb Mon Sep 17 00:00:00 2001 From: oowekyala Date: Tue, 1 Aug 2017 17:02:51 +0200 Subject: [PATCH 12/19] Cyclo rule + more stuff --- .../lang/apex/metrics/impl/CycloMetric.java | 47 ++++ .../pmd/lang/apex/metrics/impl/WmcMetric.java | 22 ++ .../impl/visitors/StandardCycloVisitor.java | 101 +++++++ .../rule/CyclomaticComplexityRule.java | 80 ++++++ .../main/resources/rulesets/apex/metrics.xml | 66 +++++ .../apex/rule/metrics/MetricsRulesTest.java | 31 +++ .../lang/apex/metrics/impl/xml/CycloTest.xml | 145 ++++++++++ .../lang/apex/metrics/impl/xml/WmcTest.xml | 95 +++++++ .../rule/metrics/xml/CyclomaticComplexity.xml | 252 ++++++++++++++++++ .../resources/rulesets/apex/metrics_test.xml | 24 ++ .../pmd/lang/java/metrics/MetricsHook.java | 2 +- 11 files changed, 864 insertions(+), 1 deletion(-) create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/CycloMetric.java create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/WmcMetric.java create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/visitors/StandardCycloVisitor.java create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/rule/CyclomaticComplexityRule.java create mode 100644 pmd-apex/src/main/resources/rulesets/apex/metrics.xml create mode 100644 pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/metrics/MetricsRulesTest.java create mode 100644 pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/metrics/impl/xml/CycloTest.xml create mode 100644 pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/metrics/impl/xml/WmcTest.xml create mode 100644 pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/metrics/xml/CyclomaticComplexity.xml create mode 100644 pmd-apex/src/test/resources/rulesets/apex/metrics_test.xml diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/CycloMetric.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/CycloMetric.java new file mode 100644 index 000000000..680680846 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/CycloMetric.java @@ -0,0 +1,47 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics.impl; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.lang3.mutable.MutableInt; + +import net.sourceforge.pmd.lang.apex.ast.ASTBooleanExpression; +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTStandardCondition; +import net.sourceforge.pmd.lang.apex.metrics.impl.visitors.StandardCycloVisitor; +import net.sourceforge.pmd.lang.metrics.MetricVersion; + +import apex.jorje.semantic.ast.expression.BooleanExpressionType; + +/** + * See the doc for the Java metric. + * + * @author Clément Fournier + */ +public class CycloMetric extends AbstractApexOperationMetric { + + + @Override + public double computeFor(ASTMethod node, MetricVersion version) { + return ((MutableInt) node.jjtAccept(new StandardCycloVisitor(), new MutableInt(1))).doubleValue(); + } + + + public static int booleanExpressionComplexity(ASTStandardCondition expression) { + Set subs = new HashSet<>(expression.findDescendantsOfType(ASTBooleanExpression.class)); + int complexity = 0; + + for (ASTBooleanExpression sub : subs) { + BooleanExpressionType type = sub.getNode().getBooleanExpressionType(); + if (type != null && (type == BooleanExpressionType.OR || type == BooleanExpressionType.AND)) { + complexity++; + } + } + + return complexity; + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/WmcMetric.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/WmcMetric.java new file mode 100644 index 000000000..7f6b8c433 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/WmcMetric.java @@ -0,0 +1,22 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics.impl; + +import net.sourceforge.pmd.lang.apex.ast.ASTUserClassOrInterface; +import net.sourceforge.pmd.lang.apex.metrics.ApexMetrics; +import net.sourceforge.pmd.lang.apex.metrics.api.ApexOperationMetricKey; +import net.sourceforge.pmd.lang.metrics.MetricVersion; +import net.sourceforge.pmd.lang.metrics.ResultOption; + +/** + * @author Clément Fournier + */ +public class WmcMetric extends AbstractApexClassMetric { + + @Override + public double computeFor(ASTUserClassOrInterface node, MetricVersion version) { + return ApexMetrics.get(ApexOperationMetricKey.CYCLO, node, ResultOption.SUM); + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/visitors/StandardCycloVisitor.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/visitors/StandardCycloVisitor.java new file mode 100644 index 000000000..001fda93d --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/visitors/StandardCycloVisitor.java @@ -0,0 +1,101 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics.impl.visitors; + +import org.apache.commons.lang3.mutable.MutableInt; + +import net.sourceforge.pmd.lang.apex.ast.ASTCatchBlockStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTDoLoopStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTForEachStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTForLoopStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTIfBlockStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTStandardCondition; +import net.sourceforge.pmd.lang.apex.ast.ASTTernaryExpression; +import net.sourceforge.pmd.lang.apex.ast.ASTThrowStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTWhileLoopStatement; +import net.sourceforge.pmd.lang.apex.ast.ApexParserVisitorAdapter; +import net.sourceforge.pmd.lang.apex.metrics.impl.CycloMetric; + +/** + * @author Clément Fournier + */ +public class StandardCycloVisitor extends ApexParserVisitorAdapter { + + @Override + public Object visit(ASTMethod node, Object data) { + return super.visit(node, data); + } + + + @Override + public Object visit(ASTIfBlockStatement node, Object data) { + ((MutableInt) data).add( + 1 + CycloMetric.booleanExpressionComplexity(node.getFirstDescendantOfType(ASTStandardCondition.class))); + super.visit(node, data); + return data; + } + + + @Override + public Object visit(ASTCatchBlockStatement node, Object data) { + ((MutableInt) data).increment(); + super.visit(node, data); + return data; + } + + + @Override + public Object visit(ASTForLoopStatement node, Object data) { + ((MutableInt) data).add( + 1 + CycloMetric.booleanExpressionComplexity(node.getFirstDescendantOfType(ASTStandardCondition.class))); + super.visit(node, data); + return data; + } + + + @Override + public Object visit(ASTForEachStatement node, Object data) { + ((MutableInt) data).increment(); + super.visit(node, data); + return data; + } + + @Override + public Object visit(ASTThrowStatement node, Object data) { + ((MutableInt) data).increment(); + super.visit(node, data); + return data; + } + + + @Override + public Object visit(ASTWhileLoopStatement node, Object data) { + ((MutableInt) data).add( + 1 + CycloMetric.booleanExpressionComplexity(node.getFirstDescendantOfType(ASTStandardCondition.class))); + super.visit(node, data); + return data; + } + + + @Override + public Object visit(ASTDoLoopStatement node, Object data) { + ((MutableInt) data).add( + 1 + CycloMetric.booleanExpressionComplexity(node.getFirstDescendantOfType(ASTStandardCondition.class))); + super.visit(node, data); + return data; + } + + + @Override + public Object visit(ASTTernaryExpression node, Object data) { + ((MutableInt) data).add( + 1 + CycloMetric.booleanExpressionComplexity(node.getFirstDescendantOfType(ASTStandardCondition.class))); + super.visit(node, data); + return data; + } + + +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/rule/CyclomaticComplexityRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/rule/CyclomaticComplexityRule.java new file mode 100644 index 000000000..e313daee1 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/rule/CyclomaticComplexityRule.java @@ -0,0 +1,80 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.metrics.rule; + + +import java.util.Stack; + +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +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.ResultOption; +import net.sourceforge.pmd.lang.rule.properties.IntegerProperty; + +/** + * Cyclomatic complexity rule using metrics. + * + * @author Clément Fournier + */ +public class CyclomaticComplexityRule extends AbstractApexRule { + + private static final IntegerProperty CLASS_LEVEL_DESCRIPTOR = new IntegerProperty( + "classReportLevel", "Total class complexity reporting threshold", 1, 200, 40, 1.0f); + + private static final IntegerProperty METHOD_LEVEL_DESCRIPTOR = new IntegerProperty( + "methodReportLevel", "Cyclomatic complexity reporting threshold", 1, 30, 10, 1.0f); + + Stack classNames = new Stack<>(); + + + public CyclomaticComplexityRule() { + definePropertyDescriptor(CLASS_LEVEL_DESCRIPTOR); + definePropertyDescriptor(METHOD_LEVEL_DESCRIPTOR); + } + + + @Override + public Object visit(ASTUserClass node, Object data) { + + classNames.push(node.getImage()); + super.visit(node, data); + classNames.pop(); + + if (ApexClassMetricKey.WMC.supports(node)) { + int classWmc = (int) ApexMetrics.get(ApexClassMetricKey.WMC, node); + + if (classWmc >= getProperty(CLASS_LEVEL_DESCRIPTOR)) { + int classHighest = (int) ApexMetrics.get(ApexOperationMetricKey.CYCLO, node, ResultOption.HIGHEST); + + String[] messageParams = {"class", + node.getImage(), + " total", + classWmc + " (highest " + classHighest + ")", }; + + addViolation(data, node, messageParams); + } + } + return data; + } + + + @Override + public final Object visit(ASTMethod node, Object data) { + + int cyclo = (int) ApexMetrics.get(ApexOperationMetricKey.CYCLO, node); + if (cyclo >= getProperty(METHOD_LEVEL_DESCRIPTOR)) { + addViolation(data, node, new String[] {node.getImage().equals(classNames.peek()) ? "constructor" : "method", + node.getQualifiedName().getOperation(), + "", + "" + cyclo, }); + } + + return data; + } + +} diff --git a/pmd-apex/src/main/resources/rulesets/apex/metrics.xml b/pmd-apex/src/main/resources/rulesets/apex/metrics.xml new file mode 100644 index 000000000..0709228dc --- /dev/null +++ b/pmd-apex/src/main/resources/rulesets/apex/metrics.xml @@ -0,0 +1,66 @@ + + + + + + These are rules which use the Metrics Framework to calculate metrics. + + + + + = 10. +Additionnally, classes with many methods of moderate complexity get reported as well once the total of their +methods' complexities reaches 40, even if none of the methods was directly reported. + +Reported methods should be broken down into several smaller methods. Reported classes should probably be broken down +into subcomponents. + ]]> + + 3 + + + + + + diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/metrics/MetricsRulesTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/metrics/MetricsRulesTest.java new file mode 100644 index 000000000..ec2fae809 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/metrics/MetricsRulesTest.java @@ -0,0 +1,31 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.metrics; + +import net.sourceforge.pmd.Rule; +import net.sourceforge.pmd.lang.apex.metrics.ApexMetricsHook; +import net.sourceforge.pmd.testframework.SimpleAggregatorTst; + +/** + * @author Clément Fournier + */ +public class MetricsRulesTest extends SimpleAggregatorTst { + + + private static final String RULESET = "apex-metrics"; + + + @Override + protected Rule reinitializeRule(Rule rule) { + ApexMetricsHook.reset(); + return super.reinitializeRule(rule); + } + + + @Override + public void setUp() { + addRule(RULESET, "CyclomaticComplexity"); + } +} diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/metrics/impl/xml/CycloTest.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/metrics/impl/xml/CycloTest.xml new file mode 100644 index 000000000..c04f26659 --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/metrics/impl/xml/CycloTest.xml @@ -0,0 +1,145 @@ + + + + + + + + + Complicated method - Standard + 2 + + '__Complicated#exception()' has value 4. + '__Complicated#example()' has value 17. + + + + + + + Empty methods should count 1 + false + 1 + + '__Foo#foo()' has value 1. + + + + + + + + + + + + #984 Cyclomatic complexity should treat constructors like methods + false + 1 + + '__Test#Test()' has value 4. + + + + + + 2 || y < 4) { + while (x++ < 10 && !(y++ < 0)); + } else if (a && b || x < 4) { + return; + } + } + } + ]]> + + + + Standard Cyclo should count boolean paths + 1 + false + + '__Foo#foo()' has value 8. + + + + + + Ternary expression counts 1 + boolean complexity + 1 + + '__Foo#bar()' has value 3. + + + + + + + diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/metrics/impl/xml/WmcTest.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/metrics/impl/xml/WmcTest.xml new file mode 100644 index 000000000..a4d807159 --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/metrics/impl/xml/WmcTest.xml @@ -0,0 +1,95 @@ + + + + + + + + + Complicated class + 1 + + '__Complicated' has value 16. + + + + + + Empty classes count 0 + 1 + + '__Foo' has value 0. + + + + + + + + Abstract classes and enums are supported + 1 + + '__Foo' has value 2. + + + + + + + + Annotations and interfaces are not supported + 0 + + + + + + \ No newline at end of file diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/metrics/xml/CyclomaticComplexity.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/metrics/xml/CyclomaticComplexity.xml new file mode 100644 index 000000000..cb4f12a92 --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/metrics/xml/CyclomaticComplexity.xml @@ -0,0 +1,252 @@ + + + + + + + + + Simple method + 1 + 1 + 2 + + The class 'Foo' has a total cyclomatic complexity of 1 (highest 1). + The method 'foo()' has a cyclomatic complexity of 1. + + + + + + + + testLessComplicatedThanReportLevel + 0 + + + + + + + Complicated method + 1 + + The method 'example()' has a cyclomatic complexity of 14. + + + + + + Constructor + 1 + 1 + + The constructor 'Foo()' has a cyclomatic complexity of 1. + + + + + + + + Test class report level + 14 + 999 + 1 + + + + + Test method report level + 999 + 14 + 1 + + + + + + + + + #984 Cyclomatic complexity should treat constructors like methods: 1 - reportMethods=true + 1 + 1 + + + + + #984 Cyclomatic complexity should treat constructors like methods: 2 -reportMethods=false + 999 + 0 + + + + + 2 || y < 4) { + while (x++ < 10 && !(y-- < 0)); + } else if (a && b || x < 4) { + return; + } + } + } + ]]> + + + + Standard Cyclo should count boolean paths + 2 + 1 + + The method 'foo()' has a cyclomatic complexity of 8. + + + + + + + + + + + Test many unreported methods + 1 + + The class 'Complicated' has a total cyclomatic complexity of 40 (highest 8). + + + + + \ No newline at end of file diff --git a/pmd-apex/src/test/resources/rulesets/apex/metrics_test.xml b/pmd-apex/src/test/resources/rulesets/apex/metrics_test.xml new file mode 100644 index 000000000..9864f7e3f --- /dev/null +++ b/pmd-apex/src/test/resources/rulesets/apex/metrics_test.xml @@ -0,0 +1,24 @@ + + + + + + Metrics testing ruleset. Each metric is tested through a dummy rule. + + + + + + + + + diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/MetricsHook.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/MetricsHook.java index 3f6eaa939..b24e0b81f 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/MetricsHook.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/MetricsHook.java @@ -5,7 +5,7 @@ package net.sourceforge.pmd.lang.java.metrics; /** - * Provides a hook into package-private methods of {@code oom}. + * Provides a hook into package-private methods of {@code java.metrics}. * * @author Clément Fournier */ From 40af40bcd2aa2873297ce74cb405e59ab919b9a3 Mon Sep 17 00:00:00 2001 From: oowekyala Date: Tue, 1 Aug 2017 19:07:44 +0200 Subject: [PATCH 13/19] Made signature matching optional + doc --- docs/_data/sidebars/pmd_sidebar.yml | 4 + .../adding_metrics_framework_for_language.md | 48 ------- .../adding_metrics_support_to_language.md | 124 ++++++++++++++++++ .../lang/metrics/AbstractMetricsComputer.java | 3 +- .../lang/metrics/AbstractMetricsFacade.java | 3 +- .../pmd/lang/metrics/MetricsComputer.java | 2 +- .../pmd/lang/metrics/ProjectMirror.java | 3 +- .../sourceforge/pmd/lang/metrics/SigMask.java | 25 ++++ .../java/metrics/signature/FieldSigMask.java | 2 +- .../metrics/signature/JavaFieldSignature.java | 2 +- .../signature/JavaOperationSignature.java | 4 +- .../{SigMask.java => JavaSigMask.java} | 11 +- .../java/metrics/signature/JavaSignature.java | 4 +- .../metrics/signature/OperationSigMask.java | 2 +- ...{SigMaskTest.java => JavaSigMaskTest.java} | 8 +- 15 files changed, 171 insertions(+), 74 deletions(-) delete mode 100644 docs/pages/pmd/devdocs/adding_metrics_framework_for_language.md create mode 100644 docs/pages/pmd/devdocs/adding_metrics_support_to_language.md create mode 100644 pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/SigMask.java rename pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/{SigMask.java => JavaSigMask.java} (84%) rename pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/{SigMaskTest.java => JavaSigMaskTest.java} (97%) diff --git a/docs/_data/sidebars/pmd_sidebar.yml b/docs/_data/sidebars/pmd_sidebar.yml index 9587ef124..7ea20c072 100644 --- a/docs/_data/sidebars/pmd_sidebar.yml +++ b/docs/_data/sidebars/pmd_sidebar.yml @@ -156,3 +156,7 @@ entries: - title: Adding a New CPD Language url: /pmd_devdocs_adding_new_cpd_language.html output: web, pdf + - title: Adding metrics support to a language + url: /pmd_devdocs_adding_metrics_support_to_language.html + output: web, pdf + diff --git a/docs/pages/pmd/devdocs/adding_metrics_framework_for_language.md b/docs/pages/pmd/devdocs/adding_metrics_framework_for_language.md deleted file mode 100644 index 9811676ba..000000000 --- a/docs/pages/pmd/devdocs/adding_metrics_framework_for_language.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: How to implement a metrics framework for an existing language -short_title: Implement a metrics framework -tags: [customizing] -summary: "PMD's Java module has an extensive framework for the calculation of metrics, which allows rule developers -to implement and use new code metrics very simply. Most of the functionality of this framework is abstracted in such -a way that any PMD supported language can implement such a framework without too much trouble. Here's how." -last_updated: July 3, 2016 -sidebar: pmd_sidebar -permalink: pmd_devdocs_adding_new_cpd_language.html -folder: pmd/devdocs ---- - -## Basic steps -* Implement the interface `QualifiedName` in a class. This implementation must be tailored to the target language so -that it can indentify unambiguously any class and operation in the analysed project (see JavaQualifiedName). -* Determine the AST nodes that correspond to class and method declaration in your language. These types are -referred hereafter as `T` and `O`, respectively. Both these types must implement the interface `QualifiableNode`, which -means they must provide a `getQualifiedName` method to give access to their qualified name. -* Implement the interface `Signature`, parameterized with the type of the method AST nodes. Method signatures -describe basic information about a method, which typically includes most of the modifiers they declare (eg -visibility, abstract or virtual, etc.). It's up to you to define the right level of detail, depending on the accuracy - of the pattern matching required. -* Make type `O` implement `SignedNode`. This makes the node capable of giving its signature. -* Create a class implementing `Memoizer` and one `Memoizer`. An abstract base class is available. Instances of -these classes each represent a class or operation, respectively. They are used to store the results of metrics that -are already computed. -* Create a class implementing `ProjectMirror`. This class will store the memoizers for all the classes and -interfaces of the analysed project. This class must be able to fetch and return a memoizer given the qualified name -of the resource it represents. As it stores the memoizers, it's a good idea to implement some signature matching -utilities in this class. What's signature matching? (See write custom metrics -- TODO) -* Create a class extending `AbstractMetricsComputer`. This object will be responsible for calculating metrics -given a memoizer, a node and info about the metric. Typically, this object is stateless so you might as well make it -a singleton. -* Create a class extending `AbstractMetricsFacade`. This class needs a reference to your `ProjectMirror` and -your `MetricsComputer`. It backs the real end user façade, and handles user provided parameters before delegating to -your `MetricsComputer`. -* Create the static façade of your framework. This one has an instance of your `MetricsFaçade` object and delegates -static methods to that instance. -* If you want to implement signature matching, create an `AbstractMetric` class, which gives access to a -`SignatureMatcher` to your metrics. Typically, your implementation of `ProjectMirror` implements a -custom `SignatureMatcher` interface, and your façade can give back its instance of the project mirror. -* Create classes `AbstractOperationMetric` and `AbstractClassMetric`. These must implement `Metric` and -`Metric`, respectively. They typically provide defaults for the `supports` method of each metric. -* Create enums `ClassMetricKey` and `OperationMetricKey`. These must implement `MetricKey` and `MetricKey`. The - enums list all available metric keys for your language. -* Create metrics by extending your base classes, reference them in your enums, and you can start using them with your - façade! \ No newline at end of file diff --git a/docs/pages/pmd/devdocs/adding_metrics_support_to_language.md b/docs/pages/pmd/devdocs/adding_metrics_support_to_language.md new file mode 100644 index 000000000..2a299be86 --- /dev/null +++ b/docs/pages/pmd/devdocs/adding_metrics_support_to_language.md @@ -0,0 +1,124 @@ +--- +title: Adding support for metrics to a language +short_title: Implement a metrics framework +tags: [customizing] +summary: "PMD's Java module has an extensive framework for the calculation of metrics, which allows rule developers +to implement and use new code metrics very simply. Most of the functionality of this framework is abstracted in such +a way that any PMD supported language can implement such a framework without too much trouble. Here's how." +last_updated: August 2017 +sidebar: pmd_sidebar +permalink: pmd_devdocs_adding_metrics_support_to_language.html +folder: pmd/devdocs +--- + +{% include warning.html content="WIP" %} + +## Internal architecture of the metrics framework + +### Overview of the Java framework + +The framework has several subsystems, the two most easily identifiable being: +* The project mirror (`PackageStats`). This data structure gathers information about the classes, methods and fields of + the analysed project. It allows metrics to know about classes outside the current one, the files being processed one + by one. It's filled by a visitor before rules apply. + + The contents of the structure are indexed with fully qualified names (`JavaQualifiedName`), which must identify + unambiguously classes and methods. The information stored in this data structure that's accessible to metrics is + mainly comprised of method and field signatures (e.g. `JavaOperationSignature`), which describes concisely the + characteristics of the method or field (roughly, its modifiers). + + The project mirror is also responsible for the memoisation of metrics. When a metric is computed, it's stored back + in this structure and can be reused later. This reduces the overhead on the calculation of e.g. aggregate results + (`ResultOption` calculations). + +* The façade. The static end-user façade (`JavaMetrics`) is backed by an instance of a `JavaMetricsFaçade`. This + allows us to abstract the functionality of the façade into `pmd-core` for other frameworks to use. The façade + instance contains a project mirror, representing the analysed project, and a metrics computer + (`JavaMetricsComputer`). It's this last object which really computes the metric and stores back its result in the + project mirror, while the façade only handles parameters. + +Metrics (`Metric`) plug in to this static system and only provide behaviour that's executed by the metrics computer. +Internally, metric keys (`MetricKey`) are parameterized to their version (`MetricVersion`) to index memoisation maps +(see `ParameterizedMetricKey`). This allows us to memoise several versions of the same metric without conflict. + +### Abstraction layer + +As you may have seen, most of the functionality of the façade components has been abstracted into `pmd-core`. This +allows us to implement new metrics frameworks quite quickly. These abstract components are parameterized by the +node types of the class and operation AST nodes. + +The rest of the framework is framed by generic interfaces, but it can't really be abstracted more than that. For +instance, the project mirror is very language specific. Java's implementation uses the natural structure provided by +the language's package system to structure the project's content. Apex, on the other, has no package system and thus +can't use the same mechanism. That explains why the interfaces framing the project mirror are very loose. Their main +goal is to provide type safety through generics. + +Signature matching is another feature that couldn't be abstracted. For now, usage resolution depends on the availability + of type resolution for the given language, which is only implemented in java. We can however match signatures on the + class' own methods or nested classes, which offers limited interest, but may be useful. + +Despite these limitations, once the project mirror is implemented, it's very straightforward to get a working +framework. Additionnally, the external behaviour of the framework is very stable across languages, yet each component + can easily be customized by adding methods or overriding existing ones. + +## Implementation of a new framework + +### 1. Groundwork + +* Create a class implementing `QualifiedName`. This implementation must be tailored to the target language so +that it can indentify unambiguously any class and operation in the analysed project (see JavaQualifiedName). +* Determine the AST nodes that correspond to class and method declaration in your language. These types are +referred hereafter as `T` and `O`, respectively. Both these types must implement the interface `QualifiableNode`, which +means they must provide a `getQualifiedName` method to give access to their qualified name. + +### 2. Implement the project mirror +* Create a class implementing `Memoizer` and one `Memoizer`. An abstract base class is available. Instances of +these classes each represent a class or operation, respectively. They are used to store the results of metrics that +are already computed. +* Create a class implementing `ProjectMirror`. This class will store the memoizers for all the classes and +interfaces of the analysed project. This class must be able to fetch and return a memoizer given the qualified name +of the resource it represents. As it stores the memoizers, it's a good idea to implement some signature matching +utilities in this class. What's signature matching? (See write custom metrics -- TODO) + +### 3. Implement the façade +* Create a class extending `AbstractMetricsComputer`. This object will be responsible for calculating metrics +given a memoizer, a node and info about the metric. Typically, this object is stateless so you might as well make it +a singleton. +* Create a class extending `AbstractMetricsFacade`. This class needs a reference to your `ProjectMirror` and +your `MetricsComputer`. It backs the real end user façade, and handles user provided parameters before delegating to +your `MetricsComputer`. +* Create the static façade of your framework. This one has an instance of your `MetricsFaçade` object and delegates +static methods to that instance. +* Create classes `AbstractOperationMetric` and `AbstractClassMetric`. These must implement `Metric` and +`Metric`, respectively. They typically provide defaults for the `supports` method of each metric. +* Create enums `ClassMetricKey` and `OperationMetricKey`. These must implement `MetricKey` and `MetricKey`. The + enums list all available metric keys for your language. +* Create metrics by extending your base classes, reference them in your enums, and you can start using them with your + façade! + +### Optional: Signature matching + +You can match the signature of anything: method, field, class, package... It depends on what's useful for you. +Suppose you want to be able to match signatures for nodes of type `N`. What you have to do then is the following: + +* Create a class implementing the interface `Signature`. Signatures describe basic information about the node, +which typically includes most of the modifiers they declare (eg visibility, abstract or virtual, etc.). +It's up to you to define the right level of detail, depending on the accuracy of the pattern matching required. +* Make type `N` implement `SignedNode`. This makes the node capable of giving its signature. Factory methods to +build a `Signature` from a `N` are a good idea. +* Create signature masks. A mask is an object that matches some signatures based on their features. For example, with + the Java framework, you can build a `JavaOperationSigMask` that matches all method signatures with visibility + `public`. A sigmask implements `SigMask`, where `S` is the type of signature your mask handles. +* Typically, the project mirror stores the signatures, so you have to implement it in a way that makes it possible to + associate a signature with the qualified name of its node. + +{% include important.html +content="Writing this, it seems dumb. If signature matching is optional, it should not require reimplementing + the project mirror. We need to work on dissociating the two. The project mirror would be reduce to a + collection of memoizers, which could be abstracted into pmd-core." %} + + +* If you want to implement signature matching, create an `AbstractMetric` class, which gives access to a +`SignatureMatcher` to your metrics. Typically, your implementation of `ProjectMirror` implements a +custom `SignatureMatcher` interface, and your façade can give back its instance of the project mirror. + diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/AbstractMetricsComputer.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/AbstractMetricsComputer.java index accf8c253..336aa2138 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/AbstractMetricsComputer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/AbstractMetricsComputer.java @@ -8,7 +8,6 @@ import java.util.List; import net.sourceforge.pmd.lang.ast.QualifiableNode; -import net.sourceforge.pmd.lang.ast.SignedNode; /** * Base class for metrics computers. These objects compute a metric and memoize it. @@ -18,7 +17,7 @@ * * @author Clément Fournier */ -public abstract class AbstractMetricsComputer & QualifiableNode> +public abstract class AbstractMetricsComputer implements MetricsComputer { @Override diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/AbstractMetricsFacade.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/AbstractMetricsFacade.java index f6f7984ef..ca7b91c27 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/AbstractMetricsFacade.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/AbstractMetricsFacade.java @@ -5,7 +5,6 @@ package net.sourceforge.pmd.lang.metrics; import net.sourceforge.pmd.lang.ast.QualifiableNode; -import net.sourceforge.pmd.lang.ast.SignedNode; import net.sourceforge.pmd.lang.metrics.Metric.Version; /** @@ -17,7 +16,7 @@ * * @author Clément Fournier */ -public abstract class AbstractMetricsFacade & QualifiableNode> { +public abstract class AbstractMetricsFacade { /** diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricsComputer.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricsComputer.java index e05edb996..44eea2698 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricsComputer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricsComputer.java @@ -17,7 +17,7 @@ * * @author Clément Fournier */ -public interface MetricsComputer & QualifiableNode> { +public interface MetricsComputer { /** diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/ProjectMirror.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/ProjectMirror.java index 7ed7e920f..877d2d426 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/ProjectMirror.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/ProjectMirror.java @@ -6,7 +6,6 @@ import net.sourceforge.pmd.lang.ast.QualifiableNode; import net.sourceforge.pmd.lang.ast.QualifiedName; -import net.sourceforge.pmd.lang.ast.SignedNode; /** * Object storing the statistics and memoizers of the analysed project, like PackageStats for Java. These are the entry @@ -28,7 +27,7 @@ * * @author Clément Fournier */ -public interface ProjectMirror & QualifiableNode> { +public interface ProjectMirror { /** * Gets the operation metric memoizer corresponding to the qualified name. diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/SigMask.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/SigMask.java new file mode 100644 index 000000000..b4ef01db1 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/SigMask.java @@ -0,0 +1,25 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.metrics; + +/** + * Generic signature mask. + * + * @param Type of signature this mask handles + * + * @author Clément Fournier + */ +public interface SigMask> { + + /** + * Returns true if the parameter is covered by this mask. + * + * @param sig The signature to test. + * + * @return True if the parameter is covered by this mask + */ + boolean covers(T sig); + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/FieldSigMask.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/FieldSigMask.java index 3c7b86e5c..cb4961c05 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/FieldSigMask.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/FieldSigMask.java @@ -9,7 +9,7 @@ * * @author Clément Fournier */ -public final class FieldSigMask extends SigMask { +public final class FieldSigMask extends JavaSigMask { private boolean coverFinal = true; private boolean coverStatic = true; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaFieldSignature.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaFieldSignature.java index e2c9f01e1..27f0367f5 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaFieldSignature.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaFieldSignature.java @@ -15,7 +15,7 @@ * * @author Clément Fournier */ -public final class JavaFieldSignature extends JavaSignature implements Signature { +public final class JavaFieldSignature extends JavaSignature { private static final Map POOL = new HashMap<>(); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaOperationSignature.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaOperationSignature.java index bd17703cc..144945ab9 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaOperationSignature.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaOperationSignature.java @@ -18,7 +18,6 @@ import net.sourceforge.pmd.lang.java.ast.ASTType; import net.sourceforge.pmd.lang.java.symboltable.ClassScope; import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; -import net.sourceforge.pmd.lang.metrics.Signature; import net.sourceforge.pmd.lang.symboltable.NameOccurrence; /** @@ -26,8 +25,7 @@ * * @author Clément Fournier */ -public final class JavaOperationSignature extends JavaSignature - implements Signature { +public final class JavaOperationSignature extends JavaSignature { private static final Map POOL = new HashMap<>(); public final Role role; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/SigMask.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaSigMask.java similarity index 84% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/SigMask.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaSigMask.java index b427db2fe..cc50cf8f5 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/SigMask.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaSigMask.java @@ -9,6 +9,7 @@ import java.util.Set; import net.sourceforge.pmd.lang.java.metrics.signature.JavaSignature.Visibility; +import net.sourceforge.pmd.lang.metrics.SigMask; /** * Generic signature mask. @@ -17,7 +18,7 @@ * * @author Clément Fournier */ -public abstract class SigMask { +public abstract class JavaSigMask> implements SigMask { /** Visibility mask. */ private Set visMask = EnumSet.allOf(Visibility.class); @@ -52,13 +53,7 @@ public void forbid(JavaSignature.Visibility... visibilities) { } - /** - * Returns true if the parameter is covered by this mask. - * - * @param sig The signature to test. - * - * @return True if the parameter is covered by this mask - */ + @Override public boolean covers(T sig) { return visMask.contains(sig.visibility); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaSignature.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaSignature.java index 722d64330..093a61146 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaSignature.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaSignature.java @@ -4,14 +4,16 @@ package net.sourceforge.pmd.lang.java.metrics.signature; +import net.sourceforge.pmd.lang.ast.SignedNode; import net.sourceforge.pmd.lang.java.ast.AccessNode; +import net.sourceforge.pmd.lang.metrics.Signature; /** * Generic signature. This class is extended by classes specific to operations and fields. * * @author Clément Fournier */ -public abstract class JavaSignature { +public abstract class JavaSignature> implements Signature { /** Visibility. */ public final Visibility visibility; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/OperationSigMask.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/OperationSigMask.java index f74ada37f..e74db2ae1 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/OperationSigMask.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/OperationSigMask.java @@ -15,7 +15,7 @@ * * @author Clément Fournier */ -public final class OperationSigMask extends SigMask { +public final class OperationSigMask extends JavaSigMask { private Set roleMask = EnumSet.allOf(Role.class); private boolean coverAbstract = false; diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/SigMaskTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/JavaSigMaskTest.java similarity index 97% rename from pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/SigMaskTest.java rename to pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/JavaSigMaskTest.java index b2d62913b..4e242472f 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/SigMaskTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/JavaSigMaskTest.java @@ -22,12 +22,12 @@ 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.signature.OperationSigMask; -import net.sourceforge.pmd.lang.java.metrics.signature.SigMask; +import net.sourceforge.pmd.lang.java.metrics.signature.JavaSigMask; /** * @author Clément Fournier */ -public class SigMaskTest extends ParserTst { +public class JavaSigMaskTest extends ParserTst { private static final String TEST_FIELDS = "class Bzaz{" + "public String x;" @@ -80,7 +80,7 @@ public class SigMaskTest extends ParserTst { @Test public void testEmptyOperationMask() { List nodes = getOrderedNodes(ASTMethodOrConstructorDeclaration.class, TEST_OPERATIONS); - SigMask mask = new OperationSigMask(); + JavaSigMask mask = new OperationSigMask(); for (ASTMethodOrConstructorDeclaration node : nodes) { if (node.isAbstract()) { @@ -97,7 +97,7 @@ public void testEmptyOperationMask() { @Test public void testEmptyFieldMask() { List nodes = getOrderedNodes(ASTFieldDeclaration.class, TEST_FIELDS); - SigMask mask = new FieldSigMask(); + JavaSigMask mask = new FieldSigMask(); for (ASTFieldDeclaration node : nodes) { assertTrue(mask.covers(JavaFieldSignature.buildFor(node))); From 05b502e24775d6d393a02d85fb6d7628e2e70cc1 Mon Sep 17 00:00:00 2001 From: oowekyala Date: Fri, 4 Aug 2017 22:26:58 +0200 Subject: [PATCH 14/19] Fix Apex module --- .../ast/ApexParserVisitorReducedAdapter.java | 29 ++++++++++ .../pmd/lang/apex/ast/ApexQualifiedName.java | 21 +++++--- .../lang/apex/metrics/AbstractApexMetric.java | 2 +- .../pmd/lang/apex/metrics/ApexClassStats.java | 34 ++++++++++-- .../pmd/lang/apex/metrics/ApexMetrics.java | 8 +-- .../lang/apex/metrics/ApexMetricsFacade.java | 16 +++++- .../lang/apex/metrics/ApexMetricsVisitor.java | 35 +++++++++--- .../metrics/ApexMetricsVisitorFacade.java | 6 ++- ...ionStats.java => ApexProjectMemoizer.java} | 7 ++- .../lang/apex/metrics/ApexProjectMirror.java | 54 ++++--------------- .../signature/ApexOperationSigMask.java | 1 - .../apex/metrics/ApexMetricsVisitorTest.java | 9 ++-- .../apex/metrics/ApexProjectMirrorTest.java | 6 +-- 13 files changed, 147 insertions(+), 81 deletions(-) create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParserVisitorReducedAdapter.java rename pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/{ApexOperationStats.java => ApexProjectMemoizer.java} (50%) diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParserVisitorReducedAdapter.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParserVisitorReducedAdapter.java new file mode 100644 index 000000000..f989a95bd --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParserVisitorReducedAdapter.java @@ -0,0 +1,29 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.ast; + +/** + * @author Clément Fournier + */ +public class ApexParserVisitorReducedAdapter extends ApexParserVisitorAdapter { + + + @Override + public final Object visit(ASTUserInterface node, Object data) { + return visit((ASTUserClassOrInterface) node, data); + } + + + @Override + public final Object visit(ASTUserClass node, Object data) { + return visit((ASTUserClassOrInterface) node, data); + } + + + public Object visit(ASTUserClassOrInterface node, Object data) { + return visit((ApexNode) node, data); + } + +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java index e682927aa..56975c430 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java @@ -20,7 +20,7 @@ */ public class ApexQualifiedName implements QualifiedName { - private static final Pattern FORMAT = Pattern.compile("(\\w+)__(\\w+)(.(\\w+))?(#(\\w+))?"); + private static final Pattern FORMAT = Pattern.compile("(\\w+)__(\\w+)(.(\\w+))?(#(\\w+))?"); // TODO private final String nameSpace; private final String[] classes; @@ -34,13 +34,13 @@ private ApexQualifiedName(String nameSpace, String[] classes, String operation) } - @Override + public String getOperation() { return operation; } - @Override + public String[] getClasses() { return Arrays.copyOf(classes, classes.length); } @@ -86,8 +86,13 @@ public String toString() { } - public static ApexQualifiedName ofString(String toParse) { - return null; + @Override + public ApexQualifiedName getClassName() { + if (isClass()) { + return this; + } + + return new ApexQualifiedName(this.nameSpace, this.classes, null); } @@ -110,6 +115,11 @@ public boolean equals(Object obj) { } + public static ApexQualifiedName ofString(String toParse) { + return null; + } + + static ApexQualifiedName ofOuterClass(ASTUserClassOrInterface astUserClass) { String ns = astUserClass.getNode().getDefiningType().getNamespace().toString(); String[] classes = {astUserClass.getImage()}; @@ -150,7 +160,6 @@ private static String getOperationString(ASTMethod node) { static ApexQualifiedName ofMethod(ASTMethod node) { ApexQualifiedName parent = node.getFirstParentOfType(ASTUserClassOrInterface.class).getQualifiedName(); - return new ApexQualifiedName(parent.nameSpace, parent.classes, getOperationString(node)); } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/AbstractApexMetric.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/AbstractApexMetric.java index dc852897d..e907c0eb8 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/AbstractApexMetric.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/AbstractApexMetric.java @@ -10,7 +10,7 @@ public class AbstractApexMetric { protected ApexSignatureMatcher getSignatureMatcher() { - return ApexMetrics.getApexProjectMirror(); + return ApexMetrics.getFacade().getProjectMirror(); } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexClassStats.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexClassStats.java index a65dc76d3..211970946 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexClassStats.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexClassStats.java @@ -4,11 +4,39 @@ package net.sourceforge.pmd.lang.apex.metrics; -import net.sourceforge.pmd.lang.apex.ast.ASTUserClassOrInterface; -import net.sourceforge.pmd.lang.metrics.AbstractMetricMemoizer; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import net.sourceforge.pmd.lang.apex.metrics.signature.ApexOperationSigMask; +import net.sourceforge.pmd.lang.apex.metrics.signature.ApexOperationSignature; /** * @author Clément Fournier */ -public class ApexClassStats extends AbstractMetricMemoizer> { +class ApexClassStats { + + private Map> operations = new HashMap<>(); + + + void addOperation(String name, ApexOperationSignature sig) { + if (!operations.containsKey(sig)) { + operations.put(sig, new HashSet<>()); + } + operations.get(sig).add(name); + } + + + public boolean hasMatchingSig(String operation, ApexOperationSigMask mask) { + for (Entry> entry : operations.entrySet()) { + if (mask.covers(entry.getKey())) { + if (entry.getValue().contains(operation)) { + return true; + } + } + } + return false; + } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java index e1c5013d5..3d0799a8c 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java @@ -26,12 +26,12 @@ private ApexMetrics() { // Cannot be instantiated /** - * Returns the project mirror of the analysed project. + * Returns the underlying facade. * - * @return The project mirror + * @return The facade */ - static ApexProjectMirror getApexProjectMirror() { - return FACADE.getLanguageSpecificProjectMemoizer(); + public static ApexMetricsFacade getFacade() { + return FACADE; } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsFacade.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsFacade.java index 1ef97f9bb..13492f74e 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsFacade.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsFacade.java @@ -15,11 +15,23 @@ public class ApexMetricsFacade extends AbstractMetricsFacade, ASTMethod> { private final ApexProjectMirror projectMirror = new ApexProjectMirror(); + private final ApexProjectMemoizer memoizer = new ApexProjectMemoizer(); /** Resets the entire project mirror. Used for tests. */ void reset() { projectMirror.reset(); + memoizer.reset(); + } + + + /** + * Gets the project mirror. + * + * @return The project mirror + */ + public ApexProjectMirror getProjectMirror() { + return projectMirror; } @@ -30,7 +42,7 @@ protected MetricsComputer, ASTMethod> getLanguageSpec @Override - protected ApexProjectMirror getLanguageSpecificProjectMemoizer() { - return projectMirror; + protected ApexProjectMemoizer getLanguageSpecificProjectMemoizer() { + return memoizer; } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitor.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitor.java index d1037c4a4..cfa0b8e30 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitor.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitor.java @@ -4,25 +4,46 @@ package net.sourceforge.pmd.lang.apex.metrics; +import java.util.Stack; + import net.sourceforge.pmd.lang.apex.ast.ASTMethod; -import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; -import net.sourceforge.pmd.lang.apex.ast.ApexParserVisitorAdapter; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClassOrInterface; +import net.sourceforge.pmd.lang.apex.ast.ApexParserVisitorReducedAdapter; /** * @author Clément Fournier */ -public class ApexMetricsVisitor extends ApexParserVisitorAdapter { +public class ApexMetricsVisitor extends ApexParserVisitorReducedAdapter { + + private final ApexProjectMemoizer memoizer; + private final ApexProjectMirror mirror; + + private final Stack stack = new Stack<>(); + + + public ApexMetricsVisitor(ApexProjectMemoizer memoizer, ApexProjectMirror mirror) { + this.memoizer = memoizer; + this.mirror = mirror; + } @Override - public Object visit(ASTUserClass node, Object data) { - ((ApexProjectMirror) data).addClass(node.getQualifiedName()); - return super.visit(node, data); + public Object visit(ASTUserClassOrInterface node, Object data) { + memoizer.addClassMemoizer(node.getQualifiedName()); + stack.push(mirror.getClassStats(node.getQualifiedName(), true)); + super.visit(node, data); + stack.pop(); + + return data; } + + @Override public Object visit(ASTMethod node, Object data) { - ((ApexProjectMirror) data).addOperation(node.getQualifiedName(), node.getSignature()); + memoizer.addOperationMemoizer(node.getQualifiedName()); + + stack.peek().addOperation(node.getQualifiedName().getOperation(), node.getSignature()); return data; } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorFacade.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorFacade.java index d01a9968f..78dbcd687 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorFacade.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorFacade.java @@ -14,8 +14,10 @@ public class ApexMetricsVisitorFacade extends ApexParserVisitorAdapter { public void initializeWith(ApexNode rootNode) { - ApexMetricsVisitor visitor = new ApexMetricsVisitor(); - rootNode.jjtAccept(visitor, ApexMetrics.getApexProjectMirror()); + ApexMetricsFacade facade = ApexMetrics.getFacade(); + ApexMetricsVisitor visitor = new ApexMetricsVisitor(facade.getLanguageSpecificProjectMemoizer(), + facade.getProjectMirror()); + rootNode.jjtAccept(visitor, null); } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexOperationStats.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMemoizer.java similarity index 50% rename from pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexOperationStats.java rename to pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMemoizer.java index 7c12089b2..4596887a8 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexOperationStats.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMemoizer.java @@ -5,12 +5,11 @@ package net.sourceforge.pmd.lang.apex.metrics; import net.sourceforge.pmd.lang.apex.ast.ASTMethod; -import net.sourceforge.pmd.lang.metrics.AbstractMetricMemoizer; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClassOrInterface; +import net.sourceforge.pmd.lang.metrics.BasicProjectMemoizer; /** - * Apex operation stats. - * * @author Clément Fournier */ -public class ApexOperationStats extends AbstractMetricMemoizer { +class ApexProjectMemoizer extends BasicProjectMemoizer, ASTMethod> { } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java index 41fed3539..de7b50115 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java @@ -7,76 +7,40 @@ import java.util.HashMap; import java.util.Map; -import net.sourceforge.pmd.lang.apex.ast.ASTMethod; -import net.sourceforge.pmd.lang.apex.ast.ASTUserClassOrInterface; import net.sourceforge.pmd.lang.apex.ast.ApexQualifiedName; import net.sourceforge.pmd.lang.apex.metrics.signature.ApexOperationSigMask; -import net.sourceforge.pmd.lang.apex.metrics.signature.ApexOperationSignature; -import net.sourceforge.pmd.lang.ast.QualifiedName; -import net.sourceforge.pmd.lang.metrics.MetricMemoizer; -import net.sourceforge.pmd.lang.metrics.ProjectMemoizer; /** * @author Clément Fournier */ -public class ApexProjectMirror implements ProjectMemoizer, ASTMethod>, ApexSignatureMatcher { +public class ApexProjectMirror implements ApexSignatureMatcher { - private final Map> operations = new HashMap<>(); private final Map classes = new HashMap<>(); void reset() { - operations.clear(); classes.clear(); } - ApexOperationStats addOperation(ApexQualifiedName qname, ApexOperationSignature sig) { - if (!operations.containsKey(sig)) { - operations.put(sig, new HashMap<>()); + ApexClassStats getClassStats(ApexQualifiedName qname, boolean createIfNotFound) { + ApexQualifiedName className = qname.getClassName(); + if (createIfNotFound && !classes.containsKey(className)) { + classes.put(className, new ApexClassStats()); } - - ApexOperationStats stats = new ApexOperationStats(); - operations.get(sig).put(qname, stats); - return stats; - } - - - ApexClassStats addClass(ApexQualifiedName qname) { - ApexClassStats stats = new ApexClassStats(); - classes.put(qname, stats); - return stats; + return classes.get(className); } @Override public boolean hasMatchingSig(ApexQualifiedName qname, ApexOperationSigMask mask) { - for (ApexOperationSignature sig : operations.keySet()) { - if (mask.covers(sig)) { - if (operations.get(sig).containsKey(qname)) { - return true; - } - } - } - return false; - } + ApexClassStats classStats = getClassStats(qname, false); + return classStats != null && classStats.hasMatchingSig(qname.getOperation(), mask); - @Override - public MetricMemoizer getOperationStats(QualifiedName qname) { - for (Map map : operations.values()) { - ApexOperationStats stats = map.get(qname); - if (stats != null) { - return stats; - } - } - return null; } - @Override - public MetricMemoizer> getClassStats(QualifiedName qname) { - return classes.get(qname); - } + } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSigMask.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSigMask.java index 71389ae16..382309374 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSigMask.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSigMask.java @@ -19,7 +19,6 @@ public class ApexOperationSigMask { public ApexOperationSigMask() { - coverAllVisibilities(); } diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorTest.java index a1b06a874..593f7d0b6 100644 --- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorTest.java +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorTest.java @@ -28,7 +28,7 @@ public class ApexMetricsVisitorTest extends ApexParserTest { @Test public void testProjectMirrorNotNull() { - assertNotNull(ApexMetrics.getApexProjectMirror()); + assertNotNull(ApexMetrics.getFacade().getProjectMirror()); } @@ -76,7 +76,7 @@ public void testOperationsAreThere() { + "\t}" + "}"); - final ApexSignatureMatcher toplevel = ApexMetrics.getApexProjectMirror(); + final ApexSignatureMatcher toplevel = ApexMetrics.getFacade().getProjectMirror(); final ApexOperationSigMask opMask = new ApexOperationSigMask(); @@ -84,7 +84,10 @@ public void testOperationsAreThere() { acu.jjtAccept(new ApexParserVisitorAdapter() { @Override public Object visit(ASTMethod node, Object data) { - assertTrue(toplevel.hasMatchingSig(node.getQualifiedName(), opMask)); + if (!node.getImage().matches("(||clone)")) { + assertTrue(toplevel.hasMatchingSig(node.getQualifiedName(), opMask)); + } + return data; } }, null); diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirrorTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirrorTest.java index 5202e851f..2ba687562 100644 --- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirrorTest.java +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirrorTest.java @@ -107,14 +107,14 @@ public void forceMemoizationTest() { private List visitWith(ApexNode acu, final boolean force) { - final ApexProjectMirror toplevel = ApexMetrics.getApexProjectMirror(); + final ApexProjectMemoizer toplevel = ApexMetrics.getFacade().getLanguageSpecificProjectMemoizer(); final List result = new ArrayList<>(); acu.jjtAccept(new ApexParserVisitorAdapter() { @Override public Object visit(ASTMethod node, Object data) { - MetricMemoizer op = toplevel.getOperationStats(node.getQualifiedName()); + MetricMemoizer op = toplevel.getOperationMemoizer(node.getQualifiedName()); result.add((int) ApexMetricsComputer.INSTANCE.computeForOperation(opMetricKey, node, force, Version.STANDARD, op)); return super.visit(node, data); } @@ -122,7 +122,7 @@ public Object visit(ASTMethod node, Object data) { @Override public Object visit(ASTUserClass node, Object data) { - MetricMemoizer> clazz = toplevel.getClassStats(node.getQualifiedName()); + MetricMemoizer> clazz = toplevel.getClassMemoizer(node.getQualifiedName()); result.add((int) ApexMetricsComputer.INSTANCE.computeForType(classMetricKey, node, force, Version.STANDARD, clazz)); return super.visit(node, data); } From b937d8451534795a6314e729774f4c6bd9c84bf4 Mon Sep 17 00:00:00 2001 From: oowekyala Date: Sat, 5 Aug 2017 14:47:11 +0200 Subject: [PATCH 15/19] Documentation --- .../adding_metrics_support_to_language.md | 107 +++++++++--------- .../pmd/lang/apex/ast/ApexQualifiedName.java | 4 +- .../lang/apex/metrics/AbstractApexMetric.java | 2 + .../pmd/lang/apex/metrics/ApexClassStats.java | 2 + .../pmd/lang/apex/metrics/ApexMetrics.java | 2 + .../apex/metrics/ApexMetricsComputer.java | 1 + .../lang/apex/metrics/ApexMetricsFacade.java | 1 + .../apex/metrics/ApexProjectMemoizer.java | 2 + .../lang/apex/metrics/ApexProjectMirror.java | 4 +- .../rule/CyclomaticComplexityRule.java | 3 +- .../lang/apex/ast/ApexQualifiedNameTest.java | 4 +- 11 files changed, 72 insertions(+), 60 deletions(-) diff --git a/docs/pages/pmd/devdocs/adding_metrics_support_to_language.md b/docs/pages/pmd/devdocs/adding_metrics_support_to_language.md index 2a299be86..785609397 100644 --- a/docs/pages/pmd/devdocs/adding_metrics_support_to_language.md +++ b/docs/pages/pmd/devdocs/adding_metrics_support_to_language.md @@ -11,80 +11,86 @@ permalink: pmd_devdocs_adding_metrics_support_to_language.html folder: pmd/devdocs --- -{% include warning.html content="WIP" %} +{% include warning.html content="WIP, unstable API" %} ## Internal architecture of the metrics framework ### Overview of the Java framework The framework has several subsystems, the two most easily identifiable being: -* The project mirror (`PackageStats`). This data structure gathers information about the classes, methods and fields of - the analysed project. It allows metrics to know about classes outside the current one, the files being processed one - by one. It's filled by a visitor before rules apply. - - The contents of the structure are indexed with fully qualified names (`JavaQualifiedName`), which must identify - unambiguously classes and methods. The information stored in this data structure that's accessible to metrics is - mainly comprised of method and field signatures (e.g. `JavaOperationSignature`), which describes concisely the - characteristics of the method or field (roughly, its modifiers). - - The project mirror is also responsible for the memoisation of metrics. When a metric is computed, it's stored back - in this structure and can be reused later. This reduces the overhead on the calculation of e.g. aggregate results - (`ResultOption` calculations). - -* The façade. The static end-user façade (`JavaMetrics`) is backed by an instance of a `JavaMetricsFaçade`. This +* A **project memoizer** (`ProjectMemoizer`). When a metric is computed, it's stored back in this structure and can be +reused later. This + reduces the overhead on the calculation of e.g. aggregate results (`ResultOption` calculations). The contents of + this data structure are indexed with fully qualified names (`JavaQualifiedName`), which must identify unambiguously + classes and methods. + +* The **façade**. The static end-user façade (`JavaMetrics`) is backed by an instance of a `JavaMetricsFaçade`. This allows us to abstract the functionality of the façade into `pmd-core` for other frameworks to use. The façade - instance contains a project mirror, representing the analysed project, and a metrics computer + instance contains a project memoizer for the analysed project, and a metrics computer (`JavaMetricsComputer`). It's this last object which really computes the metric and stores back its result in the project mirror, while the façade only handles parameters. Metrics (`Metric`) plug in to this static system and only provide behaviour that's executed by the metrics computer. -Internally, metric keys (`MetricKey`) are parameterized to their version (`MetricVersion`) to index memoisation maps -(see `ParameterizedMetricKey`). This allows us to memoise several versions of the same metric without conflict. +Internally, metric keys (`MetricKey`) are parameterized with their version (`MetricVersion`) to index memoisation +maps (see `ParameterizedMetricKey`). This allows us to memoise several versions of the same metric without conflict. + +{% include important.html content="The following will be moved when multifile analysis and metrics are separated" %} + + +At the very least, a metrics framework has those two components and is just a convenient way to compute and memoize +metrics on a single file. Yet, one of the goals of the metrics framework is to allow for **multi-file analysis**, which +make it possible, for instance, to compute the coupling between two classes. This feature uses two major +components: +* A **project mirror**. This data structure that stores info about all classes and operations (and other relevant + entities, such as fields, packages, etc.) of the analysed project. This is implemented by `PackageStats` in the Java + framework. The role of this structure is to make info about other files available to rules. It's filled by a visitor before rules apply. + + The information stored in this data structure that's accessible to metrics is mainly comprised of method and field + signatures (e.g. `JavaOperationSignature`), which describes concisely the characteristics of the method or field + (roughly, its modifiers). + +* Some kind of method and field **usage resolution**, i.e. some way to find the fully qualified name of a method from a + method call expression node. This is the trickiest part to implement. In Java it depends on type resolution. ### Abstraction layer -As you may have seen, most of the functionality of the façade components has been abstracted into `pmd-core`. This +As you may have seen, most of the functionality of the first two components are abstracted into `pmd-core`. This allows us to implement new metrics frameworks quite quickly. These abstract components are parameterized by the -node types of the class and operation AST nodes. - -The rest of the framework is framed by generic interfaces, but it can't really be abstracted more than that. For -instance, the project mirror is very language specific. Java's implementation uses the natural structure provided by -the language's package system to structure the project's content. Apex, on the other, has no package system and thus -can't use the same mechanism. That explains why the interfaces framing the project mirror are very loose. Their main -goal is to provide type safety through generics. - -Signature matching is another feature that couldn't be abstracted. For now, usage resolution depends on the availability - of type resolution for the given language, which is only implemented in java. We can however match signatures on the - class' own methods or nested classes, which offers limited interest, but may be useful. - -Despite these limitations, once the project mirror is implemented, it's very straightforward to get a working -framework. Additionnally, the external behaviour of the framework is very stable across languages, yet each component - can easily be customized by adding methods or overriding existing ones. +node types of the class and operation AST nodes. Moreover, it makes the external behaviour of the framework is very +stable across languages, yet each component can easily be customized by adding methods or overriding existing ones. + +The signature matching aspect is framed by generic interfaces, but it can't really be abstracted more +than that. For instance, the project mirror is very language specific. Java's implementation uses the natural structure +provided by the language's package system to structure the project's content. Apex, on the other, has no package +system and thus can't use the same mechanism. That explains why the interfaces framing the project mirror are very +loose. Their main goal is to provide type safety through generics. + +Moreover, usage resolution depends on the availability of type resolution for the given language, which is only implemented in +Java. For these reasons, signature matching is considered an optional feature of the metrics framework. But despite +this limitation, signature matching still provides a elegant way to find information about the class we're in. This +feature requires no usage resolution and can be used to implement sophisticated metrics, that already give access to +detection strategies. ## Implementation of a new framework ### 1. Groundwork * Create a class implementing `QualifiedName`. This implementation must be tailored to the target language so -that it can indentify unambiguously any class and operation in the analysed project (see JavaQualifiedName). +that it can indentify unambiguously any class and operation in the analysed project (see `JavaQualifiedName`). You +must implement `equals`, `hashCode` and `toString`. * Determine the AST nodes that correspond to class and method declaration in your language. These types are referred hereafter as `T` and `O`, respectively. Both these types must implement the interface `QualifiableNode`, which -means they must provide a `getQualifiedName` method to give access to their qualified name. +means they must expose a `getQualifiedName` method to give access to their qualified name. -### 2. Implement the project mirror -* Create a class implementing `Memoizer` and one `Memoizer`. An abstract base class is available. Instances of -these classes each represent a class or operation, respectively. They are used to store the results of metrics that -are already computed. -* Create a class implementing `ProjectMirror`. This class will store the memoizers for all the classes and -interfaces of the analysed project. This class must be able to fetch and return a memoizer given the qualified name -of the resource it represents. As it stores the memoizers, it's a good idea to implement some signature matching -utilities in this class. What's signature matching? (See write custom metrics -- TODO) +### 2. Implement the project memoizer +* Create a class extending `BasicProjectMemoizer`. That's all. ### 3. Implement the façade * Create a class extending `AbstractMetricsComputer`. This object will be responsible for calculating metrics given a memoizer, a node and info about the metric. Typically, this object is stateless so you might as well make it a singleton. -* Create a class extending `AbstractMetricsFacade`. This class needs a reference to your `ProjectMirror` and +* Create a class extending `AbstractMetricsFacade`. This class needs a reference to your `ProjectMemoizer` and your `MetricsComputer`. It backs the real end user façade, and handles user provided parameters before delegating to your `MetricsComputer`. * Create the static façade of your framework. This one has an instance of your `MetricsFaçade` object and delegates @@ -95,7 +101,9 @@ static methods to that instance. enums list all available metric keys for your language. * Create metrics by extending your base classes, reference them in your enums, and you can start using them with your façade! - + +{% include important.html content="The following section will be moved when multifile analysis and metrics are separated" %} + ### Optional: Signature matching You can match the signature of anything: method, field, class, package... It depends on what's useful for you. @@ -111,13 +119,6 @@ build a `Signature` from a `N` are a good idea. `public`. A sigmask implements `SigMask`, where `S` is the type of signature your mask handles. * Typically, the project mirror stores the signatures, so you have to implement it in a way that makes it possible to associate a signature with the qualified name of its node. - -{% include important.html -content="Writing this, it seems dumb. If signature matching is optional, it should not require reimplementing - the project mirror. We need to work on dissociating the two. The project mirror would be reduce to a - collection of memoizers, which could be abstracted into pmd-core." %} - - * If you want to implement signature matching, create an `AbstractMetric` class, which gives access to a `SignatureMatcher` to your metrics. Typically, your implementation of `ProjectMirror` implements a custom `SignatureMatcher` interface, and your façade can give back its instance of the project mirror. diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java index 56975c430..aab96a9de 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java @@ -116,7 +116,7 @@ public boolean equals(Object obj) { public static ApexQualifiedName ofString(String toParse) { - return null; + throw new UnsupportedOperationException(); } @@ -146,7 +146,7 @@ private static String getOperationString(ASTMethod node) { sb.append(paramTypes.get(0).getApexName()); for (int i = 1; i < paramTypes.size(); i++) { - sb.append(",").append(paramTypes.get(i).getTypeSignature()); + sb.append(",").append(paramTypes.get(i).getApexName()); } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/AbstractApexMetric.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/AbstractApexMetric.java index e907c0eb8..f093f0b5b 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/AbstractApexMetric.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/AbstractApexMetric.java @@ -5,6 +5,8 @@ package net.sourceforge.pmd.lang.apex.metrics; /** + * Base class for all Apex metrics. + * * @author Clément Fournier */ public class AbstractApexMetric { diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexClassStats.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexClassStats.java index 211970946..6619dafec 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexClassStats.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexClassStats.java @@ -14,6 +14,8 @@ import net.sourceforge.pmd.lang.apex.metrics.signature.ApexOperationSignature; /** + * Stores info about a class. + * * @author Clément Fournier */ class ApexClassStats { diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java index 3d0799a8c..080ae22d0 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetrics.java @@ -13,6 +13,8 @@ import net.sourceforge.pmd.lang.metrics.ResultOption; /** + * User-bound façade of the Apex metrics framework. + * * @author Clément Fournier */ public final class ApexMetrics { diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsComputer.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsComputer.java index cc20d48ce..74ca07e3f 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsComputer.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsComputer.java @@ -11,6 +11,7 @@ import net.sourceforge.pmd.lang.metrics.AbstractMetricsComputer; /** + * Computes metrics for the Apex framework. * @author Clément Fournier */ public class ApexMetricsComputer extends AbstractMetricsComputer, ASTMethod> { diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsFacade.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsFacade.java index 13492f74e..8c22a7e84 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsFacade.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsFacade.java @@ -10,6 +10,7 @@ import net.sourceforge.pmd.lang.metrics.MetricsComputer; /** + * Backs the static façade. * @author Clément Fournier */ public class ApexMetricsFacade extends AbstractMetricsFacade, ASTMethod> { diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMemoizer.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMemoizer.java index 4596887a8..6e71251ee 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMemoizer.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMemoizer.java @@ -9,6 +9,8 @@ import net.sourceforge.pmd.lang.metrics.BasicProjectMemoizer; /** + * Memoizer for Apex metrics. + * * @author Clément Fournier */ class ApexProjectMemoizer extends BasicProjectMemoizer, ASTMethod> { diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java index de7b50115..a747db733 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java @@ -11,6 +11,8 @@ import net.sourceforge.pmd.lang.apex.metrics.signature.ApexOperationSigMask; /** + * Equivalent to PackageStats in the java framework. + * * @author Clément Fournier */ public class ApexProjectMirror implements ApexSignatureMatcher { @@ -41,6 +43,4 @@ public boolean hasMatchingSig(ApexQualifiedName qname, ApexOperationSigMask mask } - - } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/rule/CyclomaticComplexityRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/rule/CyclomaticComplexityRule.java index e313daee1..9b3d0736d 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/rule/CyclomaticComplexityRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/rule/CyclomaticComplexityRule.java @@ -17,7 +17,8 @@ import net.sourceforge.pmd.lang.rule.properties.IntegerProperty; /** - * Cyclomatic complexity rule using metrics. + * Cyclomatic complexity rule using metrics. Uses Wmc to report classes (the Java rule will be updated as well in an + * upcoming PR) * * @author Clément Fournier */ diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedNameTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedNameTest.java index 0e97f5084..6fbbe3e92 100644 --- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedNameTest.java +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedNameTest.java @@ -59,10 +59,10 @@ public void testSimpleMethod() { public void testMethodWithArguments() { ApexNode root = ApexParserTestHelpers.parse("public class Foo { String foo(String h, Foo g) {}}"); ApexQualifiedName qname = root.getFirstDescendantOfType(ASTMethod.class).getQualifiedName(); - assertEquals("__Foo#foo(String,LFoo;)", qname.toString()); + assertEquals("__Foo#foo(String,Foo)", qname.toString()); assertEquals(1, qname.getClasses().length); assertNotNull(qname.getNameSpace()); - assertEquals("foo(String,LFoo;)", qname.getOperation()); + assertEquals("foo(String,Foo)", qname.getOperation()); } From 51bc82c889366b56aa0f3f32451240c969797511 Mon Sep 17 00:00:00 2001 From: oowekyala Date: Mon, 7 Aug 2017 18:35:14 +0200 Subject: [PATCH 16/19] Custom namespace when they're unknown --- .../pmd/lang/apex/ast/ApexQualifiedName.java | 4 +++- .../lang/apex/ast/ApexQualifiedNameTest.java | 8 ++++---- .../lang/apex/metrics/impl/xml/CycloTest.xml | 17 +++++++++++------ .../pmd/lang/apex/metrics/impl/xml/WmcTest.xml | 6 +++--- .../metrics/signature/JavaFieldSigMask.java | 1 - .../pmd/lang/java/metrics/SigMaskTest.java | 8 +------- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java index aab96a9de..720923795 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java @@ -9,6 +9,8 @@ import java.util.Objects; import java.util.regex.Pattern; +import org.apache.commons.lang3.StringUtils; + import net.sourceforge.pmd.lang.ast.QualifiedName; import apex.jorje.semantic.symbol.type.TypeInfo; @@ -123,7 +125,7 @@ public static ApexQualifiedName ofString(String toParse) { static ApexQualifiedName ofOuterClass(ASTUserClassOrInterface astUserClass) { String ns = astUserClass.getNode().getDefiningType().getNamespace().toString(); String[] classes = {astUserClass.getImage()}; - return new ApexQualifiedName(ns, classes, null); + return new ApexQualifiedName(StringUtils.isEmpty(ns) ? "c" : ns, classes, null); } diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedNameTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedNameTest.java index 6fbbe3e92..a85430f8f 100644 --- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedNameTest.java +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedNameTest.java @@ -25,7 +25,7 @@ public void testClass() { ApexNode root = ApexParserTestHelpers.parse("public class Foo {}"); ApexQualifiedName qname = ASTUserClass.class.cast(root).getQualifiedName(); - assertEquals("__Foo", qname.toString()); + assertEquals("c__Foo", qname.toString()); assertEquals(1, qname.getClasses().length); assertNotNull(qname.getNameSpace()); assertNull(qname.getOperation()); @@ -37,7 +37,7 @@ public void testNestedClass() { ApexNode root = ApexParserTestHelpers.parse("public class Foo { class Bar {}}"); ApexQualifiedName qname = root.getFirstDescendantOfType(ASTUserClass.class).getQualifiedName(); - assertEquals("__Foo.Bar", qname.toString()); + assertEquals("c__Foo.Bar", qname.toString()); assertEquals(2, qname.getClasses().length); assertNotNull(qname.getNameSpace()); assertNull(qname.getOperation()); @@ -48,7 +48,7 @@ public void testNestedClass() { public void testSimpleMethod() { ApexNode root = ApexParserTestHelpers.parse("public class Foo { String foo() {}}"); ApexQualifiedName qname = root.getFirstDescendantOfType(ASTMethod.class).getQualifiedName(); - assertEquals("__Foo#foo()", qname.toString()); + assertEquals("c__Foo#foo()", qname.toString()); assertEquals(1, qname.getClasses().length); assertNotNull(qname.getNameSpace()); assertEquals("foo()", qname.getOperation()); @@ -59,7 +59,7 @@ public void testSimpleMethod() { public void testMethodWithArguments() { ApexNode root = ApexParserTestHelpers.parse("public class Foo { String foo(String h, Foo g) {}}"); ApexQualifiedName qname = root.getFirstDescendantOfType(ASTMethod.class).getQualifiedName(); - assertEquals("__Foo#foo(String,Foo)", qname.toString()); + assertEquals("c__Foo#foo(String,Foo)", qname.toString()); assertEquals(1, qname.getClasses().length); assertNotNull(qname.getNameSpace()); assertEquals("foo(String,Foo)", qname.getOperation()); diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/metrics/impl/xml/CycloTest.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/metrics/impl/xml/CycloTest.xml index c04f26659..6b1771c72 100644 --- a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/metrics/impl/xml/CycloTest.xml +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/metrics/impl/xml/CycloTest.xml @@ -18,6 +18,11 @@ public void example() { int x = 0, y = 1, z = 2, t = 2; boolean a = false, b = true, c = false, d = true; + + for (String s : list) { + x++; + } + if (a && b || b && d) { if (y == z) { x = 2; @@ -49,8 +54,8 @@ Complicated method - Standard 2 - '__Complicated#exception()' has value 4. - '__Complicated#example()' has value 17. + 'c__Complicated#exception()' has value 4. + 'c__Complicated#example()' has value 18. @@ -61,7 +66,7 @@ false 1 - '__Foo#foo()' has value 1. + 'c__Foo#foo()' has value 1. false 1 - '__Test#Test()' has value 4. + 'c__Test#Test()' has value 4. @@ -119,7 +124,7 @@ 1 false - '__Foo#foo()' has value 8. + 'c__Foo#foo()' has value 8. @@ -128,7 +133,7 @@ Ternary expression counts 1 + boolean complexity 1 - '__Foo#bar()' has value 3. + 'c__Foo#bar()' has value 3. Complicated class 1 - '__Complicated' has value 16. + 'c__Complicated' has value 16. @@ -51,7 +51,7 @@ Empty classes count 0 1 - '__Foo' has value 0. + 'c__Foo' has value 0. Abstract classes and enums are supported 1 - '__Foo' has value 2. + 'c__Foo' has value 2. { private boolean coverFinal = true; diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/SigMaskTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/SigMaskTest.java index 2d5ef2867..75fe6fa4d 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/SigMaskTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/SigMaskTest.java @@ -74,7 +74,6 @@ public class SigMaskTest extends ParserTst { + "abstract void setZ(int x);" + "}"; - /** * Ensure any non-abstract method is covered by a newly created mask. */ @@ -92,7 +91,6 @@ public void testEmptyOperationMask() { } } - /** * Ensure any field is covered by a newly created mask. */ @@ -106,7 +104,6 @@ public void testEmptyFieldMask() { } } - @Test public void testFinalFields() { List nodes = getOrderedNodes(ASTFieldDeclaration.class, TEST_FIELDS); @@ -122,7 +119,6 @@ public void testFinalFields() { } } - @Test public void testStaticFields() { List nodes = getOrderedNodes(ASTFieldDeclaration.class, TEST_FIELDS); @@ -138,7 +134,6 @@ public void testStaticFields() { } } - @Test public void testFieldvisibility() { List nodes = getOrderedNodes(ASTFieldDeclaration.class, TEST_FIELDS); @@ -183,7 +178,7 @@ public void testFieldvisibility() { assertFalse(mask.covers(JavaFieldSignature.buildFor(node))); } } - + } @@ -236,7 +231,6 @@ public void testOperationVisibility() { } } - @Test public void testOperationRoles() { List nodes = getOrderedNodes(ASTMethodOrConstructorDeclaration.class, From 54ec57bb3ee85de4b5933cd55b4515515c4079d6 Mon Sep 17 00:00:00 2001 From: oowekyala Date: Sun, 13 Aug 2017 15:27:04 +0200 Subject: [PATCH 17/19] =?UTF-8?q?Corrections=20for=20PR=C2=A0#545?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pmd/lang/apex/ast/ApexQualifiedName.java | 18 ++++-- .../lang/apex/metrics/impl/CycloMetric.java | 6 +- .../main/resources/rulesets/apex/metrics.xml | 4 +- .../apex/metrics/ApexMetricsVisitorTest.java | 48 ++-------------- .../apex/metrics/ApexProjectMirrorTest.java | 55 +++++-------------- .../apex/metrics/MetadataDeployController.cls | 41 ++++++++++++++ 6 files changed, 79 insertions(+), 93 deletions(-) create mode 100644 pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/metrics/MetadataDeployController.cls diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java index 720923795..f65605d0a 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java @@ -7,7 +7,6 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; -import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; @@ -22,7 +21,6 @@ */ public class ApexQualifiedName implements QualifiedName { - private static final Pattern FORMAT = Pattern.compile("(\\w+)__(\\w+)(.(\\w+))?(#(\\w+))?"); // TODO private final String nameSpace; private final String[] classes; @@ -36,13 +34,11 @@ private ApexQualifiedName(String nameSpace, String[] classes, String operation) } - public String getOperation() { return operation; } - public String[] getClasses() { return Arrays.copyOf(classes, classes.length); } @@ -117,6 +113,20 @@ public boolean equals(Object obj) { } + /** + * Parses a string conforming to the format defined below and returns an ApexQualifiedName. + * + *

Here are some examples of the format: + *

    + *
  • {@code namespace__OuterClass.InnerClass}: name of an inner class + *
  • {@code namespace__Class#method(String, int)}: name of an operation + *
+ * + * @param toParse The string to parse + * + * @return An ApexQualifiedName, or null if the string couldn't be parsed + */ + // private static final Pattern FORMAT = Pattern.compile("(\\w+)__(\\w+)(.(\\w+))?(#(\\w+))?"); // TODO public static ApexQualifiedName ofString(String toParse) { throw new UnsupportedOperationException(); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/CycloMetric.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/CycloMetric.java index 737aad1b3..1946dfa24 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/CycloMetric.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/impl/CycloMetric.java @@ -15,7 +15,7 @@ import net.sourceforge.pmd.lang.apex.metrics.impl.visitors.StandardCycloVisitor; import net.sourceforge.pmd.lang.metrics.MetricVersion; -import apex.jorje.semantic.ast.expression.BooleanExpressionType; +import apex.jorje.data.ast.BooleanOp; /** * See the doc for the Java metric. @@ -44,8 +44,8 @@ public static int booleanExpressionComplexity(ASTStandardCondition expression) { int complexity = 0; for (ASTBooleanExpression sub : subs) { - BooleanExpressionType type = sub.getNode().getBooleanExpressionType(); - if (type != null && (type == BooleanExpressionType.OR || type == BooleanExpressionType.AND)) { + BooleanOp op = sub.getNode().getOp(); + if (op != null && (op == BooleanOp.AND || op == BooleanOp.OR)) { complexity++; } } diff --git a/pmd-apex/src/main/resources/rulesets/apex/metrics.xml b/pmd-apex/src/main/resources/rulesets/apex/metrics.xml index 0709228dc..4d7468f92 100644 --- a/pmd-apex/src/main/resources/rulesets/apex/metrics.xml +++ b/pmd-apex/src/main/resources/rulesets/apex/metrics.xml @@ -11,10 +11,10 @@ + externalInfoUrl="${pmd.website.baseurl}/rules/apex/metrics.html#CyclomaticComplexity"> acu = parseAndVisitForString("public with sharing class MetadataDeployController \n" - + "{\n" - + "\tprivate class Foo {\n" - + "}\n" - + "\n" - + "\tglobal String ZipData { get; set; }\t\n" - + "\t\n" - + "\tpublic MetadataService.AsyncResult AsyncResult {get; private set;}\n" - + "\t\n" - + "\tpublic String getPackageXml(String page)\n" - + "\t{\n" - + "\t\treturn '' + \n" - + "\t\t\t'' + \n" - + " \t\t\t'' + \n" - + " \t\t\t'HelloWorld' +\n" - + " \t\t\t'ApexClass' + \n" - + " \t\t\t'' + \n" - + " \t\t\t'26.0' + \n" - + "\t\t\t'';\t\t\n" - + "\t}\n" - + "\t\n" - + "\tpublic String getHelloWorldMetadata()\n" - + "\t{\n" - + "\t\treturn '' +\n" - + "\t\t\t'' +\n" - + "\t\t\t '28.0' + \n" - + "\t\t\t 'Active' +\n" - + "\t\t\t'';\t\t\n" - + "\t}\n" - + "\t\n" - + "\tpublic String getHelloWorld()\t\n" - + "\t{\n" - + "\t\treturn 'public class HelloWorld' + \n" - + "\t\t\t'{' + \n" - + "\t\t\t\t'public static void helloWorld()' +\n" - + "\t\t\t\t'{' + \n" - + "\t\t\t\t\t'System.debug(\\' Hello World\\');' +\n" - + "\t\t\t\t'}' +\n" - + "\t\t\t'}';\n" - + "\t}" - + "}"); + public void testOperationsAreThere() throws IOException { + ApexNode acu = parseAndVisitForString( + IOUtils.toString(ApexMetricsVisitorTest.class.getResourceAsStream("MetadataDeployController.cls"))); final ApexSignatureMatcher toplevel = ApexMetrics.getFacade().getProjectMirror(); diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirrorTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirrorTest.java index 6a12068c4..f7e65b4f9 100644 --- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirrorTest.java +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirrorTest.java @@ -8,10 +8,12 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Random; +import org.apache.commons.io.IOUtils; import org.junit.Test; import net.sourceforge.pmd.lang.apex.ast.ASTMethod; @@ -34,52 +36,21 @@ */ public class ApexProjectMirrorTest { - private static ApexNode acu - = parseAndVisitForString("public with sharing class MetadataDeployController \n" - + "{\n" - + "\tprivate class Foo {\n" - + "}\n" - + "\n" - + "\tglobal String ZipData { get; set; }\t\n" - + "\t\n" - + "\tpublic MetadataService.AsyncResult AsyncResult {get; private set;}\n" - + "\t\n" - + "\tpublic String getPackageXml(String page)\n" - + "\t{\n" - + "\t\treturn '' + \n" - + "\t\t\t'' + \n" - + " \t\t\t'' + \n" - + " \t\t\t'HelloWorld' +\n" - + " \t\t\t'ApexClass' + \n" - + " \t\t\t'' + \n" - + " \t\t\t'26.0' + \n" - + "\t\t\t'';\t\t\n" - + "\t}\n" - + "\t\n" - + "\tpublic String getHelloWorldMetadata()\n" - + "\t{\n" - + "\t\treturn '' +\n" - + "\t\t\t'' +\n" - + "\t\t\t '28.0' + \n" - + "\t\t\t 'Active' +\n" - + "\t\t\t'';\t\t\n" - + "\t}\n" - + "\t\n" - + "\tpublic String getHelloWorld()\t\n" - + "\t{\n" - + "\t\treturn 'public class HelloWorld' + \n" - + "\t\t\t'{' + \n" - + "\t\t\t\t'public static void helloWorld()' +\n" - + "\t\t\t\t'{' + \n" - + "\t\t\t\t\t'System.debug(\\' Hello World\\');' +\n" - + "\t\t\t\t'}' +\n" - + "\t\t\t'}';\n" - + "\t}" - + "}"); + private static ApexNode acu; private MetricKey> classMetricKey = MetricKeyUtil.of(new RandomClassMetric(), null); private MetricKey opMetricKey = MetricKeyUtil.of(new RandomOperationMetric(), null); + static { + try { + acu = parseAndVisitForString( + IOUtils.toString(ApexMetricsVisitorTest.class.getResourceAsStream("MetadataDeployController.cls"))); + } catch (IOException ioe) { + // Should definitely not happen + } + } + + @Test public void memoizationTest() { diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/metrics/MetadataDeployController.cls b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/metrics/MetadataDeployController.cls new file mode 100644 index 000000000..12af4169b --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/metrics/MetadataDeployController.cls @@ -0,0 +1,41 @@ +public with sharing class MetadataDeployController +{ + private class Foo { + } + + global String ZipData { get; set; } + + public MetadataService.AsyncResult AsyncResult {get; private set;} + + public String getPackageXml(String page) + { + return '' + + '' + + '' + + 'HelloWorld' + + 'ApexClass' + + '' + + '26.0' + + ''; + } + + public String getHelloWorldMetadata() + { + return '' + + '' + + '28.0' + + 'Active' + + ''; + } + + public String getHelloWorld() + { + return 'public class HelloWorld' + + '{' + + 'public static void helloWorld()' + + '{' + + 'System.debug(\' Hello World\');' + + '}' + + '}'; + } +} \ No newline at end of file From 65fd4ea123d9293c191b19916538fdd658eaece3 Mon Sep 17 00:00:00 2001 From: oowekyala Date: Sun, 13 Aug 2017 16:57:34 +0200 Subject: [PATCH 18/19] Added examples to metric docs & visitor description --- .../adding_metrics_support_to_language.md | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/docs/pages/pmd/devdocs/adding_metrics_support_to_language.md b/docs/pages/pmd/devdocs/adding_metrics_support_to_language.md index 785609397..7773d1a1e 100644 --- a/docs/pages/pmd/devdocs/adding_metrics_support_to_language.md +++ b/docs/pages/pmd/devdocs/adding_metrics_support_to_language.md @@ -77,30 +77,47 @@ detection strategies. ### 1. Groundwork * Create a class implementing `QualifiedName`. This implementation must be tailored to the target language so -that it can indentify unambiguously any class and operation in the analysed project (see `JavaQualifiedName`). You -must implement `equals`, `hashCode` and `toString`. + that it can indentify unambiguously any class and operation in the analysed project. You + must implement `equals`, `hashCode` and `toString`. + [Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedName.java) * Determine the AST nodes that correspond to class and method declaration in your language. These types are -referred hereafter as `T` and `O`, respectively. Both these types must implement the interface `QualifiableNode`, which -means they must expose a `getQualifiedName` method to give access to their qualified name. - -### 2. Implement the project memoizer -* Create a class extending `BasicProjectMemoizer`. That's all. + referred hereafter as `T` and `O`, respectively. Both these types must implement the interface `QualifiableNode`, + which means they must expose a `getQualifiedName` method to give access to their qualified name. + +### 2. Implement and wire the project memoizer +* Create a class extending `BasicProjectMemoizer`. There's no abstract functionality to implement. + [Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaProjectMemoizer.java) +* Create an AST visitor that fills the project memoizer with memoizers. For that, you use `BasicProjectMemoizer`'s + `addClassMemoizer` and `addOperationMemoizer` methods with a qualified name. + [Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsVisitor.java) +* Create a façade class for your visitor. This class extends a `*ParserVisitorAdapter` class and only overrides the + `initializeWith(Node)` method. It's supposed to make your real visitor accept the node in parameter. + [Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsVisitorFacade.java) +* Override the `getMetricsVisitorFacade()` method in your language's handler (e.g. `ApexHandler`). This method gives + back a `VisitorStarter` which initializes your façade with a `Node`. + [Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/AbstractJavaHandler.java#L100-L108) +* Your project memoizer should now get filled when the `metrics` attribute is set to `true` in the rule XML. ### 3. Implement the façade * Create a class extending `AbstractMetricsComputer`. This object will be responsible for calculating metrics -given a memoizer, a node and info about the metric. Typically, this object is stateless so you might as well make it -a singleton. + given a memoizer, a node and info about the metric. Typically, this object is stateless so you might as well make it + a singleton. + [Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsComputer.java) * Create a class extending `AbstractMetricsFacade`. This class needs a reference to your `ProjectMemoizer` and -your `MetricsComputer`. It backs the real end user façade, and handles user provided parameters before delegating to -your `MetricsComputer`. + your `MetricsComputer`. It backs the real end user façade, and handles user provided parameters before delegating to + your `MetricsComputer`. + [Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsFacade.java) * Create the static façade of your framework. This one has an instance of your `MetricsFaçade` object and delegates -static methods to that instance. + static methods to that instance. + [Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetrics.java) * Create classes `AbstractOperationMetric` and `AbstractClassMetric`. These must implement `Metric` and -`Metric`, respectively. They typically provide defaults for the `supports` method of each metric. + `Metric`, respectively. They typically provide defaults for the `supports` method of each metric. + [Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractJavaOperationMetric.java) * Create enums `ClassMetricKey` and `OperationMetricKey`. These must implement `MetricKey` and `MetricKey`. The - enums list all available metric keys for your language. + enums list all available metric keys for your language. + [Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetricKey.java) * Create metrics by extending your base classes, reference them in your enums, and you can start using them with your - façade! + façade! {% include important.html content="The following section will be moved when multifile analysis and metrics are separated" %} From 40c65acabb213d6495684711dab62bdc22195ca5 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Mon, 14 Aug 2017 10:20:48 +0200 Subject: [PATCH 19/19] Update changelog, refs #545 --- src/site/markdown/overview/changelog.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/site/markdown/overview/changelog.md b/src/site/markdown/overview/changelog.md index 5e45ae798..394da3cc9 100644 --- a/src/site/markdown/overview/changelog.md +++ b/src/site/markdown/overview/changelog.md @@ -50,6 +50,9 @@ on the new metrics framework for object-oriented metrics. There are already a couple of metrics (e.g. ATFD, WMC, Cyclo, LoC) implemented. More metrics are planned. Based on those metrics, rules like "GodClass" detection can be implemented more easily. +The Metrics framework has been abstracted and is available in `pmd-core` for other languages. With this +PMD release, the metrics framework is supported for both Java and Apex. + #### Configuration Error Reporting For a long time reports have been notified of configuration errors on rules, but they have remained hidden. @@ -81,7 +84,6 @@ and include them to such reports. * The deprecated rule `UseSingleton` has been removed from the ruleset `java-design`. The rule has been renamed long time ago to `UseUtilityClass`. - #### Java Symbol Table A [bug in symbol table](https://github.com/pmd/pmd/pull/549/commits/0958621ca884a8002012fc7738308c8dfc24b97c) prevented @@ -90,8 +92,9 @@ rule, but other rules may now produce improved results as consequence of this fi #### Apex Parser Update -The Apex parser version was bumped, from `1.0-sfdc-187` to `1.0-sfdc-224`. This update let us take full advatange +The Apex parser version was bumped, from `1.0-sfdc-187` to `1.0-sfdc-224`. This update let us take full advantage of the latest improvements from Salesforce, but introduces some breaking changes: + * `BlockStatements` are now created for all control structures, even if no brace is used. We have therefore added a `hasCurlyBrace` method to differentiate between both scenarios. * New AST node types are available. In particular `CastExpression`, `ConstructorPreamble`, `IllegalStoreExpression`, @@ -100,7 +103,7 @@ of the latest improvements from Salesforce, but introduces some breaking changes * Some nodes have been removed. Such is the case of `TestNode`, `DottedExpression` and `NewNameValueObjectExpression` (replaced by `NewKeyValueObjectExpression`) -Al existing rules have been updated to reflect these changes. If you have custom rules, be sure to update them. +All existing rules have been updated to reflect these changes. If you have custom rules, be sure to update them. ### Fixed Issues @@ -161,5 +164,5 @@ Al existing rules have been updated to reflect these changes. If you have custom * [#530](https://github.com/pmd/pmd/pull/530): \[java] Fix issue #527: Lombok getter annotation on enum is not recognized correctly - [Clément Fournier](https://github.com/oowekyala) * [#535](https://github.com/pmd/pmd/pull/535): \[apex] Fix broken Apex visitor adapter - [Clément Fournier](https://github.com/oowekyala) * [#542](https://github.com/pmd/pmd/pull/542): \[java] Metrics abstraction - [Clément Fournier](https://github.com/oowekyala) +* [#545](https://github.com/pmd/pmd/pull/545): \[apex] Apex metrics framework - [Clément Fournier](https://github.com/oowekyala) * [#548](https://github.com/pmd/pmd/pull/548): \[java] Metrics documentation - [Clément Fournier](https://github.com/oowekyala) -