From bb2853757465d1da6c26906907385cf335cfb92d Mon Sep 17 00:00:00 2001 From: mariofusco Date: Tue, 19 Dec 2023 16:55:08 +0100 Subject: [PATCH] [KIE-781] Improve performance of InfixOpNode --- .../kie/dmn/feel/lang/ast/NameRefNode.java | 8 +- .../lang/ast/infixexecutors/AddExecutor.java | 127 +++++++----------- .../infixexecutors/ClassIdentifierTuple.java | 56 -------- .../lang/ast/infixexecutors/DivExecutor.java | 92 ++++++------- .../infixexecutors/EvaluatedParameters.java | 38 ------ .../lang/ast/infixexecutors/MultExecutor.java | 78 ++++++----- .../lang/ast/infixexecutors/SubExecutor.java | 97 +++++++------ .../org/kie/dmn/feel/util/EvalHelper.java | 56 ++++---- .../ClassidentifierTupleTest.java | 35 ----- 9 files changed, 216 insertions(+), 371 deletions(-) delete mode 100644 kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/ClassIdentifierTuple.java delete mode 100644 kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/EvaluatedParameters.java delete mode 100644 kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/infixexecutors/ClassidentifierTupleTest.java diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/NameRefNode.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/NameRefNode.java index 4d79ee526eb..cb4a5b20245 100644 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/NameRefNode.java +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/NameRefNode.java @@ -22,7 +22,6 @@ import org.kie.dmn.api.feel.runtime.events.FEELEvent; import org.kie.dmn.feel.lang.EvaluationContext; import org.kie.dmn.feel.lang.Type; -import org.kie.dmn.feel.util.EvalHelper; import org.kie.dmn.feel.util.Msg; public class NameRefNode @@ -43,12 +42,11 @@ public NameRefNode(ParserRuleContext ctx, String text, Type type) { @Override public Object evaluate(EvaluationContext ctx) { - String varName = EvalHelper.normalizeVariableName( getText() ); - if( ! ctx.isDefined( varName ) ) { + Object result = ctx.getValue( getText() ); + if ( result == null && !ctx.isDefined( getText() ) ) { ctx.notifyEvt( astEvent( FEELEvent.Severity.ERROR, Msg.createMessage( Msg.UNKNOWN_VARIABLE_REFERENCE, getText()), null) ); - return null; } - return ctx.getValue( varName ); + return result; } @Override diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/AddExecutor.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/AddExecutor.java index 3af4d454fa4..3b3e6d348f8 100644 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/AddExecutor.java +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/AddExecutor.java @@ -18,32 +18,26 @@ */ package org.kie.dmn.feel.lang.ast.infixexecutors; +import java.math.BigDecimal; +import java.math.MathContext; +import java.time.Duration; +import java.time.LocalDate; +import java.time.chrono.ChronoPeriod; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAmount; + import org.kie.dmn.api.feel.runtime.events.FEELEvent; import org.kie.dmn.feel.lang.EvaluationContext; import org.kie.dmn.feel.lang.ast.InfixOpNode; -import org.kie.dmn.feel.lang.types.impl.ComparablePeriod; import org.kie.dmn.feel.runtime.events.InvalidParametersEvent; import org.kie.dmn.feel.util.Msg; -import java.math.MathContext; -import java.time.*; -import java.time.chrono.ChronoPeriod; -import java.time.temporal.Temporal; -import java.util.HashMap; -import java.util.Map; -import java.util.function.BiFunction; - import static org.kie.dmn.feel.lang.ast.infixexecutors.InfixExecutorUtils.addLocalDateAndDuration; -import static org.kie.dmn.feel.lang.ast.infixexecutors.InfixExecutorUtils.math; +import static org.kie.dmn.feel.util.EvalHelper.getBigDecimalOrNull; public class AddExecutor implements InfixExecutor { private static final AddExecutor INSTANCE = new AddExecutor(); - private final Map> addFunctionsByClassesTuple; - - private AddExecutor() { - addFunctionsByClassesTuple = getAddFunctionsByClassesTuple(); - } public static AddExecutor instance() { return INSTANCE; @@ -51,7 +45,7 @@ public static AddExecutor instance() { @Override public Object evaluate(Object left, Object right, EvaluationContext ctx) { - return evaluate(new EvaluatedParameters(left, right), ctx); + return add(left, right, ctx); } @Override @@ -59,69 +53,50 @@ public Object evaluate(InfixOpNode infixNode, EvaluationContext ctx) { return evaluate(infixNode.getLeft().evaluate(ctx), infixNode.getRight().evaluate(ctx), ctx); } - private Object evaluate(EvaluatedParameters params, EvaluationContext ctx) { - if (params.getLeft() == null || params.getRight() == null) { + private Object add(Object left, Object right, EvaluationContext ctx) { + if (left == null || right == null) { return null; } - ClassIdentifierTuple identifierTuple = new ClassIdentifierTuple(params.getLeft(), params.getRight()); - if (addFunctionsByClassesTuple.containsKey(identifierTuple)) { - return addFunctionsByClassesTuple.get(identifierTuple).apply(params, ctx); - } else { - return math(params.getLeft(), params.getRight(), ctx, (l, r) -> l.add(r, MathContext.DECIMAL128)); + + if (left instanceof Number) { + BigDecimal leftNumber = getBigDecimalOrNull(left); + return leftNumber != null && right instanceof Number ? + leftNumber.add(getBigDecimalOrNull(right), MathContext.DECIMAL128) : + null; + } + + if (left instanceof String) { + return right instanceof String ? left + (String) right : null; } - } - private Map> getAddFunctionsByClassesTuple() { - Map> toReturn = new HashMap<>(); - toReturn.put(new ClassIdentifierTuple(String.class, String.class), (parameters, ctx) -> parameters.getLeft() + (String) parameters.getRight()); - toReturn.put(new ClassIdentifierTuple(ChronoPeriod.class, ChronoPeriod.class), (parameters, ctx) -> - new ComparablePeriod(((ChronoPeriod) parameters.getLeft()).plus((ChronoPeriod) parameters.getRight()))); - toReturn.put(new ClassIdentifierTuple(Duration.class, Duration.class), (parameters, ctx) -> - ((Duration) parameters.getLeft()).plus((Duration) parameters.getRight())); - toReturn.put(new ClassIdentifierTuple(ZonedDateTime.class, ChronoPeriod.class), (parameters, ctx) -> - ((ZonedDateTime) parameters.getLeft()).plus((ChronoPeriod) parameters.getRight())); - toReturn.put(new ClassIdentifierTuple(OffsetDateTime.class, ChronoPeriod.class), (parameters, ctx) -> - ((OffsetDateTime) parameters.getLeft()).plus((ChronoPeriod) parameters.getRight())); - toReturn.put(new ClassIdentifierTuple(LocalDateTime.class, ChronoPeriod.class), (parameters, ctx) -> - ((LocalDateTime) parameters.getLeft()).plus((ChronoPeriod) parameters.getRight())); - toReturn.put(new ClassIdentifierTuple(LocalDate.class, ChronoPeriod.class), (parameters, ctx) -> - ((LocalDate) parameters.getLeft()).plus((ChronoPeriod) parameters.getRight())); - toReturn.put(new ClassIdentifierTuple(ZonedDateTime.class, Duration.class), (parameters, ctx) -> - ((ZonedDateTime) parameters.getLeft()).plus((Duration) parameters.getRight())); - toReturn.put(new ClassIdentifierTuple(OffsetDateTime.class, Duration.class), (parameters, ctx) -> - ((OffsetDateTime) parameters.getLeft()).plus((Duration) parameters.getRight())); - toReturn.put(new ClassIdentifierTuple(LocalDateTime.class, Duration.class), (parameters, ctx) -> - ((LocalDateTime) parameters.getLeft()).plus((Duration) parameters.getRight())); - toReturn.put(new ClassIdentifierTuple(LocalDate.class, Duration.class), (parameters, ctx) -> - addLocalDateAndDuration((LocalDate) parameters.getLeft(), (Duration) parameters.getRight())); - toReturn.put(new ClassIdentifierTuple(ChronoPeriod.class, ZonedDateTime.class), (parameters, ctx) -> - ((ZonedDateTime) parameters.getRight()).plus((ChronoPeriod) parameters.getLeft())); - toReturn.put(new ClassIdentifierTuple(ChronoPeriod.class, OffsetDateTime.class), (parameters, ctx) -> - ((OffsetDateTime) parameters.getRight()).plus((ChronoPeriod) parameters.getLeft())); - toReturn.put(new ClassIdentifierTuple(ChronoPeriod.class, LocalDateTime.class), (parameters, ctx) -> - ((LocalDateTime) parameters.getRight()).plus((ChronoPeriod) parameters.getLeft())); - toReturn.put(new ClassIdentifierTuple(ChronoPeriod.class, LocalDate.class), (parameters, ctx) -> - ((LocalDate) parameters.getRight()).plus((ChronoPeriod) parameters.getLeft())); - toReturn.put(new ClassIdentifierTuple(Duration.class, ZonedDateTime.class), (parameters, ctx) -> - ((ZonedDateTime) parameters.getRight()).plus((Duration) parameters.getLeft())); - toReturn.put(new ClassIdentifierTuple(Duration.class, OffsetDateTime.class), (parameters, ctx) -> - ((OffsetDateTime) parameters.getRight()).plus((Duration) parameters.getLeft())); - toReturn.put(new ClassIdentifierTuple(Duration.class, LocalDateTime.class), (parameters, ctx) -> - ((LocalDateTime) parameters.getRight()).plus((Duration) parameters.getLeft())); - toReturn.put(new ClassIdentifierTuple(Duration.class, LocalDate.class), (parameters, ctx) -> - addLocalDateAndDuration((LocalDate) parameters.getRight(), (Duration) parameters.getLeft())); - toReturn.put(new ClassIdentifierTuple(LocalTime.class, Duration.class), (parameters, ctx) -> - ((LocalTime) parameters.getLeft()).plus((Duration) parameters.getRight())); - toReturn.put(new ClassIdentifierTuple(Duration.class, LocalTime.class), (parameters, ctx) -> - ((LocalTime) parameters.getRight()).plus((Duration) parameters.getLeft())); - toReturn.put(new ClassIdentifierTuple(OffsetTime.class, Duration.class), (parameters, ctx) -> - ((OffsetTime) parameters.getLeft()).plus((Duration) parameters.getRight())); - toReturn.put(new ClassIdentifierTuple(Duration.class, OffsetTime.class), (parameters, ctx) -> - ((OffsetTime) parameters.getRight()).plus((Duration) parameters.getLeft())); - toReturn.put(new ClassIdentifierTuple(Temporal.class, Temporal.class), (parameters, ctx) -> { - ctx.notifyEvt(() -> new InvalidParametersEvent(FEELEvent.Severity.ERROR, Msg.OPERATION_IS_UNDEFINED_FOR_PARAMETERS.getMask())); - return null; - }); - return toReturn; + if (left instanceof Duration) { + if (right instanceof LocalDate) { + return addLocalDateAndDuration((LocalDate) right, (Duration) left); + } + if (right instanceof Duration) { + return ((Duration) left).plus((Duration) right); + } + } + if (right instanceof Duration && left instanceof LocalDate) { + return addLocalDateAndDuration((LocalDate) left, (Duration) right); + } + + if (left instanceof Temporal) { + if (right instanceof TemporalAmount) { + return ((Temporal) left).plus((TemporalAmount) right); + } + } else if (left instanceof TemporalAmount) { + if (right instanceof Temporal) { + return ((Temporal) right).plus((TemporalAmount) left); + } + if (right instanceof ChronoPeriod) { + return ((ChronoPeriod) right).plus((TemporalAmount) left); + } + } + + ctx.notifyEvt(() -> new InvalidParametersEvent(FEELEvent.Severity.ERROR, Msg.OPERATION_IS_UNDEFINED_FOR_PARAMETERS.getMask())); + return null; } + + } diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/ClassIdentifierTuple.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/ClassIdentifierTuple.java deleted file mode 100644 index bbecdbccfb6..00000000000 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/ClassIdentifierTuple.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * 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.feel.lang.ast.infixexecutors; - -import java.util.Objects; - -public class ClassIdentifierTuple { - - private final Class LEFT_TYPE; - private final Class RIGHT_TYPE; - - - public ClassIdentifierTuple(Object leftObject, Object rightObject) { - this.LEFT_TYPE = leftObject != null ? leftObject.getClass() : null; - this.RIGHT_TYPE = rightObject != null ? rightObject.getClass() : null; - } - - public ClassIdentifierTuple(Class leftType, Class rightType) { - this.LEFT_TYPE = leftType; - this.RIGHT_TYPE = rightType; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ClassIdentifierTuple that = (ClassIdentifierTuple) o; - return isEquals(LEFT_TYPE, that.LEFT_TYPE) && isEquals(RIGHT_TYPE, that.RIGHT_TYPE); - } - - @Override - public int hashCode() { - return 1; - } - - static boolean isEquals(Class thisClass, Class thatClass) { - return (thisClass != null && thatClass != null) && - (Objects.equals(thisClass, thatClass) || thatClass.isAssignableFrom(thisClass)); - } -} diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/DivExecutor.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/DivExecutor.java index 9a2bb13479b..5b8e577da77 100644 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/DivExecutor.java +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/DivExecutor.java @@ -18,33 +18,28 @@ */ package org.kie.dmn.feel.lang.ast.infixexecutors; -import org.kie.dmn.api.feel.runtime.events.FEELEvent; -import org.kie.dmn.feel.lang.EvaluationContext; -import org.kie.dmn.feel.lang.ast.InfixOpNode; -import org.kie.dmn.feel.lang.types.impl.ComparablePeriod; -import org.kie.dmn.feel.runtime.events.InvalidParametersEvent; -import org.kie.dmn.feel.util.EvalHelper; -import org.kie.dmn.feel.util.Msg; - import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.time.Duration; import java.time.chrono.ChronoPeriod; import java.time.temporal.TemporalAmount; -import java.util.HashMap; -import java.util.Map; -import java.util.function.BiFunction; + +import org.kie.dmn.api.feel.runtime.events.FEELEvent; +import org.kie.dmn.feel.lang.EvaluationContext; +import org.kie.dmn.feel.lang.ast.InfixOpNode; +import org.kie.dmn.feel.lang.types.impl.ComparablePeriod; +import org.kie.dmn.feel.runtime.events.InvalidParametersEvent; +import org.kie.dmn.feel.util.Msg; import static org.kie.dmn.feel.lang.ast.infixexecutors.InfixExecutorUtils.math; +import static org.kie.dmn.feel.util.EvalHelper.getBigDecimalOrNull; public class DivExecutor implements InfixExecutor { private static final DivExecutor INSTANCE = new DivExecutor(); - private final Map> sumFunctionsByClassesTuple; private DivExecutor() { - sumFunctionsByClassesTuple = getSumFunctionsByClassesTuple(); } public static DivExecutor instance() { @@ -53,7 +48,7 @@ public static DivExecutor instance() { @Override public Object evaluate(Object left, Object right, EvaluationContext ctx) { - return evaluate(new EvaluatedParameters(left, right), ctx); + return div(left, right, ctx); } @Override @@ -61,42 +56,47 @@ public Object evaluate(InfixOpNode infixNode, EvaluationContext ctx) { return evaluate(infixNode.getLeft().evaluate(ctx), infixNode.getRight().evaluate(ctx), ctx); } - private Object evaluate(EvaluatedParameters params, EvaluationContext ctx) { - if (params.getLeft() == null || params.getRight() == null) { + private Object div(Object left, Object right, EvaluationContext ctx) { + if (left == null || right == null) { return null; } - ClassIdentifierTuple identifierTuple = new ClassIdentifierTuple(params.getLeft(), params.getRight()); - if (sumFunctionsByClassesTuple.containsKey(identifierTuple)) { - return sumFunctionsByClassesTuple.get(identifierTuple).apply(params, ctx); - } else { - return math(params.getLeft(), params.getRight(), ctx, (l, r) -> l.divide(r, MathContext.DECIMAL128)); - } - } - private Map> getSumFunctionsByClassesTuple() { - Map> toReturn = new HashMap<>(); - toReturn.put(new ClassIdentifierTuple(Duration.class, Number.class), (parameters, ctx) -> { - final BigDecimal durationNumericValue = BigDecimal.valueOf(((Duration) parameters.getLeft()).toNanos()); - final BigDecimal rightDecimal = BigDecimal.valueOf(((Number) parameters.getRight()).doubleValue()); - return Duration.ofNanos(durationNumericValue.divide(rightDecimal, 0, RoundingMode.HALF_EVEN).longValue()); - }); - toReturn.put(new ClassIdentifierTuple(Number.class, TemporalAmount.class), (parameters, ctx) -> { - ctx.notifyEvt(() -> new InvalidParametersEvent(FEELEvent.Severity.ERROR, Msg.OPERATION_IS_UNDEFINED_FOR_PARAMETERS.getMask())); + if (left instanceof Number) { + if (right instanceof Number) { + return math(left, right, ctx, (l, r) -> l.divide(r, MathContext.DECIMAL128)); + } + if (right instanceof TemporalAmount) { + ctx.notifyEvt(() -> new InvalidParametersEvent(FEELEvent.Severity.ERROR, Msg.OPERATION_IS_UNDEFINED_FOR_PARAMETERS.getMask())); + } return null; - }); - toReturn.put(new ClassIdentifierTuple(Duration.class, Duration.class), (parameters, ctx) -> - EvalHelper.getBigDecimalOrNull(((Duration) parameters.getLeft()).getSeconds()).divide(EvalHelper.getBigDecimalOrNull(((Duration) parameters.getRight()).getSeconds()), MathContext.DECIMAL128)); - toReturn.put(new ClassIdentifierTuple(ChronoPeriod.class, Number.class), (parameters, ctx) -> { - final BigDecimal rightDecimal = EvalHelper.getBigDecimalOrNull(parameters.getRight()); - if (rightDecimal.compareTo(BigDecimal.ZERO) == 0) { - ctx.notifyEvt(() -> new InvalidParametersEvent(FEELEvent.Severity.ERROR, Msg.DIVISION_BY_ZERO.getMask())); - return null; - } else { - return ComparablePeriod.ofMonths(EvalHelper.getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) parameters.getLeft())).divide(rightDecimal, MathContext.DECIMAL128).intValue()); + } + + if (left instanceof Duration) { + if (right instanceof Number) { + final BigDecimal durationNumericValue = BigDecimal.valueOf(((Duration) left).toNanos()); + final BigDecimal rightDecimal = BigDecimal.valueOf(((Number) right).doubleValue()); + return Duration.ofNanos(durationNumericValue.divide(rightDecimal, 0, RoundingMode.HALF_EVEN).longValue()); + } + if (right instanceof Duration) { + return getBigDecimalOrNull(((Duration) left).getSeconds()).divide(getBigDecimalOrNull(((Duration) right).getSeconds()), MathContext.DECIMAL128); } - }); - toReturn.put(new ClassIdentifierTuple(ChronoPeriod.class, ChronoPeriod.class), (parameters, ctx) -> - EvalHelper.getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) parameters.getLeft())).divide(EvalHelper.getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) parameters.getRight())), MathContext.DECIMAL128)); - return toReturn; + } + + if (left instanceof ChronoPeriod) { + if (right instanceof Number) { + final BigDecimal rightDecimal = getBigDecimalOrNull(right); + if (rightDecimal.compareTo(BigDecimal.ZERO) == 0) { + ctx.notifyEvt(() -> new InvalidParametersEvent(FEELEvent.Severity.ERROR, Msg.DIVISION_BY_ZERO.getMask())); + return null; + } else { + return ComparablePeriod.ofMonths(getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) left)).divide(rightDecimal, MathContext.DECIMAL128).intValue()); + } + } + if (right instanceof ChronoPeriod) { + return getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) left)).divide(getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) right)), MathContext.DECIMAL128); + } + } + + return null; } } diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/EvaluatedParameters.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/EvaluatedParameters.java deleted file mode 100644 index a4f14dfd4bd..00000000000 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/EvaluatedParameters.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * 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.feel.lang.ast.infixexecutors; - -public class EvaluatedParameters { - - private final Object left; - private final Object right; - - public EvaluatedParameters(Object leftObject, Object rightObject) { - this.left = leftObject; - this.right = rightObject; - } - - public Object getLeft() { - return left; - } - - public Object getRight() { - return right; - } - } \ No newline at end of file diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/MultExecutor.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/MultExecutor.java index 3b435ce50b1..2fcab1286cf 100644 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/MultExecutor.java +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/MultExecutor.java @@ -18,29 +18,23 @@ */ package org.kie.dmn.feel.lang.ast.infixexecutors; -import org.kie.dmn.feel.lang.EvaluationContext; -import org.kie.dmn.feel.lang.ast.InfixOpNode; -import org.kie.dmn.feel.lang.types.impl.ComparablePeriod; -import org.kie.dmn.feel.util.EvalHelper; - import java.math.BigDecimal; import java.math.MathContext; -import java.time.*; +import java.time.Duration; import java.time.chrono.ChronoPeriod; -import java.util.HashMap; -import java.util.Map; -import java.util.function.BiFunction; + +import org.kie.dmn.feel.lang.EvaluationContext; +import org.kie.dmn.feel.lang.ast.InfixOpNode; +import org.kie.dmn.feel.lang.types.impl.ComparablePeriod; import static org.kie.dmn.feel.lang.ast.infixexecutors.InfixExecutorUtils.isAllowedMultiplicationBasedOnSpec; -import static org.kie.dmn.feel.lang.ast.infixexecutors.InfixExecutorUtils.math; +import static org.kie.dmn.feel.util.EvalHelper.getBigDecimalOrNull; public class MultExecutor implements InfixExecutor { private static final MultExecutor INSTANCE = new MultExecutor(); - private final Map> multFunctionsByClassesTuple; private MultExecutor() { - multFunctionsByClassesTuple = getMultFunctionsByClassesTuple(); } public static MultExecutor instance() { @@ -49,7 +43,7 @@ public static MultExecutor instance() { @Override public Object evaluate(Object left, Object right, EvaluationContext ctx) { - return evaluate(new EvaluatedParameters(left, right), ctx); + return mult(left, right, ctx); } @Override @@ -57,35 +51,45 @@ public Object evaluate(InfixOpNode infixNode, EvaluationContext ctx) { return evaluate(infixNode.getLeft().evaluate(ctx), infixNode.getRight().evaluate(ctx), ctx); } - private Object evaluate(EvaluatedParameters params, EvaluationContext ctx) { - if ( params.getLeft() == null || params.getRight() == null ) { + private Object mult(Object left, Object right, EvaluationContext ctx) { + if (left == null || right == null) { return null; - } else if (!isAllowedMultiplicationBasedOnSpec(params.getLeft(), params.getRight(), ctx)) { + } + if (!isAllowedMultiplicationBasedOnSpec(left, right, ctx)) { return null; } - ClassIdentifierTuple identifierTuple = new ClassIdentifierTuple(params.getLeft(), params.getRight()); - if (multFunctionsByClassesTuple.containsKey(identifierTuple)) { - return multFunctionsByClassesTuple.get(identifierTuple).apply(params, ctx); - } else { - return math( params.getLeft(), params.getRight(), ctx, (l, r) -> l.multiply( r, MathContext.DECIMAL128 ) ); + + if (left instanceof Number) { + if (right instanceof Number) { + BigDecimal leftNumber = getBigDecimalOrNull(left); + return leftNumber != null && right instanceof Number ? + leftNumber.multiply(getBigDecimalOrNull(right), MathContext.DECIMAL128) : + null; + } + if (right instanceof Duration) { + return Duration.ofSeconds(getBigDecimalOrNull(left).multiply(getBigDecimalOrNull(((Duration) right).getSeconds()), MathContext.DECIMAL128).longValue()); + } + if (right instanceof ChronoPeriod) { + return ComparablePeriod.ofMonths(getBigDecimalOrNull(left).multiply(getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) right)), MathContext.DECIMAL128).intValue()); + } + return null; } - } - private Map> getMultFunctionsByClassesTuple() { - Map> toReturn = new HashMap<>(); - toReturn.put(new ClassIdentifierTuple(Duration.class, Number.class), (parameters, ctx) -> { - final BigDecimal durationNumericValue = BigDecimal.valueOf(((Duration) parameters.getLeft()).toNanos()); - final BigDecimal rightDecimal = BigDecimal.valueOf(((Number) parameters.getRight()).doubleValue()); + if (left instanceof Duration && right instanceof Number) { + final BigDecimal durationNumericValue = BigDecimal.valueOf(((Duration) left).toNanos()); + final BigDecimal rightDecimal = BigDecimal.valueOf(((Number) right).doubleValue()); return Duration.ofNanos(durationNumericValue.multiply(rightDecimal).longValue()); - }); - toReturn.put(new ClassIdentifierTuple(Number.class, Duration.class), (parameters, ctx) -> - Duration.ofSeconds(EvalHelper.getBigDecimalOrNull(parameters.getLeft()).multiply(EvalHelper.getBigDecimalOrNull(((Duration) parameters.getRight()).getSeconds()), MathContext.DECIMAL128).longValue())); - toReturn.put(new ClassIdentifierTuple(ChronoPeriod.class, Number.class), (parameters, ctx) -> - ComparablePeriod.ofMonths(EvalHelper.getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) parameters.getLeft())).multiply(EvalHelper.getBigDecimalOrNull(parameters.getRight()), MathContext.DECIMAL128).intValue())); - toReturn.put(new ClassIdentifierTuple(Number.class, ChronoPeriod.class), (parameters, ctx) -> - ComparablePeriod.ofMonths(EvalHelper.getBigDecimalOrNull(parameters.getLeft()).multiply(EvalHelper.getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) parameters.getRight())), MathContext.DECIMAL128).intValue())); - toReturn.put(new ClassIdentifierTuple(ChronoPeriod.class, ChronoPeriod.class), (parameters, ctx) -> - EvalHelper.getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) parameters.getLeft())).multiply(EvalHelper.getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) parameters.getRight())), MathContext.DECIMAL128)); - return toReturn; + } + + if (left instanceof ChronoPeriod) { + if (right instanceof Number) { + return ComparablePeriod.ofMonths(getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) left)).multiply(getBigDecimalOrNull(right), MathContext.DECIMAL128).intValue()); + } + if (right instanceof ChronoPeriod) { + return getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) left)).multiply(getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) right)), MathContext.DECIMAL128); + } + } + + return null; } } diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/SubExecutor.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/SubExecutor.java index 2516a3c0dbd..46d7c0b6255 100644 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/SubExecutor.java +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/SubExecutor.java @@ -18,28 +18,28 @@ */ package org.kie.dmn.feel.lang.ast.infixexecutors; -import org.kie.dmn.feel.lang.EvaluationContext; -import org.kie.dmn.feel.lang.ast.InfixOpNode; -import org.kie.dmn.feel.lang.types.impl.ComparablePeriod; - +import java.math.BigDecimal; import java.math.MathContext; -import java.time.*; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.chrono.ChronoPeriod; import java.time.temporal.Temporal; -import java.util.HashMap; -import java.util.Map; -import java.util.function.BiFunction; +import java.time.temporal.TemporalAmount; + +import org.kie.dmn.feel.lang.EvaluationContext; +import org.kie.dmn.feel.lang.ast.InfixOpNode; +import org.kie.dmn.feel.lang.types.impl.ComparablePeriod; -import static org.kie.dmn.feel.lang.ast.infixexecutors.InfixExecutorUtils.math; import static org.kie.dmn.feel.lang.ast.infixexecutors.InfixExecutorUtils.subtractTemporals; +import static org.kie.dmn.feel.util.EvalHelper.getBigDecimalOrNull; public class SubExecutor implements InfixExecutor { private static final SubExecutor INSTANCE = new SubExecutor(); - private final Map> subFunctionsByClassesTuple; private SubExecutor() { - subFunctionsByClassesTuple = getSubFunctionsByClassesTuple(); } public static SubExecutor instance() { @@ -48,7 +48,7 @@ public static SubExecutor instance() { @Override public Object evaluate(Object left, Object right, EvaluationContext ctx) { - return evaluate(new EvaluatedParameters(left, right), ctx); + return sub(left, right, ctx); } @Override @@ -56,49 +56,42 @@ public Object evaluate(InfixOpNode infixNode, EvaluationContext ctx) { return evaluate(infixNode.getLeft().evaluate(ctx), infixNode.getRight().evaluate(ctx), ctx); } - private Object evaluate(EvaluatedParameters params, EvaluationContext ctx) { - if (params.getLeft() == null || params.getRight() == null) { + private Object sub(Object left, Object right, EvaluationContext ctx) { + if (left == null || right == null) { return null; } - ClassIdentifierTuple identifierTuple = new ClassIdentifierTuple(params.getLeft(), params.getRight()); - if (subFunctionsByClassesTuple.containsKey(identifierTuple)) { - return subFunctionsByClassesTuple.get(identifierTuple).apply(params, ctx); - } else { - return math(params.getLeft(), params.getRight(), ctx, (l, r) -> l.subtract(r, MathContext.DECIMAL128)); + + if (left instanceof Number) { + BigDecimal leftNumber = getBigDecimalOrNull(left); + return leftNumber != null && right instanceof Number ? + leftNumber.subtract(getBigDecimalOrNull(right), MathContext.DECIMAL128) : + null; + } + + if (right instanceof Duration) { + if (left instanceof LocalDate) { + LocalDateTime leftLDT = LocalDateTime.of((LocalDate) left, LocalTime.MIDNIGHT); + LocalDateTime evaluated = leftLDT.minus((Duration) right); + return LocalDate.of(evaluated.getYear(), evaluated.getMonth(), evaluated.getDayOfMonth()); + } + if (left instanceof Duration) { + return ((Duration) left).minus((Duration) right); + } + } + + if (left instanceof Temporal) { + if (right instanceof Temporal) { + return subtractTemporals((Temporal) left, (Temporal) right, ctx); + } + if (right instanceof TemporalAmount) { + return ((Temporal) left).minus((TemporalAmount) right); + } + } + + if (left instanceof ChronoPeriod && right instanceof ChronoPeriod) { + return new ComparablePeriod(((ChronoPeriod) left).minus((ChronoPeriod) right)); } - } - private Map> getSubFunctionsByClassesTuple() { - Map> toReturn = new HashMap<>(); - toReturn.put(new ClassIdentifierTuple(Temporal.class, Temporal.class), (parameters, ctx) -> - subtractTemporals((Temporal) parameters.getLeft(), (Temporal) parameters.getRight(), ctx)); - toReturn.put(new ClassIdentifierTuple(ChronoPeriod.class, ChronoPeriod.class), (parameters, ctx) -> - new ComparablePeriod(((ChronoPeriod) parameters.getLeft()).minus((ChronoPeriod) parameters.getRight()))); - toReturn.put(new ClassIdentifierTuple(Duration.class, Duration.class), (parameters, ctx) -> - ((Duration) parameters.getLeft()).minus((Duration) parameters.getRight())); - toReturn.put(new ClassIdentifierTuple(ZonedDateTime.class, ChronoPeriod.class), (parameters, ctx) -> - ((ZonedDateTime) parameters.getLeft()).minus((ChronoPeriod) parameters.getRight())); - toReturn.put(new ClassIdentifierTuple(OffsetDateTime.class, ChronoPeriod.class), (parameters, ctx) -> - ((OffsetDateTime) parameters.getLeft()).minus((ChronoPeriod) parameters.getRight())); - toReturn.put(new ClassIdentifierTuple(LocalDateTime.class, ChronoPeriod.class), (parameters, ctx) -> - ((LocalDateTime) parameters.getLeft()).minus((ChronoPeriod) parameters.getRight())); - toReturn.put(new ClassIdentifierTuple(LocalDate.class, ChronoPeriod.class), (parameters, ctx) -> - ((LocalDate) parameters.getLeft()).minus((ChronoPeriod) parameters.getRight())); - toReturn.put(new ClassIdentifierTuple(ZonedDateTime.class, Duration.class), (parameters, ctx) -> - ((ZonedDateTime) parameters.getLeft()).minus((Duration) parameters.getRight())); - toReturn.put(new ClassIdentifierTuple(OffsetDateTime.class, Duration.class), (parameters, ctx) -> - ((OffsetDateTime) parameters.getLeft()).minus((Duration) parameters.getRight())); - toReturn.put(new ClassIdentifierTuple(LocalDateTime.class, Duration.class), (parameters, ctx) -> - ((LocalDateTime) parameters.getLeft()).minus((Duration) parameters.getRight())); - toReturn.put(new ClassIdentifierTuple(LocalDate.class, Duration.class), (parameters, ctx) -> { - LocalDateTime leftLDT = LocalDateTime.of((LocalDate) parameters.getLeft(), LocalTime.MIDNIGHT); - LocalDateTime evaluated = leftLDT.minus((Duration) parameters.getRight()); - return LocalDate.of(evaluated.getYear(), evaluated.getMonth(), evaluated.getDayOfMonth()); - }); - toReturn.put(new ClassIdentifierTuple(LocalTime.class, Duration.class), (parameters, ctx) -> - ((LocalTime) parameters.getLeft()).minus((Duration) parameters.getRight())); - toReturn.put(new ClassIdentifierTuple(OffsetTime.class, Duration.class), (parameters, ctx) -> - ((OffsetTime) parameters.getLeft()).minus((Duration) parameters.getRight())); - return toReturn; + return null; } } diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/EvalHelper.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/EvalHelper.java index 5b57a19b0b0..3e4a3e00da7 100644 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/EvalHelper.java +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/EvalHelper.java @@ -41,8 +41,6 @@ import java.util.Optional; import java.util.TimeZone; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; import java.util.function.BiPredicate; import java.util.stream.Stream; @@ -150,33 +148,39 @@ private static boolean isValidChar(char c) { } public static BigDecimal getBigDecimalOrNull(Object value) { - if (!(value instanceof Number - || value instanceof String) - || (value instanceof Double - && (value.toString().equals("NaN") || value.toString().equals("Infinity") || value.toString().equals("-Infinity")))) { - return null; + if ( value instanceof BigDecimal ) { + return (BigDecimal) value; } - if ( !BigDecimal.class.isAssignableFrom( value.getClass() ) ) { - if ( value instanceof Long || value instanceof Integer || value instanceof Short || value instanceof Byte || - value instanceof AtomicLong || value instanceof AtomicInteger ) { - value = new BigDecimal( ((Number) value).longValue(), MathContext.DECIMAL128 ); - } else if ( value instanceof BigInteger ) { - value = new BigDecimal( (BigInteger) value, MathContext.DECIMAL128 ); - } else if ( value instanceof String ) { - try { - // we need to remove leading zeros to prevent octal conversion - value = new BigDecimal( ((String) value).replaceFirst("^0+(?!$)", ""), MathContext.DECIMAL128 ); - } catch (NumberFormatException e) { - return null; - } - } else { - // doubleValue() sometimes produce rounding errors, so we need to use toString() instead - // We also need to remove trailing zeros, if there are some so for 10d we get BigDecimal.valueOf(10) - // instead of BigDecimal.valueOf(10.0). - value = new BigDecimal( removeTrailingZeros(value.toString()), MathContext.DECIMAL128 ); + + if ( value instanceof BigInteger ) { + return new BigDecimal((BigInteger) value, MathContext.DECIMAL128); + } + + if ( value instanceof Double || value instanceof Float ) { + String stringVal = value.toString(); + if (stringVal.equals("NaN") || stringVal.equals("Infinity") || stringVal.equals("-Infinity")) { + return null; } + // doubleValue() sometimes produce rounding errors, so we need to use toString() instead + // We also need to remove trailing zeros, if there are some so for 10d we get BigDecimal.valueOf(10) + // instead of BigDecimal.valueOf(10.0). + return new BigDecimal( removeTrailingZeros(value.toString()), MathContext.DECIMAL128 ); } - return (BigDecimal) value; + + if ( value instanceof Number ) { + return new BigDecimal( ((Number) value).longValue(), MathContext.DECIMAL128 ); + } + + if ( value instanceof String ) { + try { + // we need to remove leading zeros to prevent octal conversion + return new BigDecimal(((String) value).replaceFirst("^0+(?!$)", ""), MathContext.DECIMAL128); + } catch (NumberFormatException e) { + return null; + } + } + + return null; } public static Object coerceNumber(Object value) { diff --git a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/infixexecutors/ClassidentifierTupleTest.java b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/infixexecutors/ClassidentifierTupleTest.java deleted file mode 100644 index 70ecd6b7d25..00000000000 --- a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/infixexecutors/ClassidentifierTupleTest.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * 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.feel.lang.ast.infixexecutors; - -import org.junit.Test; -import org.kie.dmn.feel.lang.types.impl.ComparablePeriod; - -import java.time.chrono.ChronoPeriod; - -import static org.junit.Assert.assertTrue; - - -public class ClassidentifierTupleTest { - - @Test - public void testClassidentifierTuple_isEquals() { - assertTrue(ClassIdentifierTuple.isEquals(ComparablePeriod.class, ChronoPeriod.class)); - } -} \ No newline at end of file