diff --git a/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/AbstractVerifier.java b/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/AbstractVerifier.java index 1a9c78498d9993907582ca235c0e1c006bf7800d..8e15bc88477306c95eddf6ce17e2c5729c2a4e6e 100644 --- a/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/AbstractVerifier.java +++ b/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/AbstractVerifier.java @@ -10,7 +10,6 @@ import de.unikoblenz.fgbks.dmn.core.verifier.helper.Value; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.Set; import java.util.logging.Logger; import org.camunda.bpm.dmn.engine.DmnDecision; @@ -61,7 +60,7 @@ public abstract class AbstractVerifier { protected boolean checkDifferentConclusion( String decisionKey, List<RuleIdentifier> currentRuleIdentifiers) { - Set<Type> outputCols = + List<Type> outputCols = ruleMap.getAllOutputTypesFromDecisionKey(Objects.requireNonNull(decisionKey)); for (Type outputType : outputCols) { List<Value> values = diff --git a/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/IdenticalRules.java b/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/IdenticalRules.java index 3fb1237d87ad5a821f3796020ad4907d00d2d248..c3abfa77f97d3cb477d4b7ee5bf7a9067551d8dc 100644 --- a/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/IdenticalRules.java +++ b/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/IdenticalRules.java @@ -8,24 +8,17 @@ import de.unikoblenz.fgbks.dmn.core.verifier.helper.Value; import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import java.util.Set; -import java.util.logging.Logger; import java.util.stream.Collectors; import org.camunda.bpm.dmn.engine.DmnDecision; public class IdenticalRules extends AbstractVerifier { - private static final Logger LOGGER = Logger.getLogger(IdenticalRules.class.getSimpleName()); - private Set<Set<String>> identicaldecicionTables; - public IdenticalRules() { super(VerifierType.Identical); } @Override - protected void beforeVerifyDecision() { - identicaldecicionTables = ruleMap.getIncludingInputDefinitions(); - } + protected void beforeVerifyDecision() {} @Override protected void verifyDecision(DmnDecision d) { @@ -50,12 +43,12 @@ public class IdenticalRules extends AbstractVerifier { } else { List<Value> currentBounds = new ArrayList<>(); List<Value> sortedBounds = - new ArrayList<>(ruleMap.getValuesFromInputType(inputs.get(i), currentRuleIdentifiers)); + ruleMap.getValuesFromInputType(inputs.get(i), currentRuleIdentifiers); sortedBounds.sort(Comparator.comparing(Value::getBoundary)); Value lastBound = null; for (Value currentBound : sortedBounds) { if (lastBound != null) { - if (currentBound.getBoundary().isEquals(lastBound.getBoundary())) { + if (currentBound.getBoundary().isEqualsTo(lastBound.getBoundary())) { currentBounds.add(lastBound); } else { if (currentBounds.size() > 0) { diff --git a/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/MissingRules.java b/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/MissingRules.java index ab8380dd306cfa8470adb3217fe6048734cd614c..66fe182a13a3354b57485ce7982f1f96061e7732 100644 --- a/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/MissingRules.java +++ b/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/MissingRules.java @@ -4,13 +4,13 @@ import de.unikoblenz.fgbks.dmn.core.models.RuleIdentifier; import de.unikoblenz.fgbks.dmn.core.models.VerificationResult; import de.unikoblenz.fgbks.dmn.core.models.VerifierType; import de.unikoblenz.fgbks.dmn.core.verifier.helper.Boundary; -import de.unikoblenz.fgbks.dmn.core.verifier.helper.DataType; import de.unikoblenz.fgbks.dmn.core.verifier.helper.Type; import de.unikoblenz.fgbks.dmn.core.verifier.helper.Value; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import org.camunda.bpm.dmn.engine.DmnDecision; @@ -22,24 +22,57 @@ public class MissingRules extends AbstractVerifier { } @Override - protected void beforeVerifyDecision() { - } + protected void beforeVerifyDecision() {} @Override protected void verifyDecision(DmnDecision d) { if (d.isDecisionTable()) { List<Type> inputs = new ArrayList<>(ruleMap.getAllInputTypesFromDecisionKey(d.getKey())); List<Value[]> missingIntervals = new ArrayList<>(); - checkForMissingRules(d, inputs, 0, new Value[inputs.size()], missingIntervals); + checkForMissingRules(d, inputs, 0, new Value[inputs.size()], missingIntervals, false); + // remove duplicate rules + for (int i = 0; i < missingIntervals.size(); i++) { + if (missingIntervals.get(i) != null) { + inner: + for (int u = i + 1; u < missingIntervals.size(); u++) { + if (missingIntervals.get(u) != null) { + boolean isEqual; + for (int x = 0; x < missingIntervals.get(i).length; x++) { + if ((missingIntervals.get(i)[x].getOrgExpression() == null) + ^ (missingIntervals.get(u)[x].getOrgExpression() == null)) { + isEqual = false; + } else { + isEqual = + (missingIntervals.get(i)[x].getOrgExpression() == null + && missingIntervals.get(u)[x].getOrgExpression() == null + || missingIntervals + .get(i)[x] + .getOrgExpression() + .equals(missingIntervals.get(u)[x].getOrgExpression())) + && missingIntervals.get(i)[x].getType() + == missingIntervals.get(u)[x].getType(); + } + if (!isEqual) { + continue inner; + } + } + } + missingIntervals.set(u, null); + } + } + } + // TODO in later state: merge rules -> like partial Reduction // add errors for missing intervals - // TODO: add duplicate rows - for (Value[] missingInt : missingIntervals) { + for (Value[] missingInt : + missingIntervals.stream().filter(Objects::nonNull).collect(Collectors.toList())) { StringBuilder sb = new StringBuilder("In table "); sb.append(d.getName()); // TODO: add real name - sb.append(" following rule is missing: "); - sb.append(Arrays.stream(missingInt) - .map(Value::getOrgExpression) - .collect(Collectors.joining(" / "))); + sb.append(" the following rule is not defined: ("); + sb.append( + Arrays.stream(missingInt) + .map(Value::getOrgExpression) + .collect(Collectors.joining("), ("))); + sb.append(")"); addVerification( VerificationResult.getBuilder() .addRules( @@ -57,82 +90,99 @@ public class MissingRules extends AbstractVerifier { List<Type> inputs, int i, Value[] missingValues, - List<Value[]> missingIntervals) { + List<Value[]> missingIntervals, + boolean hasMissing) { if (i == inputs.size()) { - missingIntervals.add(missingValues.clone()); + if (hasMissing) { + missingIntervals.add(missingValues.clone()); + } } else { - List<Value> sortedBounds = - new ArrayList<>(ruleMap.getValuesFromInputType(inputs.get(i))); + List<Value> sortedBounds = ruleMap.getValuesFromInputType(inputs.get(i)); sortedBounds.sort(Comparator.comparing(Value::getBoundary)); Value lastBound = null; int z = 0; for (Value currentBound : sortedBounds) { + boolean currentHasMissing = false; z++; Optional<Boundary> newBoundary = Optional.empty(); String orgExpr = ""; - if (inputs.get(i).getDataType() == DataType.NUMBER) { + if (inputs.get(i).getDataType().isNumeric()) { if (lastBound == null) { // check current elements if (currentBound.getBoundary().getLowerBound() != Double.MIN_VALUE) { newBoundary = currentBound.getBoundary().getNewBoundaryLower(); } } else { - newBoundary = - lastBound.getBoundary().getNewBoundaryBetween(currentBound.getBoundary()); + newBoundary = lastBound.getBoundary().getNewBoundaryBetween(currentBound.getBoundary()); } if (newBoundary.isPresent()) { orgExpr = newBoundary.get().toString(); + currentHasMissing = true; } - } else { - newBoundary = Optional.of(currentBound.getBoundary()); - orgExpr = currentBound.getOrgExpression(); } if (newBoundary.isPresent()) { - missingValues[i] = new Value(newBoundary.get(), orgExpr, - RuleIdentifier.getBuilder() - .withRowNumber(999) - .withDecision(d) - .withRuleId("newRule") - .build(), - inputs.get(i)); - checkForMissingRules(d, inputs, i + 1, missingValues, missingIntervals); - missingValues[i] = null; - } - - // TODO: check last element - if (z == sortedBounds.size()) { - if (inputs.get(i).getDataType() == DataType.NUMBER) { - if (currentBound.getBoundary().getUpperBound() != Double.MAX_VALUE) { - newBoundary = currentBound.getBoundary().getNewBoundaryUpper(); - orgExpr = newBoundary.get().toString(); - missingValues[i] = new Value(newBoundary.get(), orgExpr, + missingValues[i] = + new Value( + newBoundary.get(), + orgExpr, RuleIdentifier.getBuilder() .withRowNumber(999) .withDecision(d) .withRuleId("newRule") .build(), inputs.get(i)); - checkForMissingRules(d, inputs, i + 1, missingValues, missingIntervals); - missingValues[i] = null; - } - } else { - // not number TODO - } + checkForMissingRules( + d, inputs, i + 1, missingValues, missingIntervals, hasMissing || currentHasMissing); + missingValues[i] = null; } + // check if the current rule is has missing ranges in next columns + missingValues[i] = currentBound; + checkForMissingRules(d, inputs, i + 1, missingValues, missingIntervals, hasMissing); + missingValues[i] = null; + // set next last bound if (lastBound == null || !lastBound - .getBoundary() - .isNumberInRange( - currentBound.getBoundary().getUpperBound(), - currentBound.getBoundary().getUpperBoundType())) { + .getBoundary() + .isNumberInRange( + currentBound.getBoundary().getUpperBound(), + currentBound.getBoundary().getUpperBoundType())) { lastBound = currentBound; } + // check last element in list + newBoundary = Optional.empty(); + if (z == sortedBounds.size() && z != 1) { + if (inputs.get(i).getDataType().isNumeric()) { + if (lastBound.getBoundary().getUpperBound() != Double.MAX_VALUE) { + newBoundary = lastBound.getBoundary().getNewBoundaryUpper(); + if (newBoundary.isPresent()) { + orgExpr = newBoundary.get().toString(); + hasMissing = true; + } + } + } else { + newBoundary = Optional.of(lastBound.getBoundary()); + orgExpr = lastBound.getOrgExpression(); + } + if (newBoundary.isPresent()) { + missingValues[i] = + new Value( + newBoundary.get(), + orgExpr, + RuleIdentifier.getBuilder() + .withRowNumber(999) + .withDecision(d) + .withRuleId("newRule") + .build(), + inputs.get(i)); + checkForMissingRules(d, inputs, i + 1, missingValues, missingIntervals, hasMissing); + missingValues[i] = null; + } + } } } } @Override - protected void afterVerifyDecision() { - } + protected void afterVerifyDecision() {} } diff --git a/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/OverlappingRules.java b/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/OverlappingRules.java index ae7765c740aee4ec7120c230aab73460a8c1dcae..eaced682b1f9fc8b471d7f52b8280977c31e4259 100644 --- a/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/OverlappingRules.java +++ b/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/OverlappingRules.java @@ -6,6 +6,7 @@ import de.unikoblenz.fgbks.dmn.core.models.VerifierType; import de.unikoblenz.fgbks.dmn.core.verifier.helper.Type; import de.unikoblenz.fgbks.dmn.core.verifier.helper.Value; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -24,12 +25,16 @@ public class OverlappingRules extends AbstractVerifier { protected void verifyDecision(DmnDecision d) { if (d.isDecisionTable()) { List<Type> inputs = new ArrayList<>(ruleMap.getAllInputTypesFromDecisionKey(d.getKey())); - checkForOverlappingRules(inputs, 0, null, false); + checkForOverlappingRules(inputs, 0, null, false, Collections.emptyList()); } } private void checkForOverlappingRules( - List<Type> inputs, int i, List<RuleIdentifier> currentRuleIdentifiers, boolean hasOverlap) { + List<Type> inputs, + int i, + List<RuleIdentifier> currentRuleIdentifiers, + boolean hasOverlap, + List<RuleIdentifier> subsubmtionRules) { if (i == inputs.size()) { // do nothing, if there was no real overlap found prev.. if (hasOverlap) { @@ -47,7 +52,7 @@ public class OverlappingRules extends AbstractVerifier { } else { List<Value> currentBounds = new ArrayList<>(); List<Value> sortedBounds = - new ArrayList<>(ruleMap.getValuesFromInputType(inputs.get(i), currentRuleIdentifiers)); + ruleMap.getValuesFromInputType(inputs.get(i), currentRuleIdentifiers); sortedBounds.sort(Comparator.comparing(Value::getBoundary)); int z = 0; boolean foundPOverlap = false; @@ -71,26 +76,71 @@ public class OverlappingRules extends AbstractVerifier { foundOverlap || currentBound.getBoundary().isOverlapping(bound.getBoundary()); } } - if (foundNotInContact) { + if (foundNotInContact && currentBoundsCpy.size() > 1) { + List<RuleIdentifier> newSubsumtionRules = searchSubsubmtionElement(currentBoundsCpy); checkForOverlappingRules( inputs, i + 1, currentBoundsCpy.stream().map(Value::getRuleIdentifier).collect(Collectors.toList()), - hasOverlap || foundPOverlap); + hasOverlap + || foundPOverlap + || (newSubsumtionRules.size() > 0 + && subsubmtionRules.size() > 0 + && containsNoDuplicateRows(newSubsumtionRules, subsubmtionRules)), + newSubsumtionRules); foundPOverlap = false; } if (z == sortedBounds.size() && currentBounds.size() > 1) { + List<RuleIdentifier> newSubsumtionRules = searchSubsubmtionElement(currentBounds); + // check if checkForOverlappingRules( inputs, i + 1, currentBounds.stream().map(Value::getRuleIdentifier).collect(Collectors.toList()), - hasOverlap || foundOverlap); + hasOverlap + || foundOverlap + || (newSubsumtionRules.size() > 0 + && subsubmtionRules.size() > 0 + && containsNoDuplicateRows(newSubsumtionRules, subsubmtionRules)), + newSubsumtionRules); } foundPOverlap |= foundOverlap; } } } + private List<RuleIdentifier> searchSubsubmtionElement(List<Value> values) { + List<RuleIdentifier> subsubmtionRules = new ArrayList<>(); + outer: + for (Value value : values) { + boolean containsSubsumption = false; + for (Value innerValue : values) { + if (value.getBoundary().subsumes(innerValue.getBoundary())) { + containsSubsumption = true; + } else if (value.getBoundary().isNotInContact(innerValue.getBoundary()) + || value.getBoundary().isOverlapping(innerValue.getBoundary())) { + continue outer; + } + } + if (containsSubsumption) { + subsubmtionRules.add(value.getRuleIdentifier()); + } + } + return subsubmtionRules; + } + + private boolean containsNoDuplicateRows(List<RuleIdentifier> ri1, List<RuleIdentifier> ri2) { + for (RuleIdentifier r1 : ri1) { + for (RuleIdentifier r2 : ri2) { + if (r1.getDecisionKey().equals(r2.getDecisionKey()) + && r1.getRowNumber().equals(r2.getRowNumber())) { + return false; + } + } + } + return true; + } + @Override protected void afterVerifyDecision() {} diff --git a/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/PartialReductionRules.java b/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/PartialReductionRules.java index 7bd8b2a34983d1573cbcab7a3b144c3f5e98bc95..15da25ef0e73cb1cb4f95bf416830d55ade54a75 100644 --- a/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/PartialReductionRules.java +++ b/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/PartialReductionRules.java @@ -1,6 +1,14 @@ package de.unikoblenz.fgbks.dmn.core.verifier; +import de.unikoblenz.fgbks.dmn.core.models.RuleIdentifier; +import de.unikoblenz.fgbks.dmn.core.models.VerificationResult; import de.unikoblenz.fgbks.dmn.core.models.VerifierType; +import de.unikoblenz.fgbks.dmn.core.verifier.helper.Type; +import de.unikoblenz.fgbks.dmn.core.verifier.helper.Value; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; import org.camunda.bpm.dmn.engine.DmnDecision; public class PartialReductionRules extends AbstractVerifier { @@ -13,7 +21,140 @@ public class PartialReductionRules extends AbstractVerifier { protected void beforeVerifyDecision() {} @Override - protected void verifyDecision(DmnDecision d) {} + protected void verifyDecision(DmnDecision d) { + if (d.isDecisionTable()) { + List<List<RuleIdentifier>> identicalOutputCluster = findIdenticalOutputs(d); + List<Type> inputs = new ArrayList<>(ruleMap.getAllInputTypesFromDecisionKey(d.getKey())); + identicalOutputCluster + .stream() // do: parallelStream() ? + .forEach(c -> findPartialReduction(d, inputs, 0, c, false)); + } + } + + private List<List<RuleIdentifier>> findIdenticalOutputs(DmnDecision d) { + List<List<RuleIdentifier>> clusters = new ArrayList<>(); + List<Type> outputs = new ArrayList<>(ruleMap.getAllOutputTypesFromDecisionKey(d.getKey())); + + List<RuleIdentifier> rowsIds = + ruleMap + .getValuesFromType(outputs.get(0)) + .stream() + .map(Value::getRuleIdentifier) + .collect(Collectors.toList()); + int rowsCount = ruleMap.getValuesFromType(outputs.get(0)).size(); + for (int i = 0; i < rowsCount; i++) { + List<RuleIdentifier> cluster = new ArrayList<>(); + cluster.add(rowsIds.get(i)); + inner: + for (int u = i + 1; u < rowsCount; u++) { + for (List<RuleIdentifier> clu : clusters) { + if (clu.contains(rowsIds.get(u))) { + continue inner; + } + } + if (checkRulesHasIdenticalOutput(d, rowsIds.get(i), rowsIds.get(u))) { + cluster.add(rowsIds.get(u)); + } + } + if (cluster.size() > 1) { + clusters.add(cluster); + } + } + return clusters; + } + + private boolean checkRulesHasIdenticalOutput( + DmnDecision d, RuleIdentifier r1, RuleIdentifier r2) { + List<Type> outputs = new ArrayList<>(ruleMap.getAllOutputTypesFromDecisionKey(d.getKey())); + for (Type type : outputs) { + Value v1; + Value v2; + try { + v1 = ruleMap.getValuesFromOutputType(type, Collections.singletonList(r1)).get(0); + v2 = ruleMap.getValuesFromOutputType(type, Collections.singletonList(r2)).get(0); + } catch (Exception e) { + return false; + } + if (!v1.getBoundary().isEqualsTo(v2.getBoundary())) { + return false; + } + } + return true; + } + + private void findPartialReduction( + DmnDecision d, + List<Type> inputs, + int i, + List<RuleIdentifier> identicalOutputCluster, + boolean hasCombination) { + if (i == inputs.size()) { + if (hasCombination) { + VerificationResult.Builder vBuilder = + VerificationResult.getBuilder().withMessage(getMessageText(identicalOutputCluster)); + vBuilder.addRules(identicalOutputCluster); + addVerification(vBuilder.build()); + } + } else { + List<Value> rules = ruleMap.getValuesFromInputType(inputs.get(i), identicalOutputCluster); + List<List<RuleIdentifier>> nClusters = new ArrayList<>(); + List<Boolean> combinationCluster = new ArrayList<>(); + for (int i1 = 0; i1 < rules.size(); i1++) { + Value currentRule = rules.get(i1); + // 1. Find combinations + if (!hasCombination) { + for (int i2 = i1 + 1; i2 < rules.size(); i2++) { + Value currentRuleInner = rules.get(i2); + if (currentRule != currentRuleInner) { + if (currentRule + .getBoundary() + .combineWith(currentRuleInner.getBoundary()) + .isPresent()) { + List<RuleIdentifier> cluster = new ArrayList<>(); + cluster.add(currentRule.getRuleIdentifier()); + cluster.add(currentRuleInner.getRuleIdentifier()); + nClusters.add(cluster); + combinationCluster.add(true); + } + } + } + } + // 2. Find equal values + for (int i2 = i1 + 1; i2 < rules.size(); i2++) { + Value currentRuleInner = rules.get(i2); + if (currentRule != currentRuleInner) { + if (currentRule.getBoundary().isEqualsTo(currentRuleInner.getBoundary())) { + List<RuleIdentifier> cluster = new ArrayList<>(); + cluster.add(currentRule.getRuleIdentifier()); + cluster.add(currentRuleInner.getRuleIdentifier()); + nClusters.add(cluster); + combinationCluster.add(false); + } + } + } + } + // nex col + for (int x = 0; x < combinationCluster.size(); x++) { + if (nClusters.get(x).size() > 1) { + findPartialReduction( + d, inputs, i + 1, nClusters.get(x), combinationCluster.get(x) || hasCombination); + } + } + } + } + + private String getMessageText(List<RuleIdentifier> ruleIdentifiers) { + StringBuilder sb = new StringBuilder("Rules "); + sb.append( + ruleIdentifiers + .stream() + .map(c -> c.getRowNumber().toString()) + .collect(Collectors.joining(" and "))); + sb.append(" in table "); + sb.append(ruleIdentifiers.get(0).getTableName()); + sb.append(" can be combined."); + return sb.toString(); + } @Override protected void afterVerifyDecision() {} diff --git a/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/SubsumptionRules.java b/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/SubsumptionRules.java index 57d081c87b580b1de75ac5087453418d3d8696bc..b5f219670da39634f8e39f89aee0869e58d9eeca 100644 --- a/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/SubsumptionRules.java +++ b/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/SubsumptionRules.java @@ -83,7 +83,7 @@ public class SubsumptionRules extends AbstractVerifier { foundSubsumption |= fs; if (fs) { c1.add(cb2); - } else if (nValue.getBoundary().isEquals(cb2.getBoundary())) { + } else if (nValue.getBoundary().isEqualsTo(cb2.getBoundary())) { c1.add(cb2); if (currentRootSubsumptionElements.contains(cb2)) { subsumptionVals.add(cb2); @@ -111,7 +111,7 @@ public class SubsumptionRules extends AbstractVerifier { } List<Value> subsumptionVals = new ArrayList<>(); for (Value cb2 : selectedBounds) { - if (cb1.getBoundary().isEquals(cb2.getBoundary())) { + if (cb1.getBoundary().isEqualsTo(cb2.getBoundary())) { c1.add(cb2); if (foundSubsumption) { subsumptionVals.add(cb2); diff --git a/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/Boundary.java b/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/Boundary.java index 87cd776ff35d6d599e10e4d069c327d4d640a5b9..0644d607af3c06e5531d02ddc7d6305bce8998dd 100644 --- a/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/Boundary.java +++ b/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/Boundary.java @@ -7,7 +7,7 @@ import de.unikoblenz.fgbks.base.builder.DefaultBuilder; import java.util.Objects; import java.util.Optional; -/** Class for manage boundary and intervals. Can also handle String values. */ +/** Class for manage boundaries and intervals. Can also handle String values. */ public class Boundary implements Comparable<Boundary> { public enum BoundType { @@ -27,9 +27,11 @@ public class Boundary implements Comparable<Boundary> { private BoundType lowerBoundType; private double upperBound; private BoundType upperBoundType; + private boolean integerValue; private Boundary() { super(); + this.integerValue = false; } public double getLowerBound() { @@ -64,6 +66,14 @@ public class Boundary implements Comparable<Boundary> { this.upperBoundType = upperBoundType; } + public boolean isIntegerValue() { + return integerValue; + } + + private void setIntegerValue(boolean integerValue) { + this.integerValue = integerValue; + } + /** * Check, if a number is in between the current boundary * @@ -131,7 +141,7 @@ public class Boundary implements Comparable<Boundary> { * @return true or false */ public boolean isOverlapping(Boundary other) { - return !isEquals(other) && !isNotInContact(other) && !isSubsumption(other); + return !isEqualsTo(other) && !isNotInContact(other) && !isSubsumption(other); } /** @@ -314,7 +324,7 @@ public class Boundary implements Comparable<Boundary> { * @param other Boundary to check * @return true or false */ - public boolean isEquals(Boundary other) { + public boolean isEqualsTo(Boundary other) { return equals(other); } @@ -362,7 +372,7 @@ public class Boundary implements Comparable<Boundary> { } /** - * Create an new boundary, witch is lover than the current <br> + * Create an new boundary, which is lover than the current <br> * >10 --> <=10 * * @return An Optional of the Boundary, if the current has a possible boundary, which is lower @@ -377,6 +387,7 @@ public class Boundary implements Comparable<Boundary> { .withLowerBoundType(INCLUSIVE) .withUpperBound(lowerBound) .withUpperBoundType(lowerBoundType.getOp()) + .isInteger(isIntegerValue()) .build()); } @@ -389,9 +400,25 @@ public class Boundary implements Comparable<Boundary> { * there is no interval in between */ public Optional<Boundary> getNewBoundaryBetween(Boundary other) { - if (!isNotInContact(Objects.requireNonNull(other))) { + if (isInContact(Objects.requireNonNull(other))) { return Optional.empty(); } + // not space between the two bounds + if (this.upperBound == other.lowerBound && this.upperBoundType != other.lowerBoundType + || other.upperBound == this.lowerBound && other.upperBoundType != this.lowerBoundType) { + return Optional.empty(); + } + // check if is integer val + if (isIntegerValue()) { + if (this.upperBound + 1 == other.lowerBound + && this.upperBoundType == INCLUSIVE + && other.lowerBoundType == INCLUSIVE + || other.upperBound + 1 == this.lowerBound + && other.upperBoundType == INCLUSIVE + && this.lowerBoundType == INCLUSIVE) { + return Optional.empty(); + } + } if (this.upperBound < other.upperBound) { return Optional.of( getBuilder() @@ -399,6 +426,7 @@ public class Boundary implements Comparable<Boundary> { .withLowerBoundType(this.upperBoundType.getOp()) .withUpperBound(other.lowerBound) .withUpperBoundType(other.lowerBoundType.getOp()) + .isInteger(isIntegerValue()) .build()); } else { return Optional.of( @@ -407,12 +435,13 @@ public class Boundary implements Comparable<Boundary> { .withLowerBoundType(other.upperBoundType.getOp()) .withUpperBound(this.lowerBound) .withUpperBoundType(this.lowerBoundType.getOp()) + .isInteger(isIntegerValue()) .build()); } } /** - * Create a new boundary, witch begins at the upper bound of the original <br> + * Create a new boundary, which begins at the upper bound of the original <br> * <10 --> >=10 * * @return An Optional of the Boundary if the current has a possible boundary, which is higher @@ -427,45 +456,96 @@ public class Boundary implements Comparable<Boundary> { .withLowerBoundType(upperBoundType.getOp()) .withUpperBound(Double.MAX_VALUE) .withUpperBoundType(INCLUSIVE) + .isInteger(isIntegerValue()) .build()); } /** * Create a new Boundary from two given ones. The Optional is empty, if the given two bounds are - * not direct followed by each other. <u>Example:</u><br> + * not direct followed by each other or current in contact<u>Example:</u><br> * <code> - * [10..20] & >20 --> >=10<br> [10..20] & <10 --> <=20<br> [10..20] & <=10 --> empty - * Optional (Boundaries are overlapping)<br> + * [10..20] & >20 --> >=10<br> [10..20] & <10 --> <=20<br> [10..20] & <=0 --> empty Optional + * (Boundaries are not in contact)<br> [10..20] & <=10 --> empty Optional (Boundaries are + * overlapping)<br> * </code> * * @param other the other boundary */ - public Optional<Boundary> combineWith(Boundary other) { + public Optional<Boundary> appendWith(Boundary other) { if (isNotInContact(other)) { - if (this.upperBound == other.lowerBound && this.upperBoundType != other.lowerBoundType) { + if (this.upperBound == other.lowerBound && this.upperBoundType != other.lowerBoundType + || isIntegerValue() + && this.upperBound + 1 == other.lowerBound + && this.upperBoundType == INCLUSIVE + && other.lowerBoundType == INCLUSIVE) { return Optional.of( getBuilder() .withLowerBound(this.lowerBound) .withLowerBoundType(this.lowerBoundType) .withUpperBound(other.upperBound) .withUpperBoundType(other.getUpperBoundType()) + .isInteger(isIntegerValue()) .build()); - } else if (this.lowerBound == other.upperBound - && other.upperBoundType != this.lowerBoundType) { + } else if (this.lowerBound == other.upperBound && other.upperBoundType != this.lowerBoundType + || isIntegerValue() + && other.upperBound + 1 == this.lowerBound + && other.upperBoundType == INCLUSIVE + && this.lowerBoundType == INCLUSIVE) { return Optional.of( getBuilder() .withLowerBound(other.lowerBound) .withLowerBoundType(other.lowerBoundType) .withUpperBound(this.upperBound) .withUpperBoundType(this.getUpperBoundType()) + .isInteger(isIntegerValue()) .build()); - } else { - return Optional.empty(); } } return Optional.empty(); } + /** + * Create a new Boundary from two given ones. The Optional is empty, if the given two bounds are + * not direct followed by each other. They can be in contact <u>Example:</u><br> + * <code> + * [10..20] & >20 --> >=10<br> [10..20] & <10 --> <=20<br> [10..20] & <=0 --> empty Optional + * (Boundaries are not in contact)<br> [10..20] & <=10 --> <=20 <br> + * </code> + * + * @param other the other boundary + */ + public Optional<Boundary> combineWith(Boundary other) { + if (isNotInContact(other)) { + return appendWith(other); + } + Builder builder = getBuilder().isInteger(isIntegerValue()); + if (this.lowerBound == Double.MIN_VALUE || this.lowerBound < other.lowerBound) { + builder.withLowerBound(this.lowerBound).withLowerBoundType(this.lowerBoundType); + } else if (other.lowerBound == Double.MIN_VALUE || other.lowerBound < this.lowerBound) { + builder.withLowerBound(other.lowerBound).withLowerBoundType(other.lowerBoundType); + } else { + builder + .withLowerBound(other.lowerBound) + .withLowerBoundType( + this.lowerBoundType == INCLUSIVE || other.lowerBoundType == INCLUSIVE + ? INCLUSIVE + : EXCLUSIVE); + } + if (this.upperBound == Double.MAX_VALUE || this.upperBound > other.upperBound) { + builder.withUpperBound(this.upperBound).withUpperBoundType(this.upperBoundType); + } else if (other.upperBound == Double.MAX_VALUE || other.upperBound > this.upperBound) { + builder.withUpperBound(other.upperBound).withUpperBoundType(other.upperBoundType); + } else { + builder + .withUpperBound(other.upperBound) + .withUpperBoundType( + this.upperBoundType == INCLUSIVE || other.upperBoundType == INCLUSIVE + ? INCLUSIVE + : EXCLUSIVE); + } + return Optional.of(builder.build()); + } + @Override public int hashCode() { return Objects.hash(lowerBound, lowerBoundType, upperBound, upperBoundType); @@ -484,7 +564,27 @@ public class Boundary implements Comparable<Boundary> { * @return a new Boundary object */ public static Boundary parseFromNumericValue(String expr) { + return parseFromNumericValue(expr, false); + } + + /** + * Parse a String to a boundary object.<br> + * <u>Possible forms:</u><br> + * <code> + * Interval: [x..y] (inclusive x and inclusive y) <br> Interval: ]x..y[ (exclusive x and exclusive + * y) <br> Max Val: <=x (inclusive x) <br> Max Val: <x (exclusive x) <br> Min Val: >=x + * (inclusive x) <br> Min Val: >x (exclusive x) <br>Equal: =x (inclusive x) <br> + * </code> + * + * @param expr String to parse + * @param isIntegerValue true, if the value is a integer Value + * @return a new Boundary object + */ + public static Boundary parseFromNumericValue(String expr, boolean isIntegerValue) { Builder builder = getBuilder(); + if (isIntegerValue) { + builder.isInteger(); + } try { expr = Objects.requireNonNull(expr).trim(); if (expr.startsWith("[") || expr.startsWith("]")) { @@ -561,7 +661,7 @@ public class Boundary implements Comparable<Boundary> { } /** - * Creats a "wildcard" boundary. [-infinity..+infinity] + * Creates a "wildcard" boundary. [-infinity..+infinity] * * @return a boundary */ @@ -586,24 +686,24 @@ public class Boundary implements Comparable<Boundary> { if (getUpperBoundType() == INCLUSIVE) { sb.append("="); } - sb.append(getUpperBound()); + sb.append(getValAsString(getUpperBound())); } else if (getUpperBound() == Double.MAX_VALUE) { sb.append(">"); if (getLowerBoundType() == INCLUSIVE) { sb.append("="); } - sb.append(getLowerBound()); + sb.append(getValAsString(getLowerBound())); } else if (getLowerBound() == getUpperBound()) { - sb.append("=").append(getLowerBound()); + sb.append("=").append(getValAsString(getLowerBound())); } else { if (getLowerBoundType() == INCLUSIVE) { sb.append("["); } else { sb.append("]"); } - sb.append(getLowerBound()); + sb.append(getValAsString(getLowerBound())); sb.append(".."); - sb.append(getUpperBound()); + sb.append(getValAsString(getUpperBound())); if (getUpperBoundType() == INCLUSIVE) { sb.append("]"); } else { @@ -613,6 +713,13 @@ public class Boundary implements Comparable<Boundary> { return sb.toString(); } + private String getValAsString(double value) { + if (isIntegerValue()) { + return String.valueOf((new Double(value)).intValue()); + } + return String.valueOf((new Double(value))); + } + @Override public int compareTo(Boundary o) { if (o == null) { @@ -626,6 +733,12 @@ public class Boundary implements Comparable<Boundary> { } else { return upperBoundType == EXCLUSIVE ? -1 : 1; } + } else { + if (this.upperBound == Double.MAX_VALUE) { + return -1; + } else if (o.upperBound == Double.MAX_VALUE) { + return 1; + } } return Double.compare(this.upperBound, o.upperBound); } else { @@ -634,6 +747,8 @@ public class Boundary implements Comparable<Boundary> { } if (this.lowerBound == Double.MIN_VALUE) { return -1; + } else if (o.lowerBound == Double.MIN_VALUE) { + return 1; } else { return Double.compare(this.lowerBound, o.lowerBound); } @@ -665,6 +780,15 @@ public class Boundary implements Comparable<Boundary> { return this; } + public Builder isInteger() { + return isInteger(true); + } + + public Builder isInteger(boolean val) { + value.setIntegerValue(val); + return this; + } + @Override protected void validate() { if (value.lowerBound == Double.MIN_VALUE) { @@ -681,6 +805,19 @@ public class Boundary implements Comparable<Boundary> { "Lower bound %f is greater than upper bound %f.", value.lowerBound, value.upperBound)); } + // if boundary values are declared as integer, than there should be no not int bounds + if (value.isIntegerValue()) { + if (value.lowerBound != Double.MIN_VALUE + && value.lowerBound != Math.floor(value.lowerBound)) { + throw new IllegalArgumentException( + String.format("Lower bound %f is no integer/long value.", value.lowerBound)); + } + if (value.upperBound != Double.MAX_VALUE + && value.upperBound != Math.floor(value.upperBound)) { + throw new IllegalArgumentException( + String.format("Upper bound %f is no integer/long value.", value.upperBound)); + } + } } } } diff --git a/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/DataType.java b/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/DataType.java index d88ec668c6c0b954872b89cde88a9ac9d80174a1..bbeb1c014e8b3d581abb58574d012fd9c4aab3d5 100644 --- a/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/DataType.java +++ b/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/DataType.java @@ -1,18 +1,33 @@ package de.unikoblenz.fgbks.dmn.core.verifier.helper; +import org.camunda.bpm.dmn.engine.impl.DmnExpressionImpl; + public enum DataType { - NUMBER, - BOOLEAN, - STRING, - DATE, - UNKNOWN; + FLOATING(true), + INTEGER(true), + BOOLEAN(false), + STRING(false), + DATE(false), + UNKNOWN(false); + + private boolean numeric = false; + + private DataType(boolean numeric) { + this.numeric = numeric; + } + + public boolean isNumeric() { + return numeric; + } public static DataType getTypeFromString(String type) { switch (type) { case "integer": case "long": + return DataType.INTEGER; case "double": - return DataType.NUMBER; + case "float": + return DataType.FLOATING; case "string": return DataType.STRING; // case "date": @@ -24,4 +39,25 @@ public enum DataType { return DataType.UNKNOWN; } } + + public Boundary getBoundaryValue(DmnExpressionImpl value) { + if (value.getExpression() == null) { + return Boundary.parseBoundaryFromNullValue(); + } + switch (this) { + case INTEGER: + return Boundary.parseFromNumericValue(value.getExpression(), true); + case FLOATING: + return Boundary.parseFromNumericValue(value.getExpression()); + case BOOLEAN: + return Boundary.parseFromBooleanValue(Boolean.valueOf(value.getExpression())); + case STRING: + return Boundary.parseFromStringValue(value.getExpression()); + case DATE: // not implemented yet + case UNKNOWN: // not implemented yet + default: + throw new IllegalStateException( + String.format("Parsing boundary of value %s failed.", value.getExpression())); + } + } } diff --git a/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/RuleMap.java b/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/RuleMap.java index fab7017f5aebc1a952d11df50827a98473b19b3d..2e59a6b53df7d3b9ea7dd6adf260a3549ec9b340 100644 --- a/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/RuleMap.java +++ b/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/RuleMap.java @@ -25,13 +25,17 @@ public class RuleMap { private static final Logger LOGGER = Logger.getLogger(RuleMap.class.getSimpleName()); private List<DmnDecision> dmnDecisions; private Map<InputType, List<Value>> inputs; + private List<InputType> inputColumns; private Map<OutputType, List<Value>> outputs; + private List<OutputType> outputColumns; private Set<String> dmnDecisionsKeys; private RuleMap(List<DmnDecision> dmnDecisions) { this.dmnDecisions = new ArrayList<>(dmnDecisions); this.inputs = new HashMap<>(); + this.inputColumns = new ArrayList<>(); this.outputs = new HashMap<>(); + this.outputColumns = new ArrayList<>(); this.dmnDecisionsKeys = new HashSet<>(); } @@ -51,6 +55,7 @@ public class RuleMap { int i = 0; for (DmnDecisionTableInputImpl tableInput : table.getInputs()) { InputType it = new InputType(d, tableInput); + ruleMap.inputColumns.add(it); ruleMap.inputs.put(it, new ArrayList<>()); List<Value> inList = ruleMap.inputs.get(it); int r = 0; @@ -64,6 +69,7 @@ public class RuleMap { i = 0; for (DmnDecisionTableOutputImpl tableOutput : table.getOutputs()) { OutputType ot = new OutputType(d, tableOutput); + ruleMap.outputColumns.add(ot); ruleMap.outputs.put(ot, new ArrayList<>()); List<Value> outList = ruleMap.outputs.get(ot); int r = 0; @@ -191,41 +197,39 @@ public class RuleMap { } /** - * Return a Set of input types, witch belongs to a given decisionKey + * Return a list of input types, which belongs to a given decisionKey * * @param decisionKey the decisionKey - * @return a set of Input types + * @return a list of Input types */ - public Set<Type> getAllInputTypesFromDecisionKey(String decisionKey) { - return inputs - .keySet() + public List<Type> getAllInputTypesFromDecisionKey(String decisionKey) { + return inputColumns .stream() .filter(i -> i.getDecisionKey().equals(Objects.requireNonNull(decisionKey))) - .collect(Collectors.toSet()); + .collect(Collectors.toList()); } /** - * Return a Set of output types, witch belongs to a given decisionKey + * Return a list of output types, which belongs to a given decisionKey * * @param decisionKey the decisionKey - * @return a set of Output types + * @return a list of Output types */ - public Set<Type> getAllOutputTypesFromDecisionKey(String decisionKey) { - return outputs - .keySet() + public List<Type> getAllOutputTypesFromDecisionKey(String decisionKey) { + return outputColumns .stream() .filter(i -> i.getDecisionKey().equals(decisionKey)) - .collect(Collectors.toSet()); + .collect(Collectors.toList()); } /** - * Return a Set of input and output types, witch belongs to a given decisionKey + * Return a Set of input and output types, which belongs to a given decisionKey * * @param decisionKey the decisionKey * @return a set of output and input types */ - public Set<Type> getAllTypesFromDecisionKey(String decisionKey) { - Set<Type> types = getAllInputTypesFromDecisionKey(decisionKey); + public List<Type> getAllTypesFromDecisionKey(String decisionKey) { + List<Type> types = getAllInputTypesFromDecisionKey(decisionKey); types.addAll(getAllOutputTypesFromDecisionKey(decisionKey)); return types; } @@ -234,9 +238,9 @@ public class RuleMap { Set<Set<String>> returnSet = new HashSet<>(); for (String d1 : dmnDecisionsKeys) { Set<String> subset = new HashSet<>(); - Set<Type> inputsD1 = getAllInputTypesFromDecisionKey(d1); + List<Type> inputsD1 = getAllInputTypesFromDecisionKey(d1); for (String d2 : dmnDecisionsKeys) { - Set<Type> inputsD2 = getAllInputTypesFromDecisionKey(d2); + List<Type> inputsD2 = getAllInputTypesFromDecisionKey(d2); boolean containsAll = true; for (Type i1 : inputsD1) { boolean f2 = false; diff --git a/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/Type.java b/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/Type.java index 475923744f48295eb5d7dd829df4a15bf9b0156e..0f32a066f4607aeb83e7c6ad795ea12457c0303d 100644 --- a/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/Type.java +++ b/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/Type.java @@ -38,7 +38,7 @@ public abstract class Type { public Value getValue( DmnExpressionImpl value, DmnDecision decision, int rowNumber, String ruleId) { return new Value( - getBoundaryValue(value), + dataType.getBoundaryValue(value), value.getExpression(), RuleIdentifier.getBuilder() .withDecisionName(decision.getName()) @@ -47,28 +47,8 @@ public abstract class Type { .withRuleId(ruleId) .withConditionId(value.getId()) .withConditionName(value.getName()) - .build(), this); - } - - private Boundary getBoundaryValue(DmnExpressionImpl value) { - if (value.getExpression() == null) { - return Boundary.parseBoundaryFromNullValue(); - } - switch (this.dataType) { - case NUMBER: - return Boundary.parseFromNumericValue(value.getExpression()); - case BOOLEAN: - return Boundary.parseFromBooleanValue(Boolean.valueOf(value.getExpression())); - case STRING: - return Boundary.parseFromStringValue(value.getExpression()); - case UNKNOWN: - case DATE: - throw new IllegalStateException( - String.format( - "Parsing boundary in table %s failed. Value: %s", - decisionKey, value.getExpression())); - } - return null; + .build(), + this); } public boolean isEqualExpression(Type type) { diff --git a/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/Value.java b/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/Value.java index dd4743fdb9d2b15ea1e51afd479677f6c955c11a..7024772760a93297e283a6ff0cd32ce20911b3af 100644 --- a/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/Value.java +++ b/dmn-verifier-app/src/main/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/Value.java @@ -54,6 +54,4 @@ public class Value { public String toString() { return orgExpression; } - - } diff --git a/dmn-verifier-app/src/main/resources/sampleDMN.dmn b/dmn-verifier-app/src/main/resources/sampleDMN.dmn index 9cc3fcae8809648e026c69926ee409b0e9f2d23f..9b2993093bb416eb6668c7d8a5eba0e63db7be80 100644 --- a/dmn-verifier-app/src/main/resources/sampleDMN.dmn +++ b/dmn-verifier-app/src/main/resources/sampleDMN.dmn @@ -2,7 +2,7 @@ <definitions xmlns="http://www.omg.org/spec/DMN/20151101/dmn.xsd" xmlns:biodi="http://bpmn.io/schema/dmn/biodi/1.0" id="Definitions_1kjh9a2" name="DRD" namespace="http://camunda.org/schema/1.0/dmn"> <decision id="Decision_13nychf" name="Identical"> <extensionElements> - <biodi:bounds x="127" y="130" width="180" height="80" /> + <biodi:bounds x="87" y="130" width="180" height="80" /> </extensionElements> <decisionTable id="decisionTable_1"> <input id="input_1"> @@ -32,7 +32,7 @@ <text>=10</text> </inputEntry> <inputEntry id="UnaryTests_01s94by"> - <text>"Anna"</text> + <text>"Nicole"</text> </inputEntry> <outputEntry id="LiteralExpression_06akk3i"> <text></text> @@ -75,7 +75,7 @@ </decision> <decision id="Decision_1vo386g" name="Subsumption 1"> <extensionElements> - <biodi:bounds x="341" y="12" width="180" height="80" /> + <biodi:bounds x="286" y="41" width="180" height="80" /> </extensionElements> <decisionTable id="DecisionTable_0tq1rr0"> <input id="InputClause_0f5fjmn"> @@ -118,7 +118,7 @@ </decision> <decision id="Decision_19xi89b" name="Subsumption 2"> <extensionElements> - <biodi:bounds x="344" y="129" width="180" height="80" /> + <biodi:bounds x="284" y="129" width="180" height="80" /> </extensionElements> <decisionTable id="DecisionTable_0p3gy3p"> <input id="InputClause_095mouf"> @@ -200,7 +200,7 @@ </decision> <decision id="Decision_1883dha" name="Subsumption 3"> <extensionElements> - <biodi:bounds x="345" y="239" width="180" height="80" /> + <biodi:bounds x="284" y="221" width="180" height="80" /> </extensionElements> <decisionTable id="DecisionTable_07e2svi"> <input id="InputClause_15byi73"> @@ -249,7 +249,7 @@ </decision> <decision id="Decision_1be8xb9" name="Equivalent"> <extensionElements> - <biodi:bounds x="561" y="131" width="180" height="80" /> + <biodi:bounds x="482" y="130" width="180" height="80" /> </extensionElements> <decisionTable id="DecisionTable_1xebbjr"> <input id="InputClause_0628qmg"> @@ -316,7 +316,7 @@ </decision> <decision id="Decision_0daxsth" name="Overlapping 1"> <extensionElements> - <biodi:bounds x="768" y="132" width="180" height="80" /> + <biodi:bounds x="671" y="129" width="180" height="80" /> </extensionElements> <decisionTable id="DecisionTable_0q2corq"> <input id="InputClause_0l9m2o0"> @@ -365,7 +365,7 @@ </decision> <decision id="Decision_1iijfl3" name="Overlapping 2"> <extensionElements> - <biodi:bounds x="768" y="227" width="180" height="80" /> + <biodi:bounds x="672" y="217" width="180" height="80" /> </extensionElements> <decisionTable id="DecisionTable_0400f42"> <input id="InputClause_0qut3rd"> @@ -412,4 +412,140 @@ </rule> </decisionTable> </decision> + <decision id="Decision_1fm0nxn" name="Missing"> + <extensionElements> + <biodi:bounds x="861" y="128" width="180" height="80" /> + </extensionElements> + <decisionTable id="DecisionTable_1tqc0kn"> + <input id="InputClause_14alfam"> + <inputExpression id="LiteralExpression_0x3uzxr" typeRef="integer" /> + </input> + <output id="OutputClause_0trtb1o" typeRef="string" /> + <rule id="DecisionRule_0q3it0q"> + <inputEntry id="UnaryTests_1ymjfs8"> + <text><0</text> + </inputEntry> + <outputEntry id="LiteralExpression_0tko1l8"> + <text></text> + </outputEntry> + </rule> + <rule id="DecisionRule_1111n0y"> + <inputEntry id="UnaryTests_1lmu41i"> + <text>[10..20]</text> + </inputEntry> + <outputEntry id="LiteralExpression_1mmduh8"> + <text></text> + </outputEntry> + </rule> + <rule id="DecisionRule_0xq3fwu"> + <inputEntry id="UnaryTests_10ua1fp"> + <text>]30..40[</text> + </inputEntry> + <outputEntry id="LiteralExpression_1gzkysr"> + <text></text> + </outputEntry> + </rule> + <rule id="DecisionRule_04u8jhd"> + <inputEntry id="UnaryTests_0lqhve2"> + <text>>100</text> + </inputEntry> + <outputEntry id="LiteralExpression_0cyhmak"> + <text></text> + </outputEntry> + </rule> + </decisionTable> + </decision> + <decision id="Decision_0m4xor4" name="Partial Reduction"> + <extensionElements> + <biodi:bounds x="1049" y="129" width="180" height="80" /> + </extensionElements> + <decisionTable id="DecisionTable_0vu5ntx"> + <input id="InputClause_0s7lk3g"> + <inputExpression id="LiteralExpression_1oyp20m" typeRef="integer" /> + </input> + <input id="InputClause_1h59ox1"> + <inputExpression id="LiteralExpression_0ukhram" typeRef="integer"> + <text></text> + </inputExpression> + </input> + <output id="OutputClause_09bwosh" typeRef="string" /> + <rule id="DecisionRule_1de20wv"> + <inputEntry id="UnaryTests_1a7rjth"> + <text><0</text> + </inputEntry> + <inputEntry id="UnaryTests_0ebttad"> + <text></text> + </inputEntry> + <outputEntry id="LiteralExpression_1wxutjs"> + <text>"a"</text> + </outputEntry> + </rule> + <rule id="DecisionRule_0w7ah79"> + <inputEntry id="UnaryTests_0qnlz66"> + <text>>=0</text> + </inputEntry> + <inputEntry id="UnaryTests_04m969i"> + <text></text> + </inputEntry> + <outputEntry id="LiteralExpression_0vta1px"> + <text>"a"</text> + </outputEntry> + </rule> + <rule id="DecisionRule_0umjei3"> + <inputEntry id="UnaryTests_1meeo0u"> + <text>[10..20[</text> + </inputEntry> + <inputEntry id="UnaryTests_1x99g0s"> + <text></text> + </inputEntry> + <outputEntry id="LiteralExpression_1wp9w6k"> + <text>"b"</text> + </outputEntry> + </rule> + <rule id="DecisionRule_1imlwrg"> + <inputEntry id="UnaryTests_07tm2f5"> + <text>[20..30]</text> + </inputEntry> + <inputEntry id="UnaryTests_14aedcb"> + <text></text> + </inputEntry> + <outputEntry id="LiteralExpression_1ye3xxm"> + <text>"b"</text> + </outputEntry> + </rule> + <rule id="DecisionRule_1js6kse"> + <inputEntry id="UnaryTests_15t1l7k"> + <text>[0..10]</text> + </inputEntry> + <inputEntry id="UnaryTests_15mk7ui"> + <text>=10</text> + </inputEntry> + <outputEntry id="LiteralExpression_1uda4py"> + <text>"c"</text> + </outputEntry> + </rule> + <rule id="DecisionRule_1284m78"> + <inputEntry id="UnaryTests_065f45f"> + <text>>10</text> + </inputEntry> + <inputEntry id="UnaryTests_0tfuv1t"> + <text>=20</text> + </inputEntry> + <outputEntry id="LiteralExpression_16tu1vb"> + <text>"c"</text> + </outputEntry> + </rule> + <rule id="DecisionRule_1magf9v"> + <inputEntry id="UnaryTests_0io8iob"> + <text>]10..20]</text> + </inputEntry> + <inputEntry id="UnaryTests_1l9a1l7"> + <text>=10</text> + </inputEntry> + <outputEntry id="LiteralExpression_0to63dq"> + <text>"c"</text> + </outputEntry> + </rule> + </decisionTable> + </decision> </definitions> diff --git a/dmn-verifier-app/src/test/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/BoundaryTest.java b/dmn-verifier-app/src/test/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/BoundaryTest.java index 432e08cc8f25ed8851a3bfba6f683e24d4da21ed..473266cf89a3b49c3f6b27501d30b0c2007ff969 100644 --- a/dmn-verifier-app/src/test/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/BoundaryTest.java +++ b/dmn-verifier-app/src/test/java/de/unikoblenz/fgbks/dmn/core/verifier/helper/BoundaryTest.java @@ -236,6 +236,20 @@ class BoundaryTest { b2 = Boundary.parseBoundaryFromNullValue(); assertFalse(b1.isNotInContact(b2)); assertFalse(b2.isNotInContact(b1)); + + b1 = Boundary.parseFromNumericValue("<=0", true); + b2 = Boundary.parseFromNumericValue(">=0", true); + assertTrue(b1.isInContact(b2)); + assertTrue(b2.isInContact(b1)); + assertFalse(b1.isNotInContact(b2)); + assertFalse(b2.isNotInContact(b1)); + + b1 = Boundary.parseFromNumericValue("<0", true); + b2 = Boundary.parseFromNumericValue(">0", true); + assertFalse(b1.isInContact(b2)); + assertFalse(b2.isInContact(b1)); + assertTrue(b1.isNotInContact(b2)); + assertTrue(b2.isNotInContact(b1)); } @Test @@ -622,37 +636,94 @@ class BoundaryTest { Boundary b1 = Boundary.parseFromNumericValue("=10"); Boundary b2 = Boundary.parseFromNumericValue("<10"); Boundary b3 = Boundary.parseFromNumericValue("<=10"); - assertEquals(b3, b1.combineWith(b2).get()); - assertEquals(b3, b2.combineWith(b1).get()); + assertEquals(b3, b1.appendWith(b2).get()); + assertEquals(b3, b2.appendWith(b1).get()); b1 = Boundary.parseFromNumericValue("=10"); b2 = Boundary.parseFromNumericValue(">10"); b3 = Boundary.parseFromNumericValue(">=10"); - assertEquals(b3, b1.combineWith(b2).get()); - assertEquals(b3, b2.combineWith(b1).get()); + assertEquals(b3, b1.appendWith(b2).get()); + assertEquals(b3, b2.appendWith(b1).get()); b1 = Boundary.parseFromNumericValue("[0..10]"); b2 = Boundary.parseFromNumericValue(">10"); b3 = Boundary.parseFromNumericValue(">=0"); - assertEquals(b3, b1.combineWith(b2).get()); - assertEquals(b3, b2.combineWith(b1).get()); + assertEquals(b3, b1.appendWith(b2).get()); + assertEquals(b3, b2.appendWith(b1).get()); b1 = Boundary.parseFromNumericValue("[0..10]"); b2 = Boundary.parseFromNumericValue("<0"); b3 = Boundary.parseFromNumericValue("<=10"); - assertEquals(b3, b1.combineWith(b2).get()); - assertEquals(b3, b2.combineWith(b1).get()); + assertEquals(b3, b1.appendWith(b2).get()); + assertEquals(b3, b2.appendWith(b1).get()); b1 = Boundary.parseFromNumericValue("]0..10["); b2 = Boundary.parseFromNumericValue(">=10"); b3 = Boundary.parseFromNumericValue(">0"); - assertEquals(b3, b1.combineWith(b2).get()); - assertEquals(b3, b2.combineWith(b1).get()); + assertEquals(b3, b1.appendWith(b2).get()); + assertEquals(b3, b2.appendWith(b1).get()); b1 = Boundary.parseFromNumericValue("[0..10["); b2 = Boundary.parseFromNumericValue("<0"); b3 = Boundary.parseFromNumericValue("<10"); - assertEquals(b3, b1.combineWith(b2).get()); - assertEquals(b3, b2.combineWith(b1).get()); + assertEquals(b3, b1.appendWith(b2).get()); + assertEquals(b3, b2.appendWith(b1).get()); + } + + @Test + void testIntegerVals() { + Boundary b1 = Boundary.parseFromNumericValue("<0", true); + Boundary b2 = Boundary.parseFromNumericValue("[5..10[", true); + Boundary b3 = Boundary.parseFromNumericValue(">10", true); + + Boundary betw = Boundary.parseFromNumericValue("[0..5[", true); + assertEquals(betw, b1.getNewBoundaryBetween(b2).get()); + assertEquals(betw, b2.getNewBoundaryBetween(b1).get()); + + b1 = Boundary.parseFromNumericValue("]10..20]", true); + b2 = Boundary.parseFromNumericValue("[5..10[", true); + betw = Boundary.parseFromNumericValue("=10", true); + assertEquals(betw, b1.getNewBoundaryBetween(b2).get()); + assertEquals(betw, b2.getNewBoundaryBetween(b1).get()); + + b1 = Boundary.parseFromNumericValue(">100", true); + b2 = Boundary.parseFromNumericValue("[0..99[", true); + betw = Boundary.parseFromNumericValue("[99..100]", true); + assertEquals(betw, b1.getNewBoundaryBetween(b2).get()); + assertEquals(betw, b2.getNewBoundaryBetween(b1).get()); + + b1 = Boundary.parseFromNumericValue(">=100", true); + b2 = Boundary.parseFromNumericValue("[0..99]", true); + assertFalse(b1.getNewBoundaryBetween(b2).isPresent()); + assertFalse(b2.getNewBoundaryBetween(b1).isPresent()); + + b1 = Boundary.parseFromNumericValue("[0..5[", true); + b2 = Boundary.parseFromNumericValue("[5..10[", true); + assertFalse(b1.getNewBoundaryBetween(b2).isPresent()); + assertFalse(b2.getNewBoundaryBetween(b1).isPresent()); + + b1 = Boundary.parseFromNumericValue("=10", true); + b2 = Boundary.parseFromNumericValue("=11", true); + Boundary comb = Boundary.parseFromNumericValue("[10..11]", true); + assertEquals(comb, b1.appendWith(b2).get()); + assertEquals(comb, b2.appendWith(b1).get()); + + b1 = Boundary.parseFromNumericValue("[0..10]", true); + b2 = Boundary.parseFromNumericValue("=11", true); + comb = Boundary.parseFromNumericValue("[0..11]", true); + assertEquals(comb, b1.appendWith(b2).get()); + assertEquals(comb, b2.appendWith(b1).get()); + + b1 = Boundary.parseFromNumericValue("=10", true); + b2 = Boundary.parseFromNumericValue("[11..20]", true); + comb = Boundary.parseFromNumericValue("[10..20]", true); + assertEquals(comb, b1.appendWith(b2).get()); + assertEquals(comb, b2.appendWith(b1).get()); + + b1 = Boundary.parseFromNumericValue("=10", true); + b2 = Boundary.parseFromNumericValue("]10..20]", true); + comb = Boundary.parseFromNumericValue("[10..20]", true); + assertEquals(comb, b1.appendWith(b2).get()); + assertEquals(comb, b2.appendWith(b1).get()); } }