From 311930da37af1be7ef24b66c5168456550902d56 Mon Sep 17 00:00:00 2001 From: Edson Tirelli Date: Sat, 13 Aug 2016 22:37:08 -0400 Subject: [PATCH] Implementing FEEL runtime --- .../kie/dmn/feel/lang/ast/ContextNode.java | 6 +- .../kie/dmn/feel/lang/ast/InfixOpNode.java | 3 +- .../lang/impl/CompiledExpressionImpl.java | 1 + .../org/kie/dmn/feel/lang/runtime/FEEL.java | 7 ++ .../dmn/feel/lang/examples/ExamplesTest.java | 90 +++++++++++++++++++ .../kie/dmn/feel/lang/runtime/FEELTest.java | 8 +- .../kie/dmn/feel/lang/examples/applicant.feel | 16 ++++ .../dmn/feel/lang/examples/eligibility.feel | 15 ++++ .../feel/lang/examples/example_10_6_1.feel | 40 +++++++++ .../kie/dmn/feel/lang/runtime/examples.feel | 17 ++++ 10 files changed, 191 insertions(+), 12 deletions(-) create mode 100644 kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/examples/ExamplesTest.java create mode 100644 kie-dmn/kie-dmn-feel/src/test/resources/org/kie/dmn/feel/lang/examples/applicant.feel create mode 100644 kie-dmn/kie-dmn-feel/src/test/resources/org/kie/dmn/feel/lang/examples/eligibility.feel create mode 100644 kie-dmn/kie-dmn-feel/src/test/resources/org/kie/dmn/feel/lang/examples/example_10_6_1.feel create mode 100644 kie-dmn/kie-dmn-feel/src/test/resources/org/kie/dmn/feel/lang/runtime/examples.feel diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/ContextNode.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/ContextNode.java index b090a0690b4..497f6308bde 100644 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/ContextNode.java +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/ContextNode.java @@ -20,9 +20,7 @@ import org.kie.dmn.feel.lang.EvaluationContext; import org.kie.dmn.feel.lang.runtime.functions.CustomFEELFunction; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; +import java.util.*; public class ContextNode extends BaseNode { @@ -52,7 +50,7 @@ public void setEntries(List entries) { public Object evaluate(EvaluationContext ctx) { try { ctx.enterFrame(); - HashMap c = new HashMap<>(); + Map c = new LinkedHashMap<>(); for( ContextEntryNode cen : entries ) { String name = cen.evaluateName( ctx ); Object value = cen.evaluate( ctx ); diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/InfixOpNode.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/InfixOpNode.java index 1d646eece29..442c2875de4 100644 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/InfixOpNode.java +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/InfixOpNode.java @@ -21,6 +21,7 @@ import org.kie.dmn.feel.util.EvalHelper; import java.math.BigDecimal; +import java.math.MathContext; import java.util.function.BiPredicate; import java.util.function.BinaryOperator; @@ -107,7 +108,7 @@ public Object evaluate(EvaluationContext ctx) { case DIV: return math( left, right, ctx, (l, r) -> l.divide( r ) ); case POW: - return math( left, right, ctx, (l, r) -> l.pow( r.intValue() ) ); + return math( left, right, ctx, (l, r) -> l.pow( r.intValue(), MathContext.DECIMAL128 ) ); case AND: return and( left, right, ctx ); case OR: diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/impl/CompiledExpressionImpl.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/impl/CompiledExpressionImpl.java index 2baed9f54cd..a959d7ea52e 100644 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/impl/CompiledExpressionImpl.java +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/impl/CompiledExpressionImpl.java @@ -35,6 +35,7 @@ public ASTNode getExpression() { public Object evaluate(Map inputVariables) { EvaluationContextImpl ctx = new EvaluationContextImpl(); + inputVariables.entrySet().stream().forEach( e -> ctx.setValue( e.getKey(), e.getValue() ) ); return expression.evaluate( ctx ); } diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/runtime/FEEL.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/runtime/FEEL.java index 975acd9dabe..9bd4dbd52c7 100644 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/runtime/FEEL.java +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/runtime/FEEL.java @@ -28,6 +28,7 @@ import org.kie.dmn.feel.lang.types.BuiltInType; import org.kie.dmn.feel.util.EvalHelper; +import java.util.Collections; import java.util.Map; /** @@ -35,6 +36,8 @@ */ public class FEEL { + private static final Map EMPTY_INPUT = Collections.emptyMap(); + public static CompilerContext newCompilerContext() { return new CompilerContextImpl(); } @@ -48,6 +51,10 @@ public static CompiledExpression compile(String expression, CompilerContext ctx) return ce; } + public static Object evaluate(String expression) { + return evaluate( expression, EMPTY_INPUT ); + } + public static Object evaluate(String expression, Map inputVariables) { CompilerContext ctx = newCompilerContext(); if ( inputVariables != null ) { diff --git a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/examples/ExamplesTest.java b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/examples/ExamplesTest.java new file mode 100644 index 00000000000..63ac2f20624 --- /dev/null +++ b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/examples/ExamplesTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates. + * + * Licensed 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.feel.lang.examples; + +import org.junit.Assert; +import org.junit.Test; +import org.kie.dmn.feel.lang.runtime.FEEL; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Map; + +public class ExamplesTest { + + private static final Logger logger = LoggerFactory.getLogger( ExamplesTest.class ); + public static final String DEFAULT_IDENT = " "; + + @Test + public void testLoadApplicantContext() { + String expression = loadExpression( "applicant.feel" ); + Map applicant = (Map) FEEL.evaluate( expression ); + System.out.println( printContext( applicant ) ); + } + + @Test + public void testLoadExample_10_6_1() { + String expression = loadExpression( "example_10_6_1.feel" ); + Map context = (Map) FEEL.evaluate( expression ); + System.out.println( printContext( context ) ); + } + + @Test + public void testLoadExample_10_6_2() { + String expression = loadExpression( "example_10_6_1.feel" ); + Map context = (Map) FEEL.evaluate( expression ); + Number yearlyIncome = (Number) FEEL.evaluate( "monthly income * 12", context ); + System.out.println( "Yearly income = " + yearlyIncome ); + } + + private String loadExpression(String fileName) { + try { + return new String( Files.readAllBytes( Paths.get( getClass().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 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) e.getValue(), ident + DEFAULT_IDENT ) ); + } else { + builder.append( e.getValue() ) + .append( "\n" ); + } + } + builder.append( ident+"}\n" ); + return builder.toString(); + } + +} diff --git a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/runtime/FEELTest.java b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/runtime/FEELTest.java index 0304c7cef89..8a80e0f4652 100644 --- a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/runtime/FEELTest.java +++ b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/runtime/FEELTest.java @@ -73,6 +73,7 @@ public static Collection data() { { "(10 + 20) / (-5 * 3)", EMPTY_INPUT, BigDecimal.valueOf( -2 ) }, { "(10 + 20) / 0", EMPTY_INPUT, null }, { "10 ** 5", EMPTY_INPUT, BigDecimal.valueOf( 100000 ) }, + { "10 ** -5", EMPTY_INPUT, new BigDecimal( "0.00001" ) }, { "(5+2) ** 5", EMPTY_INPUT, BigDecimal.valueOf( 16807 ) }, { "5+2 ** 5", EMPTY_INPUT, BigDecimal.valueOf( 37 ) }, { "5+2 ** 5+3", EMPTY_INPUT, BigDecimal.valueOf( 40 ) }, @@ -314,11 +315,4 @@ public void testExpression() { assertThat( "Evaluating: '"+expression+"'", FEEL.evaluate( expression, inputVariables ), is( result ) ); } } - -// @Test @Ignore( "Java BigDecimals do not support negative numbers as power. Need to figure out what to do." ) -// public void testMathExprPow2() { -// assertThat( FEEL.evaluate( "10 ** -5", EMPTY_INPUT ), is( BigDecimal.valueOf( -0.00001 ) ) ); -// } - - } diff --git a/kie-dmn/kie-dmn-feel/src/test/resources/org/kie/dmn/feel/lang/examples/applicant.feel b/kie-dmn/kie-dmn-feel/src/test/resources/org/kie/dmn/feel/lang/examples/applicant.feel new file mode 100644 index 00000000000..5ab24d36e05 --- /dev/null +++ b/kie-dmn/kie-dmn-feel/src/test/resources/org/kie/dmn/feel/lang/examples/applicant.feel @@ -0,0 +1,16 @@ +/* + * Example context from DMN Spec page 100 + * + * Defines the data of an Applicant + */ +{ + Age : 51, + Marital Status : "M", + Employment Status : "EMPLOYED", + Existing Customer : false, + Monthly : { + Income: 10000.00, + Repayments: 2500.00, + Expenses: 3000.00 + } +} diff --git a/kie-dmn/kie-dmn-feel/src/test/resources/org/kie/dmn/feel/lang/examples/eligibility.feel b/kie-dmn/kie-dmn-feel/src/test/resources/org/kie/dmn/feel/lang/examples/eligibility.feel new file mode 100644 index 00000000000..c9ee2e74084 --- /dev/null +++ b/kie-dmn/kie-dmn-feel/src/test/resources/org/kie/dmn/feel/lang/examples/eligibility.feel @@ -0,0 +1,15 @@ +{ + Age : Applicant.Age, + Monthly Income : Applicant.Monthly.Income, + Pre-Bureau Risk Category : Affordability.Pre-Bureau Risk Category + Installment Affordable : Affordability.Installment Affordable + Eligibility : + if Pre-Bureau Risk Category = "DECLINE" or + Installment Affordable = false or + Age < 18 or + Monthly Income < 100 + then + "INELIGIBLE" + else + "ELIGIBLE" +}.Eligibility diff --git a/kie-dmn/kie-dmn-feel/src/test/resources/org/kie/dmn/feel/lang/examples/example_10_6_1.feel b/kie-dmn/kie-dmn-feel/src/test/resources/org/kie/dmn/feel/lang/examples/example_10_6_1.feel new file mode 100644 index 00000000000..4a6f8307a0a --- /dev/null +++ b/kie-dmn/kie-dmn-feel/src/test/resources/org/kie/dmn/feel/lang/examples/example_10_6_1.feel @@ -0,0 +1,40 @@ +/* + * Example context from DMN Spec page 142 + * + * Defines a complex context + */ +{ + applicant : { + age : 51, + marital status : "M", + employment status : "EMPLOYED", + existing customer : false, + monthly : { + income: 10000.00, + repayments: 2500.00, + expenses: 3000.00 + } + }, + requested product : { + product type : "STANDARD LOAN", + rate : 0.25, + term : 36, + amount : 100000.00 + }, + monthly income : applicant.monthly.income, + monthly outgoings : [ applicant.monthly.repayments, applicant.monthly.expenses ], + credit history : [ + { + record date : date( "2008-03-12" ), + event : "home mortgage", + weight : 100 + }, + { + record date : date( "2011-04-01" ), + event : "foreclosure warning", + weight : 150 + } + ], + PMT : function( rate, term, amount ) + (amount * rate / 12) / ( 1 - ( 1 + rate / 12 ) ** -term ) +} diff --git a/kie-dmn/kie-dmn-feel/src/test/resources/org/kie/dmn/feel/lang/runtime/examples.feel b/kie-dmn/kie-dmn-feel/src/test/resources/org/kie/dmn/feel/lang/runtime/examples.feel new file mode 100644 index 00000000000..5ed311e8a34 --- /dev/null +++ b/kie-dmn/kie-dmn-feel/src/test/resources/org/kie/dmn/feel/lang/runtime/examples.feel @@ -0,0 +1,17 @@ +{ a : 10, + b : 10, + c : a + b, + a + b : 5, + x : a + b + c +} + + +if Pre-Bureau Risk Category = "DECLINE" or + Installment Affordable = false or + Age < 18 or + Monthly Income < 100 +then + "INELIGIBLE" +else + "ELIGIBLE" +