/** * This checks one "column" of the decision table output(s). */ private static void checkOneResult(EvaluationContext ctx, Indexed rule, Map<Integer, String> msgs, DecisionTable.OutputClause dtOutputClause, Object result, int index) { if (dtOutputClause.isCollection() && result instanceof Collection) { for (Object value : (Collection) result) { checkOneValue(ctx, rule, msgs, dtOutputClause, value, index); } } else { checkOneValue(ctx, rule, msgs, dtOutputClause, result, index); } }
@Override public String toString() { return "decision table " + dt.getSignature(); } }
public DTInvokerFunction(DecisionTableImpl dt) { super( dt.getName() ); this.dt = dt; }
public static Object generalizedCollect( EvaluationContext ctx, DecisionTable dt, List<?> results, Function<Stream<Object>, Object> resultCollector) { final List<Map<String, Object>> raw; final List<String> names = dt.getOutputs().stream().map( o -> o.getName() != null ? o.getName() : dt.getName() ).collect( toList() ); if ( names.size() > 1 ) { raw = (List<Map<String, Object>>) results; } else { raw = results.stream().map( (Object r) -> Collections.singletonMap( names.get( 0 ), r ) ).collect( toList() ); } return range( 0, names.size() ) .mapToObj( index -> names.get( index ) ) .map( name -> resultCollector.apply( raw.stream().map( r -> r.get( name ) ) ) ) .collect( singleValueOrContext( dt.getOutputs() ) ); }
/** * Priority – multiple rules can match, with different outputs. The output that comes first in the supplied output values list is returned */ public static Object priority( EvaluationContext ctx, DecisionTable dt, List<? extends Indexed> matches, List<Object> results) { if ( matches.isEmpty() ) { return null; } List<Pair<? extends Indexed, Object>> pairs = sortPairs( ctx, dt, matches, results ); ctx.notifyEvt( () -> { List<Integer> indexes = Collections.singletonList( pairs.get( 0 ).getLeft().getIndex() + 1 ); return new DecisionTableRulesSelectedEvent( FEELEvent.Severity.INFO, "Rules fired for decision table '" + dt.getName() + "': " + indexes, dt.getName(), dt.getName(), indexes ); } ); return pairs.get( 0 ).getRight(); }
/** * Checks if the parameters match a single rule * @param ctx * @param params * @param rule * @return */ private boolean matches(EvaluationContext ctx, Object[] params, DTDecisionRule rule) { for( int i = 0; i < params.length; i++ ) { CompiledExpression compiledInput = inputs.get(i).getCompiledInput(); if ( compiledInput instanceof CompiledFEELExpression) { ctx.setValue("?", ((CompiledFEELExpression) compiledInput).apply(ctx)); } if( ! satisfies( ctx, params[i], rule.getInputEntry().get( i ) ) ) { return false; } } return true; }
/** * C# – return the count of the outputs */ public static Object countCollect( EvaluationContext ctx, DecisionTable dt, List<? extends Indexed> matches, List<Object> results) { ctx.notifyEvt( () -> { List<Integer> indexes = matches.stream().map( m -> m.getIndex() + 1 ).collect( toList() ); return new DecisionTableRulesSelectedEvent( FEELEvent.Severity.INFO, "Rules fired for decision table '" + dt.getName() + "': " + indexes, dt.getName(), dt.getName(), indexes ); } ); return generalizedCollect( ctx, dt, results, x -> new BigDecimal( x.collect( toSet() ).size() ) ); }
public static Map<Integer, String> checkResults(List<? extends DecisionTable.OutputClause> outputs, EvaluationContext ctx, List<? extends Indexed> matches, List<Object> results) { Map<Integer, String> msgs = new TreeMap<>( ); int i = 0; for( Object result : results ) { if( outputs.size() == 1 ) { checkOneResult( ctx, matches.get( i ), msgs, outputs.get( 0 ), result, 1 ); } else if( outputs.size() > 1 ) { Map<String, Object> r = (Map<String, Object>) result; int outputIndex = 1; for ( DecisionTable.OutputClause output : outputs ) { checkOneResult( ctx, matches.get( i ), msgs, output, r.get( output.getName() ), outputIndex++ ); } } i++; } return msgs; }
/** * First – return the first match in rule order */ public static Object first( EvaluationContext ctx, DecisionTable dt, List<? extends Indexed> matches, List<Object> results) { if ( matches.size() >= 1 ) { ctx.notifyEvt( () -> { int index = matches.get( 0 ).getIndex() + 1; return new DecisionTableRulesSelectedEvent( FEELEvent.Severity.INFO, "Rule fired for decision table '" + dt.getName() + "': " + index, dt.getName(), dt.getName(), Collections.singletonList( index ) ); } ); return results.get( 0 ); } return null; }
/** * Each hit results in one output value (multiple outputs are collected into a single context value) */ private Object hitToOutput(EvaluationContext ctx, FEEL feel, DTDecisionRule rule) { List<CompiledExpression> outputEntries = rule.getOutputEntry(); Map<String, Object> values = ctx.getAllValues(); if ( outputEntries.size() == 1 ) { Object value = feel.evaluate( outputEntries.get( 0 ), values ); return value; } else { Map<String, Object> output = new HashMap<>(); for (int i = 0; i < outputs.size(); i++) { output.put(outputs.get(i).getName(), feel.evaluate(outputEntries.get(i), values)); } return output; } }
private Object[] resolveActualInputs(EvaluationContext ctx, FEEL feel) { Map<String, Object> variables = ctx.getAllValues(); Object[] actualInputs = new Object[ inputs.size() ]; for( int i = 0; i < inputs.size(); i++ ) { CompiledExpression compiledInput = inputs.get( i ).getCompiledInput(); if( compiledInput != null ) { actualInputs[i] = feel.evaluate( compiledInput, variables ); } else { actualInputs[i] = feel.evaluate( inputs.get( i ).getInputExpression(), variables ); } } return actualInputs; }
private Map<Integer, String> checkResults(EvaluationContext ctx, List<DTDecisionRule> matches, List<Object> results) { return checkResults(outputs, ctx, matches, results); }
public List<List<String>> getParameterNames() { return Collections.singletonList( dt.getParameterNames() ); }
public static HitPolicy fromString(String policy) { policy = policy.toUpperCase(); for ( HitPolicy c : HitPolicy.values() ) { if ( c.shortName.equals( policy ) || c.longName.equals( policy ) ) { return c; } } throw new IllegalArgumentException( "Unknown hit policy: " + policy ); }
@Override public void setName(String name) { super.setName(name); dt.setName(name); }
public DecisionTableImpl(String name, List<String> parameterNames, List<DTInputClause> inputs, List<DTOutputClause> outputs, List<DTDecisionRule> decisionRules, HitPolicy hitPolicy, FEEL feel) { this.name = name; this.parameterNames = parameterNames; this.inputs = inputs; this.outputs = outputs; this.decisionRules = decisionRules; this.hitPolicy = hitPolicy; this.hasDefaultValues = outputs.stream().allMatch( o -> o.getDefaultValue() != null ); this.feel = feel; }
private List<Object> evaluateResults(EvaluationContext ctx, FEEL feel, Object[] params, List<DTDecisionRule> matchingDecisionRules) { List<Object> results = matchingDecisionRules.stream().map( dr -> hitToOutput( ctx, feel, dr ) ).collect( Collectors.toList()); return results; }
public static <T> Collector<T, ?, Object> singleValueOrContext(List<? extends DecisionTable.OutputClause> outputs) { return new SingleValueOrContextCollector<T>( outputs.stream().map( DecisionTable.OutputClause::getName ).collect( toList() ) ); }
/** * Output order – return a list of outputs in the order of the output values list */ public static Object outputOrder( EvaluationContext ctx, DecisionTable dt, List<? extends Indexed> matches, List<Object> results ) { if ( matches.isEmpty() ) { return null; } List<Pair<? extends Indexed, Object>> pairs = sortPairs( ctx, dt, matches, results ); ctx.notifyEvt( () -> { List<Integer> indexes = pairs.stream().map( p -> p.getLeft().getIndex() + 1 ).collect( toList() ); return new DecisionTableRulesSelectedEvent( FEELEvent.Severity.INFO, "Rules fired for decision table '" + dt.getName() + "': " + indexes, dt.getName(), dt.getName(), indexes ); } ); return pairs.stream().map( p -> p.getRight() ).collect( Collectors.toList() ); }
public String getSignature() { return getName() + "( " + parameterNames.stream().collect( Collectors.joining( ", " ) ) + " )"; }