Skip to content

Commit

Permalink
Basics FEEL Decision Table support (#8)
Browse files Browse the repository at this point in the history
* Very basics working but still missing a lot of tests and cases.

* Decision Table, Named parameter support.
  • Loading branch information
tarilabs authored and etirelli committed Oct 29, 2016
1 parent a9c7500 commit f9ffb2f
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.kie.dmn.feel.lang.EvaluationContext;
import org.kie.dmn.feel.lang.Symbol;
import org.kie.dmn.feel.runtime.FEELFunction;
import org.kie.dmn.feel.runtime.functions.DecisionTableFunction.ConcreteDTFunction;
import org.kie.dmn.feel.lang.impl.NamedParameter;
import org.kie.dmn.feel.lang.types.FunctionSymbol;
import org.slf4j.Logger;
Expand Down Expand Up @@ -94,6 +95,11 @@ public Object applyReflectively(EvaluationContext ctx, Object[] params) {
params = rearrangeParameters( params, ((JavaFunction) this).getParameterNames().get( 0 ) );
}
result = ((JavaFunction)this).apply( ctx, params );
} else if( this instanceof ConcreteDTFunction ) {
if( isNamedParams ) {
params = rearrangeParameters( params, ((ConcreteDTFunction) this).getParameterNames().get( 0 ) );
}
result = ((ConcreteDTFunction)this).apply( ctx, params );
} else {
logger.error( "Unable to find function '" + toString() +"'" );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,123 @@

package org.kie.dmn.feel.runtime.functions;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import org.kie.dmn.feel.lang.EvaluationContext;
import org.kie.dmn.feel.runtime.Range;
import org.kie.dmn.feel.runtime.UnaryTest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DecisionTableFunction
extends BaseFEELFunction {


private static final Logger LOG = LoggerFactory.getLogger( DecisionTableFunction.class );

public DecisionTableFunction() {
super( "decision table" );
}

public Object apply( @ParameterName("outputs") Object outputs, @ParameterName("input expression list") List inputExpressionList,
@ParameterName("rule list") List<List> ruleList, @ParameterName("hit policy") Object hitPolicy) {

List<DecisionRule> decisionRules = ruleList.stream()
.map(o->DecisionTableFunction.toDecisionRule(o, inputExpressionList.size()))
.collect(Collectors.toList());

// TODO shoud the name be the information item?
// TODO what if inputExpressionList does not contain only strings?
return new ConcreteDTFunction(UUID.randomUUID().toString(), inputExpressionList, decisionRules);
}

public static DecisionRule toDecisionRule(List<?> rule, int inputSize) {
// TODO should be check indeed block of inputSize n inputs, followed by block of outputs.
DecisionRule dr = new DecisionRule();
for ( int i=0 ; i<rule.size() ; i++ ) {
Object o = rule.get(i);
if ( i<inputSize ) {
if ( o instanceof UnaryTest ) {
dr.getInputEntry().add((UnaryTest) o);
} else if ( o instanceof Range ) {
dr.getInputEntry().add(x -> ((Range)o).includes((Comparable<?>)x));
} else {
dr.getInputEntry().add(x -> x.equals(o));
}
} else {
dr.getOutputEntry().add(o);
}
}
return dr;
}

public static class DecisionRule {
private List<UnaryTest> inputEntry;
private List<Object> outputEntry;

public List<UnaryTest> getInputEntry() {
if ( inputEntry == null ) {
inputEntry = new ArrayList<>();
}
return this.inputEntry;
}

public List<Object> getOutputEntry() {
if ( outputEntry == null ) {
outputEntry = new ArrayList<>();
}
return this.outputEntry;
}
}

public static class ConcreteDTFunction extends BaseFEELFunction {
private List<DecisionRule> decisionRules;
private List<String> inputs;

public ConcreteDTFunction(String name, List<String> inputs, List<DecisionRule> decisionRules) {
super(name);
this.decisionRules = decisionRules;
this.inputs = inputs;
}

public Object apply(EvaluationContext ctx, Object[] params) {
if (decisionRules.isEmpty()) {
return null;
}

if (params.length != decisionRules.get(0).getInputEntry().size()) {
LOG.error("The parameters supplied does not match input expression list");
return null;
}

for ( DecisionRule decisionRule : decisionRules ) {
Boolean ruleMatches = IntStream.range(0, params.length) // TODO could short-circuit by using for/continue
.mapToObj(i -> decisionRule.getInputEntry().get(i).apply(params[i]))
.reduce((a, b) -> a && b)
.orElse(false);
if (ruleMatches) {
if (decisionRule.getOutputEntry().size() == 1) {
return decisionRule.getOutputEntry().get(0);
} else {
return decisionRule.getOutputEntry();
}
}
}

return null;
}

@Override
protected boolean isCustomFunction() {
return true;
}

public List<List<String>> getParameterNames() {
return Arrays.asList( inputs );
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package org.kie.dmn.feel.lang.examples;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;

import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.kie.dmn.feel.FEEL;
import org.kie.dmn.feel.lang.CompiledExpression;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SimpleDecisionTablesTest {
private static final Logger logger = LoggerFactory.getLogger( ExamplesTest.class );
public static final String DEFAULT_IDENT = " ";
private static FEEL feel;

@BeforeClass
public static void setupTest() {
feel = FEEL.newInstance();
}

@Test
public void testMain() {
String expression = loadExpression( "simple_decision_tables.feel" );

// String expression2 = "decision table ( \n" +
// " rule list: [\n" +
// " [ <18, \"Young\"],\n" +
// " [ >18, \"Adult\"]\n" +
// " ]\n" +
// " )";
String expression2 = "1 = 1";
System.out.println(expression2);
CompiledExpression compile = feel.compile(expression2, feel.newCompilerContext());

System.out.println(compile);

System.out.println("---");

Map context = (Map) feel.evaluate( expression );

System.out.println( printContext( context ) );
}

private static String loadExpression(String fileName) {
try {
return new String( Files.readAllBytes( Paths.get( ExamplesTest.class.getResource( fileName ).toURI() ) ) );
} catch ( Exception e ) {
logger.error( "Error reading file " + fileName, e );
Assert.fail("Error reading file "+fileName);
}
return null;
}

private String printContext( Map context ) {
return printContext( context, "" );
}

private String printContext( Map<String, Object> context, String ident ) {
StringBuilder builder = new StringBuilder( );
builder.append( "{\n" );
for( Map.Entry e : context.entrySet() ) {
builder.append( ident )
.append( DEFAULT_IDENT )
.append( e.getKey() )
.append( ": " );
if( e.getValue() instanceof Map ) {
builder.append( printContext( (Map<String, Object>) e.getValue(), ident + DEFAULT_IDENT ) );
} else {
builder.append( e.getValue() )
.append( "\n" );
}
}
builder.append( ident+"}\n" );
return builder.toString();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
myDecisionTable : decision table (
outputs: "Adult",
input expression list: [Age],
rule list: [
[ <18, "Young"],
[ >18, "Adult"]
],
hit policy: "U"
),

/* TODO Please note errors:
line 15:66 no viable alternative at input 'Age'
line 15:79 no viable alternative at input 'History'
*/
biggerDecisionTable : decision table (
outputs: "Applicant Risk Rating",
input expression list: [Applicant Age, Medical History],
rule list: [
[ >60, "good", "Medium" ],
[ >60, "bad", "High" ],
[ [25..60], -, "Medium" ],
[ <25, "good", "Low" ],
[ <25, "bad", "Medium" ]
],
hit policy: "Unique"
),

multipleOutputs : decision table (
outputs: [Out1, Out2],
input expression list: [In1],
rule list: [
[ >1, "out1a", "out2a" ],
[ >2, "out1b", "out2b" ]
],
hit policy: "Unique"
),

// not sure if Input Expression List is expecting a list of Strings or should be some tokens?
multipleInOut : decision table (
outputs: ["Out1", "Out2"],
input expression list: ["In1", "In2"],
rule list: [
[ <47, >1, "io1a", "io2a" ],
[ >0, >0, "io1b", "io2b" ]
],
hit policy: "Unique"
),


result : myDecisionTable( 34 ),
result2: biggerDecisionTable( 35, "good" ),
result3: multipleOutputs(2),
result4: multipleInOut(In2: 47, In1:2),

basic : 1 = 1
}

0 comments on commit f9ffb2f

Please sign in to comment.