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 fabb8a22ad..1451e8c065 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 @@ -33,12 +33,12 @@ public abstract class AbstractJavaMetric implements Metric { /** - * Gives access to the project mirror to metrics. They can use it to perform signature matching. + * Gives access to a signature matcher to metrics. They can use it to perform signature matching. * - * @return The project mirror (singleton contained within {@link JavaMetricsFacade}). + * @return A signature matcher */ - protected static JavaProjectMirror getJavaProjectMirror() { - return JavaMetrics.getJavaProjectMirror(); + protected static JavaSignatureMatcher getSignatureMatcher() { + return JavaMetrics.getTopLevelPackageStats(); } } 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 deccf07114..a773c30d15 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 @@ -33,7 +33,7 @@ public final class JavaMetrics { * * @return The project mirror */ - static JavaProjectMirror getJavaProjectMirror() { + static PackageStats getTopLevelPackageStats() { return FACADE.getLanguageSpecificProjectMirror(); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsFacade.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsFacade.java index 1f8bc581e9..ad53839266 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsFacade.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsFacade.java @@ -26,7 +26,7 @@ class JavaMetricsFacade extends AbstractMetricsFacade { - - /** - * 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(JavaQualifiedName qname, OperationSigMask sigMask); - - - /** - * Returns true if the signature of the field designated by its name and the qualified name of its class is covered - * by the mask. - * - * @param qname The class of the field - * @param fieldName The name of the field - * @param sigMask The signature mask to use - * - * @return True if the signature of the field is covered by the mask - */ - boolean hasMatchingSig(JavaQualifiedName qname, String fieldName, FieldSigMask sigMask); - - } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaSignatureMatcher.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaSignatureMatcher.java new file mode 100644 index 0000000000..a95805dd32 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaSignatureMatcher.java @@ -0,0 +1,42 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.metrics; + +import net.sourceforge.pmd.lang.java.ast.JavaQualifiedName; +import net.sourceforge.pmd.lang.java.metrics.signature.FieldSigMask; +import net.sourceforge.pmd.lang.java.metrics.signature.OperationSigMask; + +/** + * Gathers the methods that the Java project mirror should make available to metrics during the computation. + * + * @author Clément Fournier + */ +public interface JavaSignatureMatcher { + + /** + * 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(JavaQualifiedName qname, OperationSigMask sigMask); + + + /** + * Returns true if the signature of the field designated by its name and the qualified name of its class is covered + * by the mask. + * + * @param qname The class of the field + * @param fieldName The name of the field + * @param sigMask The signature mask to use + * + * @return True if the signature of the field is covered by the mask + */ + boolean hasMatchingSig(JavaQualifiedName qname, String fieldName, FieldSigMask sigMask); + + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/PackageStats.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/PackageStats.java index 9413844413..8c01b465fa 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/PackageStats.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/PackageStats.java @@ -24,7 +24,7 @@ import net.sourceforge.pmd.lang.metrics.MetricMemoizer; * @author Clément Fournier * @see ClassStats */ -public final class PackageStats implements JavaProjectMirror { +public final class PackageStats implements JavaProjectMirror, JavaSignatureMatcher { private final Map subPackages = new HashMap<>(); private final Map classes = new HashMap<>(); 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 e8350a11a0..5165ec4519 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 @@ -34,7 +34,7 @@ public final class AtfdMetric { List callQNames = findAllCalls(node); int foreignCalls = 0; for (JavaQualifiedName name : callQNames) { - if (getJavaProjectMirror().hasMatchingSig(name, targetOps)) { + if (getSignatureMatcher().hasMatchingSig(name, targetOps)) { foreignCalls++; } } 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 8321c7a5f2..8fc18826c0 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 @@ -4,9 +4,14 @@ package net.sourceforge.pmd.lang.java.metrics.impl; +import java.util.List; + import org.apache.commons.lang3.mutable.MutableInt; import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTConditionalAndExpression; +import net.sourceforge.pmd.lang.java.ast.ASTConditionalOrExpression; +import net.sourceforge.pmd.lang.java.ast.ASTExpression; import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.JavaParserVisitor; import net.sourceforge.pmd.lang.java.metrics.JavaMetrics; @@ -24,27 +29,69 @@ import net.sourceforge.pmd.lang.metrics.api.ResultOption; * *

The standard version of the metric complies with McCabe's original definition [3]: * - *

  • +1 for every control flow statement ({@code if, case, catch, throw, do, while, for, break, continue}) and + *
      + *
    • +1 for every control flow statement ({@code if, case, catch, throw, do, while, for, break, continue}) and * conditional expression ({@code ? : }). Notice switch cases count as one, but not the switch itself: the point is that - * a switch should have the same complexity value as the equivalent series of {@code if} statements.
    • {@code else}, - * {@code finally} and {@code default} don't count;
    • +1 for every boolean operator ({@code &&, ||}) in the guard - * condition of a control flow statement. That's because Java has short-circuit evaluation semantics for boolean - * operators, which makes every boolean operator kind of a control flow statement in itself.
    + * a switch should have the same complexity value as the equivalent series of {@code if} statements. + *
  • {@code else}, {@code finally} and {@code default} don't count; + *
  • +1 for every boolean operator ({@code &&, ||}) in the guard condition of a control flow statement. That's because + * Java has short-circuit evaluation semantics for boolean operators, which makes every boolean operator kind of a + * control flow statement in itself. + *
* *

Version {@link CycloVersion#IGNORE_BOOLEAN_PATHS}: Boolean operators are not counted, which means that empty * fall-through cases in {@code switch} statements are not counted as well. * - *

References:

  • [1] Lanza, Object-Oriented Metrics in Practice, 2005.
  • [2] McCabe, A Complexity Measure, - * in Proceedings of the 2nd ICSE (1976).
  • [3] Sonarqube - * online documentation
+ *

References: + * + *

    + *
  • [1] Lanza, Object-Oriented Metrics in Practice, 2005. + *
  • [2] McCabe, A Complexity Measure, in Proceedings of the 2nd ICSE (1976). + *
  • [3] Sonarqube online documentation + *
* * @author Clément Fournier * @since June 2017 */ public final class CycloMetric { + private CycloMetric() { + + } + // TODO:cf Cyclo should develop factorized boolean operators to count them + + /** + * Evaluates the number of paths through a boolean expression. This is the total number of {@code &&} and {@code ||} + * operators appearing in the expression. This is used in the calculation of cyclomatic and n-path complexity. + * + * @param expr Expression to analyse + * + * @return The number of paths through the expression + */ + public static int booleanExpressionComplexity(ASTExpression expr) { + if (expr == null) { + return 0; + } + + List andNodes = expr.findDescendantsOfType(ASTConditionalAndExpression.class); + List orNodes = expr.findDescendantsOfType(ASTConditionalOrExpression.class); + + int complexity = 0; + + for (ASTConditionalOrExpression element : orNodes) { + complexity += element.jjtGetNumChildren() - 1; + } + + for (ASTConditionalAndExpression element : andNodes) { + complexity += element.jjtGetNumChildren() - 1; + } + + return complexity; + } + + /** Variants of CYCLO. */ public enum CycloVersion implements MetricVersion { /** Do not count the paths in boolean expressions as decision points. */ @@ -72,4 +119,5 @@ public final class CycloMetric { return 1 + JavaMetrics.get(JavaOperationMetricKey.CYCLO, node, version, ResultOption.AVERAGE); } } + } 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 9a37a2f0a5..3b669d51d7 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 @@ -136,7 +136,7 @@ public class DataStructureTest extends ParserTst { } private List visitWith(ASTCompilationUnit acu, final boolean force) { - final JavaProjectMirror toplevel = JavaMetrics.getJavaProjectMirror(); + final PackageStats toplevel = JavaMetrics.getTopLevelPackageStats(); final List result = new ArrayList<>(); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsVisitorTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsVisitorTest.java index f9e368eff3..482f7cc7f6 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsVisitorTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsVisitorTest.java @@ -39,7 +39,7 @@ public class JavaMetricsVisitorTest { @Test public void testPackageStatsNotNull() { - assertNotNull(JavaMetrics.getJavaProjectMirror()); + assertNotNull(JavaMetrics.getTopLevelPackageStats()); } @@ -47,7 +47,7 @@ public class JavaMetricsVisitorTest { public void testOperationsAreThere() { ASTCompilationUnit acu = parseAndVisitForClass15(MetricsVisitorTestData.class); - final JavaProjectMirror toplevel = JavaMetrics.getJavaProjectMirror(); + final JavaSignatureMatcher toplevel = JavaMetrics.getTopLevelPackageStats(); final OperationSigMask opMask = new OperationSigMask(); @@ -67,7 +67,7 @@ public class JavaMetricsVisitorTest { parseAndVisitForClass15(MetricsVisitorTestData.class); - final JavaProjectMirror toplevel = JavaMetrics.getJavaProjectMirror(); + final JavaSignatureMatcher toplevel = JavaMetrics.getTopLevelPackageStats(); final FieldSigMask fieldSigMask = new FieldSigMask(); @@ -90,7 +90,7 @@ public class JavaMetricsVisitorTest { public void testStaticOperationsSig() { parseAndVisitForClass15(MetricsVisitorTestData.class); - final JavaProjectMirror toplevel = JavaMetrics.getJavaProjectMirror(); + final JavaSignatureMatcher toplevel = JavaMetrics.getTopLevelPackageStats(); final OperationSigMask operationSigMask = new OperationSigMask(); operationSigMask.restrictRolesTo(Role.STATIC);