Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.kie.dmn.validation.dtanalysis.mcdc.MCDCAnalyser Maven / Gradle / Ivy
/**
* 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.mcdc;
import java.math.BigDecimal;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.kie.dmn.feel.runtime.Range.RangeBoundary;
import org.kie.dmn.model.api.DecisionTable;
import org.kie.dmn.model.api.HitPolicy;
import org.kie.dmn.validation.dtanalysis.model.Bound;
import org.kie.dmn.validation.dtanalysis.model.DDTAInputEntry;
import org.kie.dmn.validation.dtanalysis.model.DDTARule;
import org.kie.dmn.validation.dtanalysis.model.DDTATable;
import org.kie.dmn.validation.dtanalysis.model.Interval;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MCDCAnalyser {
private static final Logger LOG = LoggerFactory.getLogger(MCDCAnalyser.class);
private final DDTATable ddtaTable;
private final DecisionTable dt;
private Optional elseRuleIdx = Optional.empty();
private List> allEnumValues = new ArrayList<>();
private List selectedBlocks = new ArrayList<>();
public MCDCAnalyser(DDTATable ddtaTable, DecisionTable dt) {
this.ddtaTable = ddtaTable;
this.dt = dt;
}
public List compute() {
if (dt.getHitPolicy() != HitPolicy.UNIQUE && dt.getHitPolicy() != HitPolicy.ANY && dt.getHitPolicy() != HitPolicy.PRIORITY) {
return Collections.emptyList(); // cannot analyse.
}
if (!ddtaTable.getColIDsStringWithoutEnum().isEmpty()) {
return Collections.emptyList(); // if not enumerated output values, cannot analyse.
}
// Step1
calculateElseRuleIdx();
calculateAllEnumValues();
// Step2, 3
int i = 1;
while (areInputsYetToBeVisited()) {
LOG.debug("=== Step23, iteration {}", i);
step23();
i++;
}
while (!step4whichOutputYetToVisit().isEmpty()) {
step4();
}
LOG.info("The final results are as follows. (marked with R the 'red color' records which are duplicates, changing input is marked with * sign)");
LOG.info("Left Hand Side for Positive:");
Set mcdcRecords = new LinkedHashSet<>();
// cycle positive side first
for (PosNegBlock b : selectedBlocks) {
boolean add = mcdcRecords.add(b.posRecord);
if (add) {
LOG.info("+ {}", b.posRecord.toString(b.cMarker));
} else {
LOG.info("R {}", b.posRecord.toString(b.cMarker));
}
}
// cycle negative side
LOG.info("Right Hand Side for Negative:");
for (PosNegBlock b : selectedBlocks) {
for (Record negRecord : b.negRecords) {
boolean add = mcdcRecords.add(negRecord);
if (add) {
LOG.info("- {}", negRecord);
} else {
LOG.info("R {}", negRecord);
}
}
LOG.info(" ");
}
LOG.info("total of cases: {}", mcdcRecords.size());
return selectedBlocks;
}
private void step4() {
Set>> outYetToVisit = step4whichOutputYetToVisit();
Optional>> findFirst = outYetToVisit.stream().findFirst();
if (findFirst.isEmpty()) {
throw new IllegalArgumentException("step4 was invoked despite there are no longer output to visit.");
}
List> pickOutToVisit = findFirst.get();
List rules = new ArrayList<>();
for (int ruleIdx = 0; ruleIdx < ddtaTable.getRule().size(); ruleIdx++) {
if (ddtaTable.getRule().get(ruleIdx).getOutputEntry().equals(pickOutToVisit)) {
rules.add(ruleIdx);
}
}
LOG.trace("rules {}", rules);
List> blocks = new ArrayList<>();
for (int ruleIdx : rules) {
List valuesForRule = negBlockValuesForRule(ruleIdx);
if (valuesForRule.isEmpty()) {
LOG.debug("step4, while looking for candidate values for rule {} I could NOT re-use from a negative case, computing new set.", ruleIdx);
Object[] posCandidate = findValuesForRule(ruleIdx, Collections.unmodifiableList(allEnumValues));
if (Stream.of(posCandidate).anyMatch(x -> x == null)) {
throw new IllegalStateException();
}
valuesForRule.add(posCandidate);
}
for (Object[] posCandidate : valuesForRule) {
LOG.trace("ruleIdx {} values {}", ruleIdx + 1, posCandidate);
Record posCandidateRecord = new Record(ruleIdx, posCandidate, ddtaTable.getRule().get(ruleIdx).getOutputEntry());
for (int chgInput = 0; chgInput < ddtaTable.getInputs().size(); chgInput++) {
Optional calculatePosNegBlock = calculatePosNegBlock(chgInput, posCandidate[chgInput], posCandidateRecord, Collections.unmodifiableList(allEnumValues));
if (calculatePosNegBlock.isPresent()) {
PosNegBlock posNegBlock = calculatePosNegBlock.get();
int w = computeAdditionalWeightIntroBlock(posNegBlock);
LOG.trace("{} weight: {}", posNegBlock, w);
blocks.add(new SimpleEntry<>(posNegBlock, w));
}
}
}
}
Optional posNegBlockFirst = blocks.stream().sorted(Entry.comparingByValue()).map(Entry::getKey).findFirst();
if (posNegBlockFirst.isEmpty()) {
throw new IllegalStateException("there is no candidable posNegBlocks.");
}
PosNegBlock posNegBlock = posNegBlockFirst.get();
LOG.trace("step4 selecting block: \n{}", posNegBlock);
selectBlock(posNegBlock);
}
private Set>> step4whichOutputYetToVisit() {
Set>> outYetToVisit = ddtaTable.getRule().stream().map(r -> r.getOutputEntry()).collect(Collectors.toSet());
outYetToVisit.removeAll(getVisitedPositiveOutput());
LOG.trace("outYetToVisit {}", outYetToVisit);
if (outYetToVisit.size() > 1 && elseRuleIdx.isPresent() && outYetToVisit.contains(ddtaTable.getRule().get(elseRuleIdx.get()).getOutputEntry())) {
LOG.trace("outYetToVisit will be filtered of the Else rule's output {}.", ddtaTable.getRule().get(elseRuleIdx.get()).getOutputEntry());
outYetToVisit.remove(ddtaTable.getRule().get(elseRuleIdx.get()).getOutputEntry());
LOG.trace("outYetToVisit {}", outYetToVisit);
}
return outYetToVisit;
}
private List negBlockValuesForRule(int ruleIdx) {
List result = new ArrayList<>();
for (PosNegBlock b : selectedBlocks) {
for (Record nr : b.negRecords) {
if (nr.ruleIdx == ruleIdx) {
result.add(nr.enums);
}
}
}
return result;
}
private boolean areInputsYetToBeVisited() {
List idx = getAllColumnIndexes();
idx.removeAll(getAllColumnVisited());
return idx.size() > 0;
}
private void step23() {
LOG.debug("step23() ------------------------------");
List visitedIndexes = getAllColumnVisited();
LOG.debug("Visited Inputs: {}", debugListPlusOne(visitedIndexes));
List>> visitedPositiveOutput = getVisitedPositiveOutput();
LOG.debug("Visited positive Outputs: {}", visitedPositiveOutput);
debugAllEnumValues();
List allIndexes = getAllColumnIndexes();
allIndexes.removeAll(visitedIndexes);
LOG.debug("Inputs yet to be analysed: {}", debugListPlusOne(allIndexes));
List idxMoreEnums = whichIndexHasMoreEnums(allIndexes);
LOG.debug("2.a Pick the input with greatest number of enum values {} ? it's: {}",
debugListPlusOne(allIndexes),
debugListPlusOne(idxMoreEnums));
Integer idxMostMatchingRules = idxMoreEnums.stream()
.map(i -> new SimpleEntry(i, matchingRulesForInput(i, allEnumValues.get(i).get(0)).size()))
.max(Entry.comparingByValue())
.map(Entry::getKey)
.orElseThrow(() -> new RuntimeException());
LOG.debug("2.b Choose the input with the greatest number of rules matching that enum {} ? it's: {}",
idxMoreEnums.stream()
.map(i -> new SimpleEntry(i, matchingRulesForInput(i, allEnumValues.get(i).get(0)).size()))
.collect(Collectors.toList()),
idxMostMatchingRules + 1);
List candidateBlocks = new ArrayList<>();
Object value = allEnumValues.get(idxMostMatchingRules).get(0);
List matchingRulesForInput = matchingRulesForInput(idxMostMatchingRules, value);
for (int ruleIdx : matchingRulesForInput) {
Object[] knownValues = new Object[ddtaTable.getInputs().size()];
knownValues[idxMostMatchingRules] = value;
List valuesForRule = combinatorialValuesForRule(ruleIdx, knownValues, Collections.unmodifiableList(allEnumValues));
for (Object[] posCandidate : valuesForRule) {
LOG.trace("ruleIdx {} values {}", ruleIdx + 1, posCandidate);
if (Stream.of(posCandidate).anyMatch(x -> x == null)) {
continue;
}
List ruleIndexesMatchingValues = ruleIndexesMatchingValues(posCandidate);
if (!ruleIndexesMatchingValues.remove((Integer) ruleIdx)) {
continue; // the posCandidate is actually matching another rule (in priorities)
}
if (ruleIndexesMatchingValues.size() > 0) {
LOG.debug("Skipping posCandidate {} as it could also match rules {}, besides the one currently under calculus {}", posCandidate, ruleIndexesMatchingValues, ruleIdx);
continue;
}
Record posCandidateRecord = new Record(ruleIdx, posCandidate, ddtaTable.getRule().get(ruleIdx).getOutputEntry());
Optional calculatePosNegBlock = calculatePosNegBlock(idxMostMatchingRules, value, posCandidateRecord, Collections.unmodifiableList(allEnumValues));
if (calculatePosNegBlock.isPresent()) {
candidateBlocks.add(calculatePosNegBlock.get());
}
}
}
LOG.trace("3. Input {}, initial candidate blocks \n{}", idxMostMatchingRules + 1, candidateBlocks);
Set>> filter1outs = candidateBlocks.stream().map(b -> b.posRecord.output).collect(Collectors.toSet());
LOG.trace("filter1outs {}", filter1outs);
if (filter1outs.stream().anyMatch(not(getVisitedPositiveOutput()::contains))) {
LOG.trace("Trying to prioritize non-yet visited outputs...");
Set>> hypo = new HashSet<>(filter1outs);
hypo.removeAll(getVisitedPositiveOutput());
if (elseRuleIdx.isPresent() && hypo.size() == 1 && hypo.iterator().next().equals(ddtaTable.getRule().get(elseRuleIdx.get()).getOutputEntry())) {
LOG.trace("...won't be prioritizing non-yet visited outputs, otherwise I would prioritize the Else rules.");
} else {
filter1outs.removeAll(getVisitedPositiveOutput());
LOG.trace("I recomputed filter1outs to prioritize non-yet visited outputs {}", filter1outs);
}
}
if (filter1outs.size() > 1 && elseRuleIdx.isPresent() && filter1outs.contains(ddtaTable.getRule().get(elseRuleIdx.get()).getOutputEntry())) {
LOG.trace("filter1outs will be filtered of the Else rule's output {}.", ddtaTable.getRule().get(elseRuleIdx.get()).getOutputEntry());
filter1outs.remove(ddtaTable.getRule().get(elseRuleIdx.get()).getOutputEntry());
LOG.trace("filter1outs {}", filter1outs);
}
List>, Integer>> filter1outsMatchingRules = filter1outs.stream()
.map(out -> new SimpleEntry<>(out, (int) ddtaTable.getRule().stream().filter(r -> r.getOutputEntry().equals(out)).count()))
.sorted(Entry.comparingByValue())
.collect(Collectors.toList());
LOG.trace("3. of those blocks positive outputs, how many rule do they match? {}", filter1outsMatchingRules);
List> filter1outWithLessMatchingRules = filter1outsMatchingRules.get(0).getKey();
LOG.trace("3. positive output of those block with the less matching rules? {}", filter1outWithLessMatchingRules);
List filter2 = candidateBlocks.stream().filter(b -> b.posRecord.output.equals(filter1outWithLessMatchingRules)).collect(Collectors.toList());
LOG.trace("3.FILTER-2 blocks with output corresponding to the less matching rules {}, the blocks are: \n{}", filter1outWithLessMatchingRules, filter2);
List> blockWeighted = filter2.stream()
.map(b -> new SimpleEntry<>(b, computeAdditionalWeightIntroBlock(b)))
.sorted(Entry.comparingByValue())
.collect(Collectors.toList());
LOG.trace("3. blocks sorted by fewest new cases (natural weight order): \n{}", blockWeighted);
PosNegBlock selectedBlock = blockWeighted.get(0).getKey();
LOG.trace("3. I select the first, chosen block to be select: \n{}", selectedBlock);
selectBlock(selectedBlock);
}
/**
* JDK-11 polyfill
*/
public static Predicate not(Predicate t) {
return t.negate();
}
private int computeAdditionalWeightIntroBlock(PosNegBlock newBlock) {
int score = 0;
Record posRecord = newBlock.posRecord;
if (selectedBlocks.stream().noneMatch(vb -> vb.posRecord.equals(posRecord))) {
score++;
}
for (Record negRecord : newBlock.negRecords) {
if (selectedBlocks.stream().flatMap(vb -> vb.negRecords.stream()).noneMatch(nr -> nr.equals(negRecord))) {
score++;
}
}
return score;
}
private void selectBlock(PosNegBlock selected) {
selectedBlocks.add(selected);
// int index = selected.cMarker;
// allEnumValues.get(index).removeAll(Arrays.asList(selected.posRecord.enums[index]));
}
private List>> getVisitedPositiveOutput() {
return selectedBlocks.stream().map(b -> b.posRecord.output).collect(Collectors.toList());
}
private List getAllColumnVisited() {
return selectedBlocks.stream().map(b -> b.cMarker).collect(Collectors.toList());
}
private List getAllColumnIndexes() {
return IntStream.range(0, ddtaTable.getInputs().size()).boxed().collect(Collectors.toList());
}
private List debugListPlusOne(List input) {
return input.stream().map(x -> x + 1).collect(Collectors.toList());
}
private List whichIndexHasMoreEnums(List allIndexes) {
Map> byIndex = new HashMap<>();
for (Integer idx : allIndexes) {
byIndex.put(idx, allEnumValues.get(idx));
}
Integer max = byIndex.values().stream().map(List::size).max(Integer::compareTo).orElse(0);
List collect = byIndex.entrySet().stream().filter(kv -> kv.getValue().size() == max).map(Entry::getKey).collect(Collectors.toList());
return collect;
}
private Optional calculatePosNegBlock(Integer idx, Object value, Record posCandidate, List> allEnumValues) {
List> posOutput = posCandidate.output;
List> enumValues = allEnumValues.get(idx);
List> allOtherEnumValues = new ArrayList<>(enumValues);
allOtherEnumValues.remove(value);
List negativeRecords = new ArrayList<>();
for (Object otherEnumValue : allOtherEnumValues) {
Object[] negCandidate = Arrays.copyOf(posCandidate.enums, posCandidate.enums.length);
negCandidate[idx] = otherEnumValue;
Record negRecordForNegCandidate = null;
for (int i = 0; negRecordForNegCandidate == null && i < ddtaTable.getRule().size(); i++) {
DDTARule rule = ddtaTable.getRule().get(i);
boolean ruleMatches = ruleMatches(rule, negCandidate);
if (ruleMatches) {
negRecordForNegCandidate = new Record(i, negCandidate, rule.getOutputEntry());
}
}
if (negRecordForNegCandidate != null) {
negativeRecords.add(negRecordForNegCandidate);
}
}
boolean allNegValuesDiffer = true;
for (Record record : negativeRecords) {
allNegValuesDiffer &= !record.output.equals(posOutput);
}
if (allNegValuesDiffer) {
PosNegBlock posNegBlock = new PosNegBlock(idx, posCandidate, negativeRecords);
return Optional.of(posNegBlock);
}
LOG.trace("For In{}={} and candidate positive of {}, it cannot be a matching rule because some negative case had SAME output {}", idx + 1, value, posCandidate, negativeRecords);
return Optional.empty();
}
private static boolean ruleMatches(DDTARule rule, Object[] values) {
boolean ruleMatches = true;
for (int c = 0; ruleMatches && c < rule.getInputEntry().size(); c++) {
Object cValue = values[c];
ruleMatches &= rule.getInputEntry().get(c).getIntervals().stream().anyMatch(interval -> interval.asRangeIncludes(cValue));
}
return ruleMatches;
}
private List ruleIndexesMatchingValues(Object[] values) {
List ruleIndexes = new ArrayList<>();
for (int i = 0; i < ddtaTable.getRule().size(); i++) {
DDTARule rule = ddtaTable.getRule().get(i);
if (ruleMatches(rule, values)) {
ruleIndexes.add(i);
}
}
if (dt.getHitPolicy() == HitPolicy.PRIORITY) {
List>> outputs = new ArrayList<>();
for (Integer ruleIdx : ruleIndexes) {
DDTARule rule = ddtaTable.getRule().get(ruleIdx);
List> ruleOutput = rule.getOutputEntry();
outputs.add(ruleOutput);
}
List> computedOutput = new ArrayList<>();
for (int i = 0; i < ddtaTable.getOutputs().size(); i++) {
List outputOrder = ddtaTable.getOutputs().get(i).getOutputOrder();
int outputCursor = Integer.MAX_VALUE;
for (List> outs : outputs) {
Comparable> out = outs.get(i);
if (outputOrder.indexOf(out) < outputCursor) {
outputCursor = outputOrder.indexOf(out);
}
}
computedOutput.add((Comparable>) outputOrder.get(outputCursor));
}
List pIndexes = new ArrayList<>();
for (Integer ruleIdx : ruleIndexes) {
DDTARule rule = ddtaTable.getRule().get(ruleIdx);
List> ruleOutput = rule.getOutputEntry();
if (ruleOutput.equals(computedOutput)) {
pIndexes.add(ruleIdx);
}
}
return pIndexes;
}
return ruleIndexes;
}
public static class PosNegBlock {
public final int cMarker;
public final Record posRecord;
public final List negRecords;
public PosNegBlock(int cMarker, Record posRecord, List negRecords) {
this.cMarker = cMarker;
this.posRecord = posRecord;
this.negRecords = negRecords;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("PosNeg block In ").append(cMarker + 1).append("=").append(posRecord.enums[cMarker]).append("\n");
sb.append(" + ").append(posRecord).append("\n");
for (Record negRecord : negRecords) {
sb.append(" - ").append(negRecord).append("\n");
}
return sb.toString();
}
}
public static class Record {
public final int ruleIdx;
public final Object[] enums;
public final List> output;
public Record(int ruleIdx, Object[] enums, List> output) {
this.ruleIdx = ruleIdx;
this.enums = enums;
this.output = output;
}
@Override
public String toString() {
return String.format("%2s", ruleIdx + 1) + " [" + Arrays.stream(enums).map(Object::toString).collect(Collectors.joining("; ")) + "] -> " + output;
}
public String toString(int cMarker) {
StringBuilder ts = new StringBuilder(String.format("%2s", ruleIdx + 1));
ts.append(" [");
for (int i = 0; i < enums.length; i++) {
if (i == cMarker) {
ts.append("*");
}
ts.append(enums[i]);
if (i + 1 < enums.length) {
ts.append("; ");
}
}
ts.append("] -> ").append(output);
return ts.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.deepHashCode(enums);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Record other = (Record) obj;
if (!Arrays.deepEquals(enums, other.enums))
return false;
return true;
}
}
private List combinatorialValuesForRule(int ruleIdx, Object[] knownValues, List> allEnumValues) {
List result = new ArrayList<>();
List inputEntry = ddtaTable.getRule().get(ruleIdx).getInputEntry();
List> validEnumValues = new ArrayList<>();
for (int i = 0; i < inputEntry.size(); i++) {
List enumForI = new ArrayList<>();
if (knownValues[i] == null) {
DDTAInputEntry ddtaInputEntry = inputEntry.get(i);
List> enumValues = allEnumValues.get(i);
for (Object object : enumValues) {
if (ddtaInputEntry.getIntervals().stream().anyMatch(interval -> interval.asRangeIncludes(object))) {
enumForI.add(object);
}
}
} else {
enumForI.add(knownValues[i]);
}
validEnumValues.add(enumForI);
}
List> combinatorial = new ArrayList<>();
combinatorial.add(new ArrayList<>());
for (int i = 0; i < inputEntry.size(); i++) {
List> combining = new ArrayList<>();
for (List existing : combinatorial) {
for (Object enumForI : validEnumValues.get(i)) {
List building = new ArrayList<>(existing);
building.add(enumForI);
combining.add(building);
}
}
combinatorial = combining;
}
return combinatorial.stream().map(List::toArray).collect(Collectors.toList());
}
private Object[] findValuesForRule(int ruleIdx, Object[] knownValues, List> allEnumValues) {
Object[] result = Arrays.copyOf(knownValues, knownValues.length);
List inputEntry = ddtaTable.getRule().get(ruleIdx).getInputEntry();
for (int i = 0; i < inputEntry.size(); i++) {
if (result[i] == null) {
DDTAInputEntry ddtaInputEntry = inputEntry.get(i);
List> enumValues = allEnumValues.get(i);
Interval interval0 = ddtaInputEntry.getIntervals().get(0);
if (interval0.isSingularity()) {
result[i] = interval0.getLowerBound().getValue();
} else if (interval0.getLowerBound().getBoundaryType() == RangeBoundary.CLOSED && interval0.getLowerBound().getValue() != Interval.NEG_INF) {
result[i] = interval0.getLowerBound().getValue();
} else if (interval0.getUpperBound().getBoundaryType() == RangeBoundary.CLOSED && interval0.getUpperBound().getValue() != Interval.POS_INF) {
result[i] = interval0.getUpperBound().getValue();
}
if (!enumValues.contains(result[i])) {
result[i] = null; // invalidating if the chosen enum is not part of the plausible ones
}
if (result[i] == null) {
for (Object object : enumValues) {
if (ddtaInputEntry.getIntervals().stream().anyMatch(interval -> interval.asRangeIncludes(object))) {
result[i]= object;
break;
}
}
}
}
}
return result;
}
private Object[] findValuesForRule(int ruleIdx, List> allEnumValues) {
Object[] result = new Object[ddtaTable.getInputs().size()];
List inputEntry = ddtaTable.getRule().get(ruleIdx).getInputEntry();
for (int i = 0; i < inputEntry.size(); i++) {
if (result[i] == null) {
DDTAInputEntry ddtaInputEntry = inputEntry.get(i);
List> enumValues = allEnumValues.get(i);
for (Object object : enumValues) {
if (ddtaInputEntry.getIntervals().stream().anyMatch(interval -> interval.asRangeIncludes(object))) {
result[i] = object;
break;
}
}
}
}
return result;
}
private List matchingRulesForInput(int colIdx, Object value) {
List results = new ArrayList<>();
List rules = ddtaTable.getRule();
for (int i = 0; i < rules.size(); i++) {
List intervals = rules.get(i).getInputEntry().get(colIdx).getIntervals();
if (intervals.stream().anyMatch(interval -> interval.asRangeIncludes(value))) {
results.add(i);
}
}
LOG.trace("Considering just In{}={} in the original decision tables matches rules: {} total of {} rules.", colIdx + 1, value, debugListPlusOne(results), results.size());
return results;
}
private void calculateAllEnumValues() {
for (int idx = 0; idx < ddtaTable.inputCols(); idx++) {
if (ddtaTable.getInputs().get(idx).isDiscreteDomain()) {
List> discreteValues = new ArrayList<>(ddtaTable.getInputs().get(idx).getDiscreteDMNOrder());
allEnumValues.add(discreteValues); // add _the collection_
continue;
}
List colIntervals = ddtaTable.projectOnColumnIdx(idx);
List bounds = colIntervals.stream().flatMap(i -> Stream.of(i.getLowerBound(), i.getUpperBound())).collect(Collectors.toList());
Collections.sort(bounds);
LOG.trace("bounds (sorted) {}", bounds);
List enumValues = new ArrayList<>();
Bound> prevBound = bounds.remove(0);
while (bounds.size() > 0 && bounds.get(0).compareTo(prevBound) == 0) {
prevBound = bounds.remove(0); //look-ahead.
}
while (bounds.size() > 0) {
Bound> curBound = bounds.remove(0);
while (bounds.size() > 0 && bounds.get(0).compareTo(curBound) == 0) {
curBound = bounds.remove(0); //look-ahead.
}
LOG.trace("prev {}, cur {}", prevBound, curBound);
if (prevBound.isUpperBound() && curBound.isLowerBound()) {
// do nothing.
} else if (prevBound.isUpperBound() && curBound.isUpperBound()) {
if (curBound.getBoundaryType() == RangeBoundary.CLOSED && !isBoundInfinity(curBound)) {
enumValues.add(curBound.getValue());
} else {
LOG.trace("looking for value in-between {} {} ", prevBound, curBound);
enumValues.add(inBetween(prevBound, curBound));
}
} else if (prevBound.isLowerBound() && curBound.isLowerBound()) {
if (prevBound.getBoundaryType() == RangeBoundary.CLOSED && !isBoundInfinity(prevBound)) {
enumValues.add(prevBound.getValue());
} else {
LOG.trace("looking for value in-between {} {} ", prevBound, curBound);
enumValues.add(inBetween(prevBound, curBound));
}
} else {
if (prevBound.getBoundaryType() == RangeBoundary.CLOSED && !isBoundInfinity(prevBound)) {
enumValues.add(prevBound.getValue());
} else if (curBound.getBoundaryType() == RangeBoundary.CLOSED && !isBoundInfinity(curBound)) {
enumValues.add(curBound.getValue());
} else {
LOG.trace("looking for value in-between {} {} ", prevBound, curBound);
enumValues.add(inBetween(prevBound, curBound));
}
}
prevBound = curBound;
while (bounds.size() > 0 && bounds.get(0).compareTo(prevBound) == 0) {
prevBound = bounds.remove(0); //look-ahead.
}
}
LOG.trace("enumValues: {}", enumValues);
allEnumValues.add(enumValues);
}
for (int in=0; in inX = allEnumValues.get(in);
List sorted = inX.stream().map(v -> new Pair((Comparable>) v, matchingRulesForInput(inIdx, v).size()))
.sorted()
.collect(Collectors.toList());
LOG.debug("Input {} sorted by number of matching rules: {}", inIdx + 1, sorted);
List> sortedByMatchingRules = sorted.stream()
.map(Pair::getKey)
.collect(Collectors.toList());
allEnumValues.set(inIdx, sortedByMatchingRules);
}
debugAllEnumValues();
}
public static class Pair implements Comparable {
private final Comparable key;
private final int occurences;
private final Comparator c1 = Comparator.comparing(Pair::getOccurences).reversed();
public Pair(Comparable> key, int occurences) {
this.key = key;
this.occurences = occurences;
}
public Comparable> getKey() {
return key;
}
public int getOccurences() {
return occurences;
}
@Override
public int compareTo(Pair o) {
if (c1.compare(this, o) != 0) {
return c1.compare(this, o);
} else {
return -1 * this.key.compareTo(o.key);
}
}
@Override
public String toString() {
return "{" + key + "=" + occurences + "}";
}
}
private void debugAllEnumValues() {
LOG.debug("allEnumValues:");
for (int idx = 0; idx < allEnumValues.size(); idx++) {
LOG.debug("allEnumValues In{}= {}", idx + 1, allEnumValues.get(idx));
}
}
private void calculateElseRuleIdx() {
if (dt.getHitPolicy() == HitPolicy.PRIORITY) {// calculate "else" rule if present.
for (int ruleIdx = ddtaTable.getRule().size() - 1; ruleIdx>=0 && elseRuleIdx.isEmpty(); ruleIdx--) {
DDTARule rule = ddtaTable.getRule().get(ruleIdx);
List ie = rule.getInputEntry();
boolean checkAll = true;
for (int colIdx = 0; colIdx < ie.size() && checkAll; colIdx++) {
DDTAInputEntry ieIDX = ie.get(colIdx);
boolean idIDXsize1 = ieIDX.getIntervals().size() == 1;
Interval ieIDXint0 = ieIDX.getIntervals().get(0);
Interval domainMinMax = ddtaTable.getInputs().get(colIdx).getDomainMinMax();
boolean equals = ieIDXint0.equals(domainMinMax);
checkAll &= idIDXsize1 && equals;
}
if (checkAll) {
LOG.debug("I believe P table with else rule: {}", ruleIdx + 1);
elseRuleIdx = Optional.of(ruleIdx);
}
}
}
}
private Object inBetween(Bound a, Bound b) {
if (a.getValue() instanceof BigDecimal || b.getValue() instanceof BigDecimal) {
BigDecimal aValue = a.getValue() == Interval.NEG_INF ? ((BigDecimal) b.getValue()).add(new BigDecimal(-2)) : (BigDecimal) a.getValue();
BigDecimal bValue = b.getValue() == Interval.POS_INF ? ((BigDecimal) a.getValue()).add(new BigDecimal(+2)) : (BigDecimal) b.getValue();
BigDecimal guessWork = new BigDecimal(aValue.intValue() + 1);
if (bValue.compareTo(guessWork) > 0) {
return guessWork;
} else if (bValue.compareTo(guessWork) == 0 && b.isLowerBound() && b.getBoundaryType() == RangeBoundary.OPEN) {
return guessWork;
} else {
throw new UnsupportedOperationException();
}
}
throw new UnsupportedOperationException();
}
private boolean isBoundInfinity(Bound b) {
return b.getValue() == Interval.NEG_INF || b.getValue() == Interval.POS_INF;
}
}