org.kie.dmn.validation.dtanalysis.DMNDTAnalyser Maven / Gradle / Ivy
The newest version!
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.kie.dmn.validation.dtanalysis;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.xml.namespace.QName;
import org.kie.dmn.api.core.DMNModel;
import org.kie.dmn.core.ast.DMNBaseNode;
import org.kie.dmn.core.compiler.DMNProfile;
import org.kie.dmn.core.impl.DMNModelImpl;
import org.kie.dmn.core.util.Msg;
import org.kie.dmn.core.util.MsgUtil;
import org.kie.dmn.core.util.NamespaceUtil;
import org.kie.dmn.feel.codegen.feel11.ProcessedExpression;
import org.kie.dmn.feel.codegen.feel11.ProcessedUnaryTest;
import org.kie.dmn.feel.lang.CompilerContext;
import org.kie.dmn.feel.lang.SimpleType;
import org.kie.dmn.feel.lang.ast.BaseNode;
import org.kie.dmn.feel.lang.ast.DashNode;
import org.kie.dmn.feel.lang.ast.InfixOpNode;
import org.kie.dmn.feel.lang.ast.InfixOperator;
import org.kie.dmn.feel.lang.ast.NameRefNode;
import org.kie.dmn.feel.lang.ast.NullNode;
import org.kie.dmn.feel.lang.ast.RangeNode;
import org.kie.dmn.feel.lang.ast.RangeNode.IntervalBoundary;
import org.kie.dmn.feel.lang.ast.UnaryTestListNode;
import org.kie.dmn.feel.lang.ast.UnaryTestNode;
import org.kie.dmn.feel.lang.ast.UnaryTestNode.UnaryOperator;
import org.kie.dmn.feel.lang.ast.Visitor;
import org.kie.dmn.feel.lang.impl.FEELBuilder;
import org.kie.dmn.feel.lang.impl.InterpretedExecutableExpression;
import org.kie.dmn.feel.lang.impl.UnaryTestInterpretedExecutableExpression;
import org.kie.dmn.feel.lang.types.BuiltInType;
import org.kie.dmn.feel.runtime.Range.RangeBoundary;
import org.kie.dmn.model.api.BusinessKnowledgeModel;
import org.kie.dmn.model.api.DMNModelInstrumentedBase;
import org.kie.dmn.model.api.Decision;
import org.kie.dmn.model.api.DecisionRule;
import org.kie.dmn.model.api.DecisionTable;
import org.kie.dmn.model.api.HitPolicy;
import org.kie.dmn.model.api.InputClause;
import org.kie.dmn.model.api.ItemDefinition;
import org.kie.dmn.model.api.LiteralExpression;
import org.kie.dmn.model.api.OutputClause;
import org.kie.dmn.model.api.UnaryTests;
import org.kie.dmn.validation.DMNValidator;
import org.kie.dmn.validation.DMNValidator.Validation;
import org.kie.dmn.validation.dtanalysis.DMNDTAnalyserValueFromNodeVisitor.DMNDTAnalyserOutputClauseVisitor;
import org.kie.dmn.validation.dtanalysis.DMNDTAnalyserValueFromNodeVisitor.SupportedConstantValueVisitor;
import org.kie.dmn.validation.dtanalysis.mcdc.MCDCAnalyser;
import org.kie.dmn.validation.dtanalysis.mcdc.MCDCAnalyser.PosNegBlock;
import org.kie.dmn.validation.dtanalysis.model.Bound;
import org.kie.dmn.validation.dtanalysis.model.BoundValueComparator;
import org.kie.dmn.validation.dtanalysis.model.DDTAInputClause;
import org.kie.dmn.validation.dtanalysis.model.DDTAInputEntry;
import org.kie.dmn.validation.dtanalysis.model.DDTAOutputClause;
import org.kie.dmn.validation.dtanalysis.model.DDTARule;
import org.kie.dmn.validation.dtanalysis.model.DDTATable;
import org.kie.dmn.validation.dtanalysis.model.DTAnalysis;
import org.kie.dmn.validation.dtanalysis.model.Hyperrectangle;
import org.kie.dmn.validation.dtanalysis.model.Interval;
import org.kie.dmn.validation.dtanalysis.model.NullBoundImpl;
import org.kie.dmn.validation.dtanalysis.model.Overlap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DMNDTAnalyser implements InternalDMNDTAnalyser {
private static final Logger LOG = LoggerFactory.getLogger(DMNDTAnalyser.class);
private final org.kie.dmn.feel.FEEL FEEL;
private final DMNDTAnalyserValueFromNodeVisitor valueFromNodeVisitor;
private final DMNDTAnalyserOutputClauseVisitor outputClauseVisitor;
public DMNDTAnalyser(List dmnProfiles) {
FEEL = FEELBuilder.builder().withProfiles((List) dmnProfiles).build();
valueFromNodeVisitor = new DMNDTAnalyserValueFromNodeVisitor((List) dmnProfiles);
outputClauseVisitor = new DMNDTAnalyserOutputClauseVisitor((List) dmnProfiles);
}
@Override
public List analyse(DMNModel model, Set flags) {
if (!flags.contains(Validation.ANALYZE_DECISION_TABLE)) {
throw new IllegalArgumentException();
}
List results = new ArrayList<>();
List extends DecisionTable> decisionTables = model.getDefinitions().findAllChildren(DecisionTable.class);
for (DecisionTable dt : decisionTables) {
try {
DTAnalysis result = dmnDTAnalysis(model, dt, flags);
results.add(result);
} catch (Throwable t) {
LOG.debug("Skipped dmnDTAnalysis for table: {}", dt.getId(), t);
DTAnalysis result = DTAnalysis.ofError(dt, t);
results.add(result);
}
}
return results;
}
private DTAnalysis dmnDTAnalysis(DMNModel model, DecisionTable dt, Set flags) {
LOG.debug("Starting analsysis for DT with id: {}", dt.getId());
DDTATable ddtaTable = new DDTATable();
compileTableInputClauses(model, dt, ddtaTable);
compileTableOutputClauses(model, dt, ddtaTable);
compileTableRules(model, dt, ddtaTable);
compileTableComputeColStringMissingEnum(model, dt, ddtaTable);
printDebugTableInfo(ddtaTable);
DTAnalysis analysis = new DTAnalysis(dt, ddtaTable);
analysis.computeOutputInLOV();
if (!dt.getHitPolicy().equals(HitPolicy.COLLECT)) {
if (ddtaTable.getColIDsStringWithoutEnum().isEmpty()) {
LOG.debug("findGaps");
findGaps(analysis, ddtaTable, 0, new Interval[ddtaTable.inputCols()], Collections.emptyList());
} else {
LOG.debug("findGaps Skipped because getColIDsStringWithoutEnum is not empty: {}", ddtaTable.getColIDsStringWithoutEnum());
}
LOG.debug("findOverlaps");
findOverlaps(analysis, ddtaTable, 0, new Interval[ddtaTable.inputCols()], Collections.emptyList());
} else {
LOG.debug("findGaps(), findOverlaps() are Skipped because getHitPolicy is COLLECT.");
}
LOG.debug("computeMaskedRules");
analysis.computeMaskedRules();
LOG.debug("computeMisleadingRules");
analysis.computeMisleadingRules();
LOG.debug("normalize");
analysis.normalize();
LOG.debug("computeSubsumptions");
analysis.computeSubsumptions();
LOG.debug("computeContractions");
analysis.computeContractions();
LOG.debug("compute1stNFViolations");
analysis.compute1stNFViolations();
LOG.debug("compute2ndNFViolations");
analysis.compute2ndNFViolations();
LOG.debug("computeHitPolicyRecommender");
analysis.computeHitPolicyRecommender();
if (flags.contains(Validation.COMPUTE_DECISION_TABLE_MCDC)) {
LOG.debug("mcdc.");
List selectedBlocks = new MCDCAnalyser(ddtaTable, dt).compute();
analysis.setMCDCSelectedBlocks(selectedBlocks);
}
LOG.debug("Finished analsysis for DT with id: {}", dt.getId());
return analysis;
}
private void compileTableComputeColStringMissingEnum(DMNModel model, DecisionTable dt, DDTATable ddtaTable) {
for (int iColIdx = 0; iColIdx < ddtaTable.inputCols(); iColIdx++) {
InputClause ie = dt.getInput().get(iColIdx);
QName typeRef = NamespaceUtil.getNamespaceAndName(dt, ((DMNModelImpl) model).getImportAliasesForNS(), ie.getInputExpression().getTypeRef(), model.getNamespace());
if (SimpleType.STRING.equals(typeRef.getLocalPart()) && !ddtaTable.getInputs().get(iColIdx).isDiscreteDomain()) {
Interval infStringDomain = ddtaTable.getInputs().get(iColIdx).getDomainMinMax();
boolean areAllSinglePointOrAll = true;
for (int jRowIdx = 0; jRowIdx < dt.getRule().size() && areAllSinglePointOrAll; jRowIdx++) {
DDTAInputEntry colRowInputEntry = ddtaTable.getRule().get(jRowIdx).getInputEntry().get(iColIdx);
if (colRowInputEntry.isAllSingularities()) {
LOG.debug("col {} row {} are all singularities, assuming positive `areAllSinglePointOrAll`={} and continue. {}", iColIdx, jRowIdx, areAllSinglePointOrAll, colRowInputEntry.getUts());
} else {
for (Interval interval : colRowInputEntry.getIntervals()) {
areAllSinglePointOrAll = areAllSinglePointOrAll && infStringDomain.equals(interval);
}
}
}
if (areAllSinglePointOrAll) {
ddtaTable.addColIdStringWithoutEnum(iColIdx + 1);
}
}
}
}
private void printDebugTableInfo(DDTATable ddtaTable) {
if (LOG.isDebugEnabled()) {
LOG.debug("{}", ddtaTable);
LOG.debug("project on columns.");
for (int colIdx = 0; colIdx < ddtaTable.inputCols(); colIdx++) {
LOG.debug("colIdx " + colIdx);
List intervals = ddtaTable.projectOnColumnIdx(colIdx);
LOG.debug("{}", intervals);
List bounds = intervals.stream().flatMap(i -> Stream.of(i.getLowerBound(), i.getUpperBound())).collect(Collectors.toList());
LOG.debug("{}", bounds);
Collections.sort(bounds);
LOG.debug("{}", bounds);
}
LOG.debug("col IDs being String without Enum: {}", ddtaTable.getColIDsStringWithoutEnum());
}
}
private void compileTableRules(DMNModel model, DecisionTable dt, DDTATable ddtaTable) {
for (int jRowIdx = 0; jRowIdx < dt.getRule().size(); jRowIdx++) {
DecisionRule r = dt.getRule().get(jRowIdx);
DDTARule ddtaRule = new DDTARule();
int jColIdx = 0;
for (UnaryTests ie : r.getInputEntry()) {
ProcessedUnaryTest compileUnaryTests = (ProcessedUnaryTest) FEEL.processUnaryTests(ie.getText(), feelCtx(model, dt));
UnaryTestInterpretedExecutableExpression interpreted = compileUnaryTests.getInterpreted();
if (interpreted == UnaryTestInterpretedExecutableExpression.EMPTY) {
throw new DMNDTAnalysisException("Gaps/Overlaps analysis cannot be performed for InputEntry with unary test containing: " + ie.getText(), dt);
}
UnaryTestListNode utln = (UnaryTestListNode) interpreted.getASTNode();
verifyUtln(utln, dt);
DDTAInputClause ddtaInputClause = ddtaTable.getInputs().get(jColIdx);
ToIntervals toIntervals = toIntervals(utln.getElements(), utln.isNegated(), ddtaInputClause.getDomainMinMax(), ddtaInputClause.getDiscreteValues(), jRowIdx + 1, jColIdx + 1);
DDTAInputEntry ddtaInputEntry = new DDTAInputEntry(utln.getElements(), toIntervals.intervals, toIntervals.allSingularities);
for (Interval interval : ddtaInputEntry.getIntervals()) {
Interval domainMinMax = ddtaTable.getInputs().get(jColIdx).getDomainMinMax();
if (!domainMinMax.includes(interval)) {
throw new IllegalStateException(MsgUtil.createMessage(Msg.DTANALYSIS_ERROR_RULE_OUTSIDE_DOMAIN, jRowIdx + 1, interval, domainMinMax, jColIdx + 1));
}
}
ddtaRule.getInputEntry().add(ddtaInputEntry);
jColIdx++;
}
for (LiteralExpression oe : r.getOutputEntry()) {
ProcessedExpression compile = (ProcessedExpression) FEEL.compile(oe.getText(), feelCtx(model, dt));
InterpretedExecutableExpression interpreted = compile.getInterpreted();
BaseNode outputEntryNode = (BaseNode) interpreted.getASTNode();
Comparable> value = valueFromNode(outputEntryNode, outputClauseVisitor);
ddtaRule.getOutputEntry().add(value);
jColIdx++;
}
ddtaTable.addRule(ddtaRule);
}
}
/**
* Internal method to check for common mistakes when writing FEEL unary test in decision tables.
* @param dt
*/
private void verifyUtln(UnaryTestListNode utln, DecisionTable dt) {
for (BaseNode ut : utln.getElements()) {
if (ut instanceof UnaryTestNode) {
UnaryTestNode utn = (UnaryTestNode) ut;
if (utn.getValue() instanceof RangeNode && ( utn.getOperator() == UnaryOperator.IN || utn.getOperator() == UnaryOperator.EQ )) {
RangeNode rangeNode = (RangeNode) utn.getValue();
Optional diamond = checkForDiamondRange(rangeNode);
if (diamond.isPresent()) { // unrecognized '<> value' FEEL unary test
throw new DMNDTAnalysisException("Unrecognized unary test: '" + ut.getText() + "'; did you meant to write 'not("+ diamond.get().getText() +")' instead?", dt);
}
} else if (utn.getOperator() == UnaryOperator.NE) { // unrecognized '!= value' FEEL unary test
throw new DMNDTAnalysisException("Unrecognized unary test: '" + ut.getText() + "'; did you meant to write 'not("+ utn.getValue().getText() +")' instead?", dt);
} else if (utn.getOperator() == UnaryOperator.TEST && utn.getValue() instanceof InfixOpNode) {
InfixOpNode infixOpNode = (InfixOpNode) utn.getValue();
if (infixOpNode.getOperator() != InfixOperator.NE) {
continue;
}
boolean leftIsQmark = infixOpNode.getLeft() instanceof NameRefNode && infixOpNode.getLeft().getText().equals("?");
SupportedConstantValueVisitor constantVisitor = new DMNDTAnalyserValueFromNodeVisitor.SupportedConstantValueVisitor();
boolean rightIsConstant = infixOpNode.getRight().accept(constantVisitor);
if (leftIsQmark && rightIsConstant) { // unmanaged '? != value' FEEL extended unary test
throw new DMNDTAnalysisException("Unmanaged unary test: '" + ut.getText() + "'; you could write 'not("+ infixOpNode.getRight().getText() +")' instead.", dt);
}
}
}
}
}
private Optional checkForDiamondRange(RangeNode rangeNode) {
if ((rangeNode.getStart() instanceof NullNode || rangeNode.getStart() == null) && rangeNode.getUpperBound() == IntervalBoundary.OPEN && rangeNode.getEnd() instanceof RangeNode) {
return Optional.ofNullable(((RangeNode) rangeNode.getEnd()).getStart()); // <> value
} else if ((rangeNode.getEnd() instanceof NullNode || rangeNode.getEnd() == null) && rangeNode.getLowerBound() == IntervalBoundary.OPEN && rangeNode.getStart() instanceof RangeNode) {
return Optional.ofNullable(((RangeNode) rangeNode.getStart()).getEnd()); // >< value
} else {
return Optional.empty();
}
}
/**
* Builds a feel context containing the named keys for the DRG node dependencies.
* This helps to detect when a Unary test contains symbols (named reference) and therefore static analysis is not supported (ref DROOLS-4607)
*/
private CompilerContext feelCtx(DMNModel model, DecisionTable dt) {
CompilerContext feelCtx = FEEL.newCompilerContext();
DMNModelInstrumentedBase parentDRGelement = dt.getParentDRDElement();
DMNBaseNode parentNode = null;
if (parentDRGelement instanceof Decision) {
Decision decision = (Decision) parentDRGelement;
parentNode = (DMNBaseNode) model.getDecisionByName(decision.getName());
} else if (parentDRGelement instanceof BusinessKnowledgeModel) {
BusinessKnowledgeModel bkm = (BusinessKnowledgeModel) parentDRGelement;
parentNode = (DMNBaseNode) model.getBusinessKnowledgeModelByName(bkm.getName());
}
if (parentNode != null) {
parentNode.getDependencies().keySet().forEach(k -> feelCtx.addInputVariableType(k, BuiltInType.UNKNOWN));
}
return feelCtx;
}
private void compileTableInputClauses(DMNModel model, DecisionTable dt, DDTATable ddtaTable) {
for (int jColIdx = 0; jColIdx < dt.getInput().size(); jColIdx++) {
InputClause ie = dt.getInput().get(jColIdx);
Interval infDomain = new Interval(RangeBoundary.CLOSED, Interval.NEG_INF, Interval.POS_INF, RangeBoundary.CLOSED, 0, jColIdx + 1);
String allowedValues;
if (ie.getInputValues() != null) {
allowedValues = ie.getInputValues().getText();
} else {
QName typeRef = NamespaceUtil.getNamespaceAndName(dt, ((DMNModelImpl) model).getImportAliasesForNS(), ie.getInputExpression().getTypeRef(), model.getNamespace());
allowedValues = findAllowedValues(model, typeRef);
}
if (allowedValues != null) {
ProcessedUnaryTest compileUnaryTests = (ProcessedUnaryTest) FEEL.processUnaryTests(allowedValues, FEEL.newCompilerContext());
UnaryTestInterpretedExecutableExpression interpreted = compileUnaryTests.getInterpreted();
UnaryTestListNode utln = (UnaryTestListNode) interpreted.getASTNode();
List utlnElements = new ArrayList<>(utln.getElements());
boolean allowNull = removeEQNullUnaryTest(utlnElements);
if (utlnElements.size() != 1) {
verifyUnaryTestsAllEQ(utlnElements, dt);
List> discreteValues = getDiscreteValues(utlnElements);
List> inputOrder = Collections.unmodifiableList(new ArrayList<>(discreteValues));
Collections.sort((List) discreteValues);
Interval discreteDomainMinMax = new Interval(RangeBoundary.CLOSED, discreteValues.get(0), discreteValues.get(discreteValues.size() - 1), RangeBoundary.CLOSED, 0, jColIdx + 1);
DDTAInputClause ic = new DDTAInputClause(discreteDomainMinMax, allowNull, discreteValues, inputOrder);
ddtaTable.getInputs().add(ic);
} else if (utlnElements.size() == 1) {
UnaryTestNode utn0 = (UnaryTestNode) utlnElements.get(0);
Interval interval = utnToInterval(utn0, infDomain, null, 0, jColIdx + 1);
DDTAInputClause ic = new DDTAInputClause(interval, allowNull);
ddtaTable.getInputs().add(ic);
} else {
throw new IllegalStateException("inputValues not null but utln: " + utln);
}
} else {
DDTAInputClause ic = new DDTAInputClause(infDomain, false);
ddtaTable.getInputs().add(ic);
}
}
}
private boolean removeEQNullUnaryTest(List utlnElements) {
boolean found = false;
ListIterator it = utlnElements.listIterator();
while (it.hasNext()) {
BaseNode cur = it.next();
if (cur instanceof UnaryTestNode) {
UnaryTestNode utn = (UnaryTestNode) cur;
if (utn.getOperator() == UnaryOperator.EQ && utn.getValue() instanceof NullNode) {
it.remove();
found = true;
}
}
}
return found;
}
private void compileTableOutputClauses(DMNModel model, DecisionTable dt, DDTATable ddtaTable) {
for (int jColIdx = 0; jColIdx < dt.getOutput().size(); jColIdx++) {
OutputClause oe = dt.getOutput().get(jColIdx);
Interval infDomain = new Interval(RangeBoundary.CLOSED, Interval.NEG_INF, Interval.POS_INF, RangeBoundary.CLOSED, 0, jColIdx + 1);
String allowedValues = null;
if (oe.getOutputValues() != null) {
allowedValues = oe.getOutputValues().getText();
} else {
QName outputTypeRef = (oe.getTypeRef() == null && dt.getOutput().size() == 1) ? dt.getTypeRef() : oe.getTypeRef();
if (outputTypeRef != null) {
QName typeRef = NamespaceUtil.getNamespaceAndName(dt, ((DMNModelImpl) model).getImportAliasesForNS(), outputTypeRef, model.getNamespace());
allowedValues = findAllowedValues(model, typeRef);
}
}
if (allowedValues != null) {
ProcessedUnaryTest compileUnaryTests = (ProcessedUnaryTest) FEEL.processUnaryTests(allowedValues, FEEL.newCompilerContext());
UnaryTestInterpretedExecutableExpression interpreted = compileUnaryTests.getInterpreted();
UnaryTestListNode utln = (UnaryTestListNode) interpreted.getASTNode();
List utlnElements = new ArrayList<>(utln.getElements());
boolean allowNull = removeEQNullUnaryTest(utlnElements);
if (utlnElements.size() != 1) {
verifyUnaryTestsAllEQ(utlnElements, dt);
List> discreteValues = getDiscreteValues(utlnElements);
List> outputOrder = Collections.unmodifiableList(new ArrayList<>(discreteValues));
Collections.sort((List) discreteValues);
Interval discreteDomainMinMax = new Interval(RangeBoundary.CLOSED, discreteValues.get(0), discreteValues.get(discreteValues.size() - 1), RangeBoundary.CLOSED, 0, jColIdx + 1);
DDTAOutputClause ic = new DDTAOutputClause(discreteDomainMinMax, discreteValues, outputOrder);
ddtaTable.getOutputs().add(ic);
} else if (utlnElements.size() == 1) {
UnaryTestNode utn0 = (UnaryTestNode) utlnElements.get(0);
Interval interval = utnToInterval(utn0, infDomain, null, 0, jColIdx + 1);
DDTAOutputClause ic = new DDTAOutputClause(interval);
ddtaTable.getOutputs().add(ic);
} else {
throw new IllegalStateException("inputValues not null but utln: " + utln);
}
} else {
DDTAOutputClause ic = new DDTAOutputClause(infDomain);
ddtaTable.getOutputs().add(ic);
}
}
}
private void verifyUnaryTestsAllEQ(List utlnElements, DecisionTable dt) {
if (!utlnElements.stream().allMatch(e -> e instanceof UnaryTestNode && ((UnaryTestNode) e).getOperator() == UnaryOperator.EQ)) {
throw new DMNDTAnalysisException("Multiple constraint on column: " + utlnElements, dt);
}
}
/**
* Transform a UnaryTestListNode's elements into a List of discrete values for input/output clause enumeration
*/
private List> getDiscreteValues(List utlnElements) {
List> discreteValues = new ArrayList<>();
for (BaseNode e : utlnElements) {
BaseNode value = ((UnaryTestNode) e).getValue();
if (!(value instanceof NullNode)) { // to retrieve value from input/output clause enumeration, null is ignored.
Comparable> v = valueFromNode(value);
discreteValues.add(v);
}
}
return discreteValues;
}
private String findAllowedValues(DMNModel model, QName typeRef) {
if (typeRef.getNamespaceURI().equals(model.getNamespace())) {
Optional opt = model.getDefinitions().getItemDefinition().stream().filter(id -> id.getName().equals(typeRef.getLocalPart())).findFirst();
if (opt.isPresent()) {
ItemDefinition id = opt.get();
if (id.getAllowedValues() != null) {
return id.getAllowedValues().getText();
}
} else {
throw new IllegalStateException("Unable to locate typeRef " + typeRef + " to determine domain.");
}
} else if (typeRef.getNamespaceURI().equals(model.getDefinitions().getURIFEEL()) && typeRef.getLocalPart().equals("boolean")) {
return "false, true";
}
List childModels = ((DMNModelImpl) model).getImportChainDirectChildModels();
return childModels.stream()
.map(childModel -> findAllowedValues(childModel, typeRef))
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
}
private void findOverlaps(DTAnalysis analysis, DDTATable ddtaTable, int jColIdx, Interval[] currentIntervals, Collection activeRules) {
LOG.debug("findOverlaps jColIdx {}, currentIntervals {}, activeRules {}", jColIdx, currentIntervals, activeRules);
if (jColIdx < ddtaTable.inputCols()) {
List bounds = findBoundsSorted(ddtaTable, jColIdx, activeRules);
List activeIntervals = new ArrayList<>();
Bound> lastBound = null;
for (Bound> currentBound : bounds) {
if (lastBound == null) {
lastBound = currentBound;
}
LOG.debug("lastBound {} currentBound {} activeIntervals {} == rules {}", lastBound, currentBound, activeIntervals, activeIntervalsToRules(activeIntervals));
if (activeIntervals.size() > 1 && canBeNewCurrInterval(lastBound, currentBound)) {
Interval analysisInterval = new Interval(lastBound.isUpperBound() ? Interval.invertBoundary(lastBound.getBoundaryType()) : lastBound.getBoundaryType(),
lastBound.getValue(),
currentBound.getValue(),
currentBound.isLowerBound() ? Interval.invertBoundary(currentBound.getBoundaryType()) : currentBound.getBoundaryType(),
0, 0);
currentIntervals[jColIdx] = analysisInterval;
findOverlaps(analysis, ddtaTable, jColIdx + 1, currentIntervals, activeIntervalsToRules(activeIntervals));
}
if (currentBound.isLowerBound()) {
activeIntervals.add(currentBound.getParent());
} else {
activeIntervals.remove(currentBound.getParent());
}
lastBound = currentBound;
}
currentIntervals[jColIdx] = null; // facilitate debugging.
} else if (jColIdx == ddtaTable.inputCols()) {
if (activeRules.size() > 1) {
Hyperrectangle overlap = new Hyperrectangle(ddtaTable.inputCols(), Arrays.asList(currentIntervals));
LOG.debug("OVERLAP DETECTED {}", overlap);
analysis.addOverlap(new Overlap(activeRules, overlap));
}
} else {
throw new IllegalStateException();
}
LOG.debug(".");
}
private static void findGaps(DTAnalysis analysis, DDTATable ddtaTable, int jColIdx, Interval[] currentIntervals, Collection activeRules) {
LOG.debug("findGaps jColIdx {}, currentIntervals {}, activeRules {}", jColIdx, currentIntervals, activeRules);
if (jColIdx < ddtaTable.inputCols()) {
findBoundsSorted(ddtaTable, jColIdx, activeRules);
List bounds = findBoundsSorted(ddtaTable, jColIdx, activeRules);
Interval domainRange = ddtaTable.getInputs().get(jColIdx).getDomainMinMax();
// from domain start to the 1st bound
if (!domainRange.getLowerBound().equals(bounds.get(0))) {
currentIntervals[jColIdx] = lastDimensionUncoveredInterval(domainRange.getLowerBound(), bounds.get(0), domainRange);
Hyperrectangle gap = new Hyperrectangle(ddtaTable.inputCols(), buildEdgesForHyperrectangleFromIntervals(currentIntervals, jColIdx));
analysis.addGap(gap);
LOG.debug("STARTLEFT GAP DETECTED {}", gap);
}
// cycle rule's interval bounds
List activeIntervals = new ArrayList<>();
Bound> lastBound = NullBoundImpl.NULL;
for (Bound> currentBound : bounds) {
LOG.debug("lastBound {} currentBound {} activeIntervals {} == rules {}", lastBound, currentBound, activeIntervals, activeIntervalsToRules(activeIntervals));
if (activeIntervals.isEmpty() && lastBound != NullBoundImpl.NULL && !Bound.adOrOver(lastBound, currentBound)) {
currentIntervals[jColIdx] = lastDimensionUncoveredInterval(lastBound, currentBound, domainRange);
Hyperrectangle gap = new Hyperrectangle(ddtaTable.inputCols(), buildEdgesForHyperrectangleFromIntervals(currentIntervals, jColIdx));
LOG.debug("GAP DETECTED {}", gap);
analysis.addGap(gap);
}
if (!activeIntervals.isEmpty() && canBeNewCurrInterval(lastBound, currentBound)) {
Interval missingInterval = new Interval(lastBound.isUpperBound() ? Interval.invertBoundary(lastBound.getBoundaryType()) : lastBound.getBoundaryType(),
lastBound.getValue(),
currentBound.getValue(),
currentBound.isLowerBound() ? Interval.invertBoundary(currentBound.getBoundaryType()) : currentBound.getBoundaryType(),
0, 0);
currentIntervals[jColIdx] = missingInterval;
findGaps(analysis, ddtaTable, jColIdx + 1, currentIntervals, activeIntervalsToRules(activeIntervals));
}
if (currentBound.isLowerBound()) {
activeIntervals.add(currentBound.getParent());
} else {
activeIntervals.remove(currentBound.getParent());
}
lastBound = currentBound;
}
// from last Nth bound, to domain end.
if (!lastBound.equals(domainRange.getUpperBound())) {
currentIntervals[jColIdx] = lastDimensionUncoveredInterval(lastBound, domainRange.getUpperBound(), domainRange);
Hyperrectangle gap = new Hyperrectangle(ddtaTable.inputCols(), buildEdgesForHyperrectangleFromIntervals(currentIntervals, jColIdx));
LOG.debug("ENDRIGHT GAP DETECTED {}", gap);
analysis.addGap(gap);
}
currentIntervals[jColIdx] = null; // facilitate debugging.
}
LOG.debug(".");
}
private static List findBoundsSorted(DDTATable ddtaTable, int jColIdx, Collection activeRules) {
List intervals = ddtaTable.projectOnColumnIdx(jColIdx);
if (!activeRules.isEmpty()) {
intervals = intervals.stream().filter(i -> activeRules.contains(i.getRule())).collect(Collectors.toList());
}
LOG.debug("intervals {}", intervals);
List bounds = intervals.stream().flatMap(i -> Stream.of(i.getLowerBound(), i.getUpperBound())).collect(Collectors.toList());
Collections.sort(bounds);
LOG.debug("bounds (sorted) {}", bounds);
return bounds;
}
private static List buildEdgesForHyperrectangleFromIntervals(Interval[] currentIntervals, int intervalsIndex) {
List edges = new ArrayList<>();
for (int p = 0; p <= intervalsIndex; p++) {
edges.add(currentIntervals[p]);
}
return edges;
}
private static Collection activeIntervalsToRules(List activeIntervals) {
return activeIntervals.stream().map(Interval::getRule).collect(Collectors.toList());
}
/**
* Avoid a situation to "open" a new currentInterval for pair of same-side equals bounds like: x], x]
*/
private static boolean canBeNewCurrInterval(Bound> lastBound, Bound> currentBound) {
int vCompare = BoundValueComparator.compareValueDispatchingToInf(lastBound, currentBound);
if (vCompare != 0) {
return true;
} else {
if (lastBound.isLowerBound() && currentBound.isUpperBound()) {
return true;
} else if (lastBound.isUpperBound() && lastBound.getBoundaryType() == RangeBoundary.OPEN
&& currentBound.isLowerBound() && currentBound.getBoundaryType() == RangeBoundary.OPEN) {
return true; // the case x) (x
} else {
return false;
}
}
}
private static Interval lastDimensionUncoveredInterval(Bound> l, Bound> r, Interval domain) {
boolean isLmin = l.isLowerBound() && l.equals(domain.getLowerBound());
boolean isRmax = r.isUpperBound() && r.equals(domain.getUpperBound());
return new Interval(isLmin ? domain.getLowerBound().getBoundaryType() : Interval.invertBoundary(l.getBoundaryType()),
l.getValue(),
r.getValue(),
isRmax ? domain.getUpperBound().getBoundaryType() : Interval.invertBoundary(r.getBoundaryType()),
0, 0);
}
private ToIntervals toIntervals(List elements, boolean isNegated, Interval minMax, List discreteValues, int rule, int col) {
if (elements.size() == 1 && elements.get(0) instanceof UnaryTestNode && ((UnaryTestNode) elements.get(0)).getValue() instanceof NullNode && !isNegated) {
return new ToIntervals(Collections.emptyList(), false);
}
if (discreteValues != null && !discreteValues.isEmpty() && areAllEQUnaryTest(elements) && elements.size() > 1) {
return toIntervalsEQUnaryTests(elements, isNegated, discreteValues, rule, col);
} else {
List results = new ArrayList<>();
for (BaseNode n : elements) {
if (n instanceof DashNode) {
results.add(new Interval(minMax.getLowerBound().getBoundaryType(),
minMax.getLowerBound().getValue(),
minMax.getUpperBound().getValue(),
minMax.getUpperBound().getBoundaryType(),
rule,
col));
} else if (n instanceof UnaryTestNode) {
UnaryTestNode ut = (UnaryTestNode) n;
if (ut.getValue() instanceof NullNode && isNegated) {
// If there is a not(null), it covers the whole domain, so it can be immediately returned.
return new ToIntervals(
Collections.singletonList(
new Interval(minMax.getLowerBound().getBoundaryType(),
minMax.getLowerBound().getValue(),
minMax.getUpperBound().getValue(),
minMax.getUpperBound().getBoundaryType(),
rule,
col)),
false);
} else {
Interval interval = utnToInterval(ut, minMax, discreteValues, rule, col);
results.add(interval);
}
}
}
final boolean allSingularities = results.stream().allMatch(Interval::isSingularity); // intentionally record singularities before negating / not()
if (isNegated) {
return new ToIntervals(Interval.invertOverDomain(results, minMax), allSingularities);
}
return new ToIntervals(results, allSingularities);
}
}
private ToIntervals toIntervalsEQUnaryTests(List elements, boolean isNegated, final List discreteValues, int rule, int col) {
List results = new ArrayList<>();
int bitsetLogicalSize = discreteValues.size(); // JDK BitSet size will always be larger.
BitSet hitValues = new BitSet(bitsetLogicalSize);
for (BaseNode n : elements) {
Comparable> thisValue = valueFromNode(((UnaryTestNode) n).getValue());
int indexOf = discreteValues.indexOf(thisValue);
if (indexOf < 0) {
throw new IllegalStateException("Unable to determine discreteValue index for: " + n);
}
hitValues.set(indexOf);
}
if (isNegated) {
hitValues.flip(0, bitsetLogicalSize);
}
int lowerBoundIdx = -1;
int upperBoundIdx = -1;
for (int i = 0; i < hitValues.length(); i++) {
boolean curValue = hitValues.get(i);
if (curValue) {
if (lowerBoundIdx < 0) {
lowerBoundIdx = i;
upperBoundIdx = i;
} else {
upperBoundIdx = i;
}
} else {
if (lowerBoundIdx >= 0) {
results.add(createIntervalOfRule(discreteValues, rule, col, lowerBoundIdx, upperBoundIdx));
lowerBoundIdx = -1;
upperBoundIdx = -1;
}
}
}
if (lowerBoundIdx >= 0) {
results.add(createIntervalOfRule(discreteValues, rule, col, lowerBoundIdx, upperBoundIdx));
}
final boolean allSingularities = results.stream().allMatch(Interval::isSingularity);
return new ToIntervals(results, allSingularities);
}
private static class ToIntervals {
public final List intervals;
public final boolean allSingularities;
public ToIntervals(List intervals, boolean allSingularities) {
this.intervals = intervals;
this.allSingularities = allSingularities;
}
}
private static Interval createIntervalOfRule(List discreteValues, int rule, int col, int lowerBoundIdx, int upperBoundIdx) {
Comparable> lowValue = (Comparable>) discreteValues.get(lowerBoundIdx);
Comparable> highValue = (Comparable>) discreteValues.get(upperBoundIdx);
if (upperBoundIdx + 1 == discreteValues.size()) {
return new Interval(RangeBoundary.CLOSED, lowValue, highValue, RangeBoundary.CLOSED, rule, col);
} else {
return new Interval(RangeBoundary.CLOSED, lowValue, (Comparable>) discreteValues.get(upperBoundIdx + 1), RangeBoundary.OPEN, rule, col);
}
}
private static boolean areAllEQUnaryTest(List elements) {
try {
boolean result = true;
for (BaseNode n : elements) {
result = result && ((UnaryTestNode) n).getOperator() == UnaryOperator.EQ;
}
return result;
} catch (Throwable e) {
return false;
}
}
private Interval utnToInterval(UnaryTestNode ut, Interval minMax, List discreteValues, int rule, int col) {
if (ut.getOperator() == UnaryOperator.EQ) {
if (discreteValues == null || discreteValues.isEmpty()) {
return new Interval(RangeBoundary.CLOSED, valueFromNode(ut.getValue()), valueFromNode(ut.getValue()), RangeBoundary.CLOSED, rule, col);
} else {
Comparable> thisValue = valueFromNode(ut.getValue());
int indexOf = discreteValues.indexOf(thisValue);
if (indexOf < 0) {
throw new IllegalStateException("Unable to determine discreteValue index for: " + ut);
}
if (indexOf + 1 == discreteValues.size()) {
return new Interval(RangeBoundary.CLOSED, thisValue, thisValue, RangeBoundary.CLOSED, rule, col);
}
return new Interval(RangeBoundary.CLOSED, thisValue, (Comparable>) discreteValues.get(indexOf + 1), RangeBoundary.OPEN, rule, col);
}
} else if (ut.getOperator() == UnaryOperator.LTE) {
return new Interval(minMax.getLowerBound().getBoundaryType(), minMax.getLowerBound().getValue(), valueFromNode(ut.getValue()), RangeBoundary.CLOSED, rule, col);
} else if (ut.getOperator() == UnaryOperator.LT) {
return new Interval(minMax.getLowerBound().getBoundaryType(), minMax.getLowerBound().getValue(), valueFromNode(ut.getValue()), RangeBoundary.OPEN, rule, col);
} else if (ut.getOperator() == UnaryOperator.GT) {
return new Interval(RangeBoundary.OPEN, valueFromNode(ut.getValue()), minMax.getUpperBound().getValue(), minMax.getUpperBound().getBoundaryType(), rule, col);
} else if (ut.getOperator() == UnaryOperator.GTE) {
return new Interval(RangeBoundary.CLOSED, valueFromNode(ut.getValue()), minMax.getUpperBound().getValue(), minMax.getUpperBound().getBoundaryType(), rule, col);
} else if (ut.getValue() instanceof RangeNode) {
RangeNode rangeNode = (RangeNode) ut.getValue();
if (!(rangeNode.getStart() instanceof NullNode || rangeNode.getEnd() instanceof NullNode)) {
return new Interval(rangeNode.getLowerBound() == IntervalBoundary.OPEN ? RangeBoundary.OPEN : RangeBoundary.CLOSED,
valueFromNode(rangeNode.getStart()),
valueFromNode(rangeNode.getEnd()),
rangeNode.getUpperBound() == IntervalBoundary.OPEN ? RangeBoundary.OPEN : RangeBoundary.CLOSED,
rule,
col);
} else if (rangeNode.getStart() instanceof NullNode) {
return new Interval(minMax.getLowerBound().getBoundaryType(),
minMax.getLowerBound().getValue(),
valueFromNode(rangeNode.getEnd()),
rangeNode.getUpperBound() == IntervalBoundary.OPEN ? RangeBoundary.OPEN : RangeBoundary.CLOSED,
rule,
col);
} else {
return new Interval(rangeNode.getLowerBound() == IntervalBoundary.OPEN ? RangeBoundary.OPEN : RangeBoundary.CLOSED,
valueFromNode(rangeNode.getStart()),
minMax.getUpperBound().getValue(),
minMax.getUpperBound().getBoundaryType(),
rule,
col);
}
} else {
throw new UnsupportedOperationException("UnaryTest type: " + ut);
}
}
private Comparable> valueFromNode(BaseNode node, Visitor> visitor) {
return node.accept(visitor);
}
private Comparable> valueFromNode(BaseNode node) {
return valueFromNode(node, valueFromNodeVisitor);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy