red6/dmn-check

View on GitHub
validators/src/main/java/de/redsix/dmncheck/feel/Subsumption.java

Summary

Maintainability
A
0 mins
Test Coverage
package de.redsix.dmncheck.feel;

import java.util.Optional;
import java.util.function.BiPredicate;
import java.util.function.Function;

final class Subsumption {

    private interface Comparison<A extends Comparable> extends BiPredicate<A, A> {}

    static final Comparison<?> eq = (a, b) -> a.compareTo(b) == 0;
    private static final Comparison<?> nq = (a, b) -> a.compareTo(b) != 0;
    private static final Comparison<?> gt = (a, b) -> a.compareTo(b) > 0;
    private static final Comparison<?> lt = (a, b) -> a.compareTo(b) < 0;
    private static final Comparison<?> ge = (a, b) -> a.compareTo(b) == 0 || a.compareTo(b) > 0;
    private static final Comparison<?> le = (a, b) -> a.compareTo(b) == 0 || a.compareTo(b) < 0;

    static Optional<Boolean> subsumes(
            final FeelExpression expression, final FeelExpression otherExpression, final Comparison comparison) {
        return FeelExpressions.caseOf(expression)
                .Empty_(Optional.of(true))
                .Null_(FeelExpressions.caseOf(otherExpression)
                        .Null_(Optional.of(true))
                        .otherwise_(Optional.of(false)))
                .BooleanLiteral(
                        (aBool) -> compareLiterals(aBool, FeelExpressions::getABoolean, otherExpression, comparison))
                .DateLiteral((dateTime) ->
                        compareLiterals(dateTime, FeelExpressions::getDateTime, otherExpression, comparison))
                .DoubleLiteral(
                        (aDouble) -> compareLiterals(aDouble, FeelExpressions::getADouble, otherExpression, comparison))
                .IntegerLiteral((integer) ->
                        compareLiterals(integer, FeelExpressions::getAInteger, otherExpression, comparison))
                .StringLiteral(
                        (string) -> compareLiterals(string, FeelExpressions::getString, otherExpression, comparison))
                .VariableLiteral((name) -> subsumesVariableLiteral(name, otherExpression, comparison))
                .RangeExpression((leftInc, lowerBound, upperBound, rightInc) ->
                        subsumesRangeExpression(leftInc, lowerBound, upperBound, rightInc, otherExpression))
                .UnaryExpression((operator, operand) -> subsumesUnaryExpression(operator, operand, otherExpression))
                .otherwise_(Optional.empty());
    }

    private static Optional<Boolean> subsumesVariableLiteral(
            String name, FeelExpression otherExpression, Comparison comparison) {
        return FeelExpressions.caseOf(otherExpression)
                .VariableLiteral(otherName -> Optional.of(comparison.test(name, otherName)))
                .UnaryExpression(((operator, operand) ->
                        Operator.NOT.equals(operator) ? subsumesVariableLiteral(name, operand, nq) : Optional.of(true)))
                .otherwise_(Optional.of(true));
    }

    private static Optional<Boolean> subsumesUnaryExpression(
            Operator operator, FeelExpression operand, FeelExpression otherExpression) {
        return FeelExpressions.caseOf(otherExpression)
                .RangeExpression((leftInc, lowerBound, upperBound, rightInc) -> switch (operator) {
                    case LT -> subsumes(upperBound, operand, rightInc ? lt : le);
                    case GT -> subsumes(operand, lowerBound, leftInc ? lt : le);
                    case LE -> subsumes(upperBound, operand, rightInc ? le : lt);
                    case GE -> subsumes(operand, lowerBound, leftInc ? le : lt);
                    default -> Optional.of(false);
                })
                .UnaryExpression((otherOperator, otherOperand) -> {
                    if (operator.equals(otherOperator) && operand.equals(otherOperand)) {
                        return Optional.of(true);
                    }

                    if (operator.isGreaterThan() && otherOperator.isGreaterThan()
                            || operator.isLessThan() && otherOperator.isLessThan()) {
                        return subsumes(otherOperand, operand, fromOperator(operator));
                    } else {
                        return Optional.of(false);
                    }
                })
                .otherwise(() -> {
                    if (operator.equals(Operator.NOT) && otherExpression.isLiteral()) {
                        return subsumes(operand, otherExpression, nq);
                    } else {
                        return Optional.of(false);
                    }
                });
    }

    private static Optional<Boolean> subsumesRangeExpression(
            boolean leftInc,
            FeelExpression lowerBound,
            FeelExpression upperBound,
            boolean rightInc,
            FeelExpression otherExpression) {
        return FeelExpressions.caseOf(otherExpression)
                .RangeExpression((otherLeftInc, otherLowerBound, otherUpperBound, otherRightInc) -> subsumes(
                                lowerBound, otherLowerBound, leftInc ? le : lt)
                        .flatMap(subsumesLowerBound -> subsumes(otherUpperBound, upperBound, rightInc ? le : lt)
                                .flatMap(subsumesUpperBound -> Optional.of(subsumesLowerBound && subsumesUpperBound))))
                .otherwise(() -> {
                    if (otherExpression.isLiteral()) {
                        return subsumes(lowerBound, otherExpression, leftInc ? le : lt)
                                .flatMap(subsumedByLowerBound -> subsumes(
                                                upperBound, otherExpression, rightInc ? ge : gt)
                                        .flatMap(subsumedByUpperBound ->
                                                Optional.of(subsumedByLowerBound && subsumedByUpperBound)));
                    } else {
                        return Optional.of(false);
                    }
                });
    }

    private static <R extends Comparable> Optional<Boolean> compareLiterals(
            R value,
            Function<FeelExpression, Optional<R>> extractOtherValue,
            FeelExpression otherExpression,
            Comparison comparison) {
        return Optional.of(extractOtherValue
                .apply(otherExpression)
                .map(otherValue -> comparison.test(value, otherValue))
                .orElse(false));
    }

    private static Comparison fromOperator(final Operator operator) {
        return switch (operator) {
            case LE -> le;
            case LT -> lt;
            case GT -> gt;
            case GE -> ge;
            default -> eq;
        };
    }
}