diff --git a/java-checks-common/src/main/java/org/sonar/java/checks/helpers/logic/HelperPredicates.java b/java-checks-common/src/main/java/org/sonar/java/checks/helpers/logic/HelperPredicates.java new file mode 100644 index 00000000000..3dc0ac27576 --- /dev/null +++ b/java-checks-common/src/main/java/org/sonar/java/checks/helpers/logic/HelperPredicates.java @@ -0,0 +1,19 @@ +package org.sonar.java.checks.helpers.logic; + +import org.sonar.plugins.java.api.semantic.Symbol; + +import static org.sonar.java.checks.helpers.logic.Ternary.UNKNOWN; + +/** + * Demo for {@link Ternary} + */ +public class HelperPredicates { + private HelperPredicates() {} + + public static Ternary isUsed(Symbol symbol) { + if(symbol.isUnknown()) { + return UNKNOWN; + } + return Ternary.of(!symbol.usages().isEmpty()); + } +} diff --git a/java-checks-common/src/main/java/org/sonar/java/checks/helpers/logic/Summary.java b/java-checks-common/src/main/java/org/sonar/java/checks/helpers/logic/Summary.java new file mode 100644 index 00000000000..cc92cbe7e37 --- /dev/null +++ b/java-checks-common/src/main/java/org/sonar/java/checks/helpers/logic/Summary.java @@ -0,0 +1,58 @@ +package org.sonar.java.checks.helpers.logic; + +import static org.sonar.java.checks.helpers.logic.Ternary.FALSE; +import static org.sonar.java.checks.helpers.logic.Ternary.TRUE; +import static org.sonar.java.checks.helpers.logic.Ternary.UNKNOWN; + +/** + * Utility to for implementing logical "and" and "or" in {@link Ternary}, both + * directly and as a {@link java.util.stream.Collector}. + */ +class Summary { + private boolean allTrue = true; + private boolean anyTrue = false; + private boolean allFalse = true; + private boolean anyFalse = false; + + void add(Ternary arg) { + allTrue &= arg.is(true); + anyTrue |= arg.is(true); + allFalse &= arg.is(false); + anyFalse |= arg.is(false); + } + + Summary addAll(Ternary... args) { + for (Ternary arg : args) { + add(arg); + } + return this; + } + + Summary combine(Summary other) { + allTrue &= other.allTrue; + anyTrue |= other.anyTrue; + allFalse &= other.allFalse; + anyFalse |= other.anyFalse; + return this; + } + + Ternary logicalAnd() { + if (allTrue) { + return TRUE; + } else if (anyFalse) { + return FALSE; + } else { + return UNKNOWN; + } + } + + Ternary logicalOr() { + if (allFalse) { + return FALSE; + } else if (anyTrue) { + return TRUE; + } else { + return UNKNOWN; + } + } +} diff --git a/java-checks-common/src/main/java/org/sonar/java/checks/helpers/logic/Ternary.java b/java-checks-common/src/main/java/org/sonar/java/checks/helpers/logic/Ternary.java new file mode 100644 index 00000000000..142ffca5b9d --- /dev/null +++ b/java-checks-common/src/main/java/org/sonar/java/checks/helpers/logic/Ternary.java @@ -0,0 +1,110 @@ +package org.sonar.java.checks.helpers.logic; + +import java.util.stream.Collector; + +/** + * Implementation of tree-valued logic in which a proposition can be true, false, or unknown. + * + *
This class allows us to be precise in certain predicates, for instance + * {@code type.isSubtypeOf("com.example.MySuper")}, where the exact value is unknown + * due to missing semantics. + */ +public enum Ternary { + TRUE, FALSE, UNKNOWN; + + /** + * Adapter for {@code boolean}. + */ + public static Ternary of(boolean value) { + return value ? TRUE : FALSE; + } + + /** + * Adapter for {@code boolean} where {@code null} means {@code UNKNOWN}. + */ + public static Ternary ofNullable(Boolean value) { + return value == null ? UNKNOWN : of(value); + } + + /** + * Checks whether the object is exactly {@code TRUE} or {@code FALSE}. + */ + public boolean is(boolean value) { + if (value) { + return this == TRUE; + } else { + return this == FALSE; + } + } + + /** + * The value is known and true. + */ + public boolean isTrue() { + return this == TRUE; + } + + /** + * The value is known and false. + */ + public boolean isFalse() { + return this == FALSE; + } + + public boolean maybeTrue() { + return this == TRUE || this == UNKNOWN; + } + + public boolean maybeFalse() { + return this == FALSE || this == UNKNOWN; + } + + /** + * Negation. Unknown stays unknown, otherwise the usual boolean logic. + */ + public Ternary not() { + return switch (this) { + case TRUE -> FALSE; + case FALSE -> TRUE; + case UNKNOWN -> UNKNOWN; + }; + } + + /** + * Alternative: + *