Copilot commented on code in PR #6519: URL: https://github.com/apache/incubator-kie-drools/pull/6519#discussion_r2540888936
########## kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/dialectHandlers/BFEELDialectHandler.java: ########## @@ -0,0 +1,326 @@ +/* + * 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.dialectHandlers; + +import org.kie.dmn.feel.lang.EvaluationContext; +import org.kie.dmn.feel.lang.FEELDialect; +import org.kie.dmn.feel.lang.types.impl.ComparablePeriod; +import org.kie.dmn.feel.util.BooleanEvalHelper; + +import java.math.BigDecimal; +import java.time.Duration; +import java.time.LocalDate; +import java.time.chrono.ChronoPeriod; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.BiFunction; + +import static org.kie.dmn.feel.lang.ast.infixexecutors.InfixExecutorUtils.*; +import static org.kie.dmn.feel.util.BooleanEvalHelper.evalRight; + +/** + * Handler implementation of the DialectHandler interface providing BFEEL specific + * functionalities + */ +public class BFEELDialectHandler extends DefaultDialectHandler implements DialectHandler { + + /** + * Builds the BFeel specific 'Addition' operations. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the BFeel specific 'Addition' operations + */ + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getAddOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + + // any String operand → concatenate both as strings + map.put( + new CheckedPredicate((left, right) -> left instanceof String || right instanceof String, false), + (left, right) -> { + String leftNum = getString(left); + String rightNum = getString(right); + return leftNum + rightNum; + } + ); + + // date + number → return the number + map.put( + new CheckedPredicate((left, right) -> left instanceof LocalDate && right instanceof Number, false), + (left, right) -> right + ); + map.put( + new CheckedPredicate((left, right) -> left instanceof Number && right instanceof LocalDate, false), + (left, right) -> left + ); + + // Number + null → number + map.put( + new CheckedPredicate((left, right) -> left instanceof Number && right == null, false), + (left, right) -> left + ); + + // null + Number → number + map.put( + new CheckedPredicate((left, right) -> left == null && right instanceof Number, false), + (left, right) -> right + ); + + map.putAll(getCommonAddOperations(ctx)); + return map; + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getAndOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + // Special case: true AND otherwise → false + map.put( + new CheckedPredicate((left, right) -> { + Boolean leftBool = BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect); + Object rightValue = evalRight(right, ctx); + Boolean rightBool = BooleanEvalHelper.getBooleanOrDialectDefault(rightValue, dialect); + return Boolean.TRUE.equals(leftBool) && Boolean.FALSE.equals(rightBool); + }, false), + (left, right) -> Boolean.FALSE + ); + + // Special case: otherwise AND true → false + map.put( + new CheckedPredicate((left, right) -> { + Boolean leftBool = BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect); + Object rightValue = evalRight(right, ctx); + Boolean rightBool = BooleanEvalHelper.getBooleanOrDialectDefault(rightValue, dialect); + return leftBool == null && Boolean.TRUE.equals(rightBool); + }, false), + (left, right) -> Boolean.FALSE + ); + + // Special case: otherwise AND otherwise → false + map.put( + new CheckedPredicate((left, right) -> { + Boolean leftBool = BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect); + Object rightValue = evalRight(right, ctx); + Boolean rightBool = BooleanEvalHelper.getBooleanOrDialectDefault(rightValue, dialect); + return leftBool == null && Boolean.FALSE.equals(rightBool); + }, false), + (left, right) -> Boolean.FALSE + ); + map.putAll(getCommonAddOperations(ctx)); Review Comment: The `getAndOperations` method calls `getCommonAddOperations(ctx)` instead of `getCommonAndOperations(ctx)`. This is a copy-paste error that will cause the AND operations to incorrectly include addition operation logic. Should be: ```java map.putAll(getCommonAndOperations(ctx)); ``` ```suggestion map.putAll(getCommonAndOperations(ctx)); ``` ########## kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/dialectHandlers/BFEELDialectHandler.java: ########## @@ -0,0 +1,326 @@ +/* + * 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.dialectHandlers; + +import org.kie.dmn.feel.lang.EvaluationContext; +import org.kie.dmn.feel.lang.FEELDialect; +import org.kie.dmn.feel.lang.types.impl.ComparablePeriod; +import org.kie.dmn.feel.util.BooleanEvalHelper; + +import java.math.BigDecimal; +import java.time.Duration; +import java.time.LocalDate; +import java.time.chrono.ChronoPeriod; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.BiFunction; + +import static org.kie.dmn.feel.lang.ast.infixexecutors.InfixExecutorUtils.*; +import static org.kie.dmn.feel.util.BooleanEvalHelper.evalRight; + +/** + * Handler implementation of the DialectHandler interface providing BFEEL specific + * functionalities + */ +public class BFEELDialectHandler extends DefaultDialectHandler implements DialectHandler { + + /** + * Builds the BFeel specific 'Addition' operations. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the BFeel specific 'Addition' operations + */ + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getAddOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + + // any String operand → concatenate both as strings + map.put( + new CheckedPredicate((left, right) -> left instanceof String || right instanceof String, false), + (left, right) -> { + String leftNum = getString(left); + String rightNum = getString(right); + return leftNum + rightNum; + } + ); + + // date + number → return the number + map.put( + new CheckedPredicate((left, right) -> left instanceof LocalDate && right instanceof Number, false), + (left, right) -> right + ); + map.put( + new CheckedPredicate((left, right) -> left instanceof Number && right instanceof LocalDate, false), + (left, right) -> left + ); + + // Number + null → number + map.put( + new CheckedPredicate((left, right) -> left instanceof Number && right == null, false), + (left, right) -> left + ); + + // null + Number → number + map.put( + new CheckedPredicate((left, right) -> left == null && right instanceof Number, false), + (left, right) -> right + ); + + map.putAll(getCommonAddOperations(ctx)); + return map; + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getAndOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + // Special case: true AND otherwise → false + map.put( + new CheckedPredicate((left, right) -> { + Boolean leftBool = BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect); + Object rightValue = evalRight(right, ctx); + Boolean rightBool = BooleanEvalHelper.getBooleanOrDialectDefault(rightValue, dialect); + return Boolean.TRUE.equals(leftBool) && Boolean.FALSE.equals(rightBool); + }, false), + (left, right) -> Boolean.FALSE + ); + + // Special case: otherwise AND true → false + map.put( + new CheckedPredicate((left, right) -> { + Boolean leftBool = BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect); + Object rightValue = evalRight(right, ctx); + Boolean rightBool = BooleanEvalHelper.getBooleanOrDialectDefault(rightValue, dialect); + return leftBool == null && Boolean.TRUE.equals(rightBool); + }, false), + (left, right) -> Boolean.FALSE Review Comment: The predicate for "Special case: otherwise AND true → false" is checking if `leftBool == null && Boolean.TRUE.equals(rightBool)` and returning `Boolean.FALSE`. However, according to standard FEEL semantics, `null AND true` should return `null`, not `false`. This override for BFEEL needs verification against the BFEEL specification. ```suggestion (left, right) -> null ``` ########## kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/dialectHandlers/DefaultDialectHandler.java: ########## @@ -0,0 +1,716 @@ +/* + * 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.dialectHandlers; + +import ch.obermuhlner.math.big.BigDecimalMath; +import org.kie.dmn.api.feel.runtime.events.FEELEvent; +import org.kie.dmn.feel.lang.EvaluationContext; +import org.kie.dmn.feel.lang.FEELDialect; +import org.kie.dmn.feel.lang.ast.infixexecutors.InfixExecutorUtils; +import org.kie.dmn.feel.lang.types.impl.ComparablePeriod; +import org.kie.dmn.feel.runtime.events.InvalidParametersEvent; +import org.kie.dmn.feel.util.BooleanEvalHelper; +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.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.chrono.ChronoPeriod; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAmount; +import java.util.*; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; + +import static org.kie.dmn.feel.lang.ast.infixexecutors.InfixExecutorUtils.*; +import static org.kie.dmn.feel.util.BooleanEvalHelper.evalRight; +import static org.kie.dmn.feel.util.NumberEvalHelper.getBigDecimalOrNull; + +/** + * Base implementation of the DialectHandler interface providing common + * functionality for all dialects. + */ +public abstract class DefaultDialectHandler implements DialectHandler { + + /** + * Builds the common 'Addition' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Addition' operations + */ + protected Map<CheckedPredicate, BiFunction<Object, Object, Object>> getCommonAddOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + + // Number + Number + map.put( + new CheckedPredicate((left, right) -> left instanceof Number && right instanceof Number, false), + (left, right) -> { + BigDecimal leftNum = getBigDecimalOrNull(left); + BigDecimal rightNum = getBigDecimal(right, ctx); + return leftNum != null && rightNum != null ? leftNum.add(rightNum, MathContext.DECIMAL128) : null; + } + ); + + // Duration + LocalDate + map.put( + new CheckedPredicate((left, right) -> left instanceof Duration && right instanceof LocalDate, false), + (left, right) -> addLocalDateAndDuration((LocalDate) right, (Duration) left) + ); + + // LocalDate + Duration + map.put( + new CheckedPredicate((left, right) -> left instanceof LocalDate && right instanceof Duration, false), + (left, right) -> addLocalDateAndDuration((LocalDate) left, (Duration) right) + ); + + // Duration + Duration + map.put( + new CheckedPredicate((left, right) -> left instanceof Duration && right instanceof Duration, false), + (left, right) -> ((Duration) left).plus((Duration) right) + ); + + // Temporal + TemporalAmount + map.put( + new CheckedPredicate((left, right) -> left instanceof Temporal && right instanceof TemporalAmount, false), + (left, right) -> ((Temporal) left).plus((TemporalAmount) right) + ); + + // TemporalAmount + Temporal + map.put( + new CheckedPredicate((left, right) -> left instanceof TemporalAmount && right instanceof Temporal, false), + (left, right) -> ((Temporal) right).plus((TemporalAmount) left) + ); + + // TemporalAmount + ChronoPeriod + map.put( + new CheckedPredicate((left, right) -> left instanceof TemporalAmount && right instanceof ChronoPeriod, false), + (left, right) -> ((ChronoPeriod) right).plus((TemporalAmount) left) + ); + + // left or right -> null + map.put( + new CheckedPredicate((left, right) -> left == null || right == null, false), + (left, right) -> null + ); + return map; + } + + + /** + * Builds the common 'And' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'And' operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonAndOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + + // false AND anything → false (short‑circuit) + map.put( + new CheckedPredicate((left, right) -> Boolean.FALSE.equals(BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect)), false), + (left, right) -> Boolean.FALSE + ); + + // true AND true → true + map.put( + new CheckedPredicate((left, right) -> Boolean.TRUE.equals(BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect)) + && Boolean.TRUE.equals(evalRight(right, ctx)), false), + (left, right) -> Boolean.TRUE + ); + + // true AND false → false + map.put( + new CheckedPredicate((left, right) -> Boolean.TRUE.equals(BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect)) + && Boolean.FALSE.equals(evalRight(right, ctx)), false), + (left, right) -> Boolean.FALSE + ); + + // true AND otherwise → null + map.put( + new CheckedPredicate((left, right) -> Boolean.TRUE.equals(BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect)) + && evalRight(right, ctx) == null, false), + (left, right) -> null + ); + + // otherwise AND true → null + map.put( + new CheckedPredicate((left, right) -> BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect) == null + && Boolean.TRUE.equals(evalRight(right, ctx)), false), + (left, right) -> null + ); + + // otherwise AND false → false + map.put( + new CheckedPredicate((left, right) -> BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect) == null + && Boolean.FALSE.equals(evalRight(right, ctx)), false), + (left, right) -> Boolean.FALSE + ); + + // otherwise AND otherwise → null + map.put( + new CheckedPredicate((left, right) -> BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect) == null + && evalRight(right, ctx) == null, false), + (left, right) -> null + ); + + return map; + } + + /** + * Builds the common 'Equal' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Equal' operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonEqualOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + + map.put( + new CheckedPredicate((left, right) -> true, false), + (left, right) -> BooleanEvalHelper.isEqual(left, right, dialect) + ); + return map; + } + + /** + * Builds the common 'Greater than Or EqualTo' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Greater than Or EqualTo' operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonGteOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + + // left > right OR left == right + map.put( + new CheckedPredicate((left, right) -> true, false), + (left, right) -> InfixExecutorUtils.or( + BooleanEvalHelper.compare(left, right, dialect, (leftNum, rightNum) -> leftNum.compareTo(rightNum) > 0), + BooleanEvalHelper.isEqual(left, right, dialect), + ctx + ) + ); + return map; + } + + /** + * Builds the common 'Greater than' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Greater than' operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonGtOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + + // left > right + map.put( + new CheckedPredicate((left, right) -> true, false), + (left, right) -> BooleanEvalHelper.compare(left, right, dialect, (leftNum, rightNum) -> leftNum.compareTo(rightNum) > 0) + ); + return map; + } + + /** + * Builds the common 'Less than Or EqualTo' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Less than Or EqualTo' operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonLteOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + + // left < right OR left == right + map.put( + new CheckedPredicate((left, right) -> true, false), + (left, right) -> InfixExecutorUtils.or( + BooleanEvalHelper.compare(left, right, dialect, (leftNum, rightNum) -> leftNum.compareTo(rightNum) < 0), + BooleanEvalHelper.isEqual(left, right, dialect), + ctx + ) + ); + return map; + } + + /** + * Builds the common 'Less than' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Less than Or EqualTo' operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonLtOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + + // left < right + map.put( + new CheckedPredicate((left, right) -> true, false), + (left, right) -> BooleanEvalHelper.compare(left, right, dialect, (leftNum, rightNum) -> leftNum.compareTo(rightNum) < 0) + ); + return map; + } + + /** + * Builds the common 'Not EqualTo' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Not EqualTo' operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonNotEqualOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + + map.put( + new CheckedPredicate((left, right) -> true, false), + (left, right) -> { + Boolean result = BooleanEvalHelper.isEqual(left, right, dialect); + return result != null ? !result : null; + } + ); + return map; + } + + /** + * Builds the common 'Or' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Or operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonOrOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + + // true OR anything → true (short‑circuit) + map.put( + new CheckedPredicate((left, right) -> Boolean.TRUE.equals(BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect)), false), + (left, right) -> Boolean.TRUE + ); + + // false OR true → true + map.put( + new CheckedPredicate((left, right) -> Boolean.FALSE.equals(BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect)) + && Boolean.TRUE.equals(evalRight(right, ctx)), false), + (left, right) -> Boolean.TRUE + ); + + // false OR false → false + map.put( + new CheckedPredicate((left, right) -> Boolean.FALSE.equals(BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect)) + && Boolean.FALSE.equals(evalRight(right, ctx)), false), + (left, right) -> Boolean.FALSE + ); + + // false OR otherwise → null + map.put( + new CheckedPredicate((left, right) -> Boolean.FALSE.equals(BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect)) + && evalRight(right, ctx) == null, false), + (left, right) -> null + ); + + // otherwise OR true → true + map.put( + new CheckedPredicate((left, right) -> BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect) == null + && Boolean.TRUE.equals(evalRight(right, ctx)), false), + (left, right) -> Boolean.TRUE + ); + + // otherwise OR false → null + map.put( + new CheckedPredicate((left, right) -> BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect) == null + && Boolean.FALSE.equals(evalRight(right, ctx)), false), + (left, right) -> null + ); + + // otherwise OR otherwise → null + map.put( + new CheckedPredicate((left, right) -> BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect) == null + && evalRight(right, ctx) == null, false), + (left, right) -> null + ); + + return map; + } + + /** + * Builds the common 'Power of' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Power of' operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonPowOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + map.put( + new CheckedPredicate((left, right) -> true, false), + (left, right) -> math(left, right, ctx, + (l, r) -> BigDecimalMath.pow(l, r, MathContext.DECIMAL128)) + ); + return map; + } + + /** + * Builds the common 'Substraction' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Substraction' operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonSubOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + // null - Number + map.put( + new CheckedPredicate((left, right) -> + (left == null && right instanceof Number) || (right == null && left instanceof Number), false), + (left, right) -> { + BigDecimal leftNum = getBigDecimal(left, ctx); + BigDecimal rightNum = getBigDecimal(right, ctx); + return leftNum != null && rightNum != null ? leftNum.subtract(rightNum, MathContext.DECIMAL128) : null; + } + ); + + // Number - Number + map.put( + new CheckedPredicate((left, right) -> left instanceof Number && right instanceof Number, false), + (left, right) -> { + BigDecimal leftNum = getBigDecimal(left, ctx); + BigDecimal rightNum = getBigDecimal(right, ctx); + return leftNum != null && rightNum != null ? leftNum.subtract(rightNum, MathContext.DECIMAL128) : null; + } + ); + + // LocalDate - Duration + map.put( + new CheckedPredicate((left, right) -> left instanceof LocalDate && right instanceof Duration, false), + (left, right) -> { + LocalDateTime leftLDT = LocalDateTime.of((LocalDate) left, LocalTime.MIDNIGHT); + LocalDateTime evaluated = leftLDT.minus((Duration) right); + return LocalDate.of(evaluated.getYear(), evaluated.getMonth(), evaluated.getDayOfMonth()); + } + ); + + // Duration - Duration + map.put( + new CheckedPredicate((left, right) -> left instanceof Duration && right instanceof Duration, false), + (left, right) -> ((Duration) left).minus((Duration) right) + ); + + // Temporal - Temporal + map.put( + new CheckedPredicate((left, right) -> left instanceof Temporal && right instanceof Temporal, false), + (left, right) -> subtractTemporals((Temporal) left, (Temporal) right, ctx) + ); + + // Temporal - TemporalAmount + map.put( + new CheckedPredicate((left, right) -> left instanceof Temporal && right instanceof TemporalAmount, false), + (left, right) -> ((Temporal) left).minus((TemporalAmount) right) + ); + + // ChronoPeriod - ChronoPeriod + map.put( + new CheckedPredicate((left, right) -> left instanceof ChronoPeriod && right instanceof ChronoPeriod, false), + (left, right) -> new ComparablePeriod(((ChronoPeriod) left).minus((ChronoPeriod) right)) + ); + + + // left == null || right == null + map.put( + new CheckedPredicate((left, right) -> left == null || right == null, false), + (left, right) -> null + ); + + return map; + } + + /** + * Builds the common 'Multiplication' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Multiplication operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonMultOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + // Number * Number + map.put( + new CheckedPredicate((left, right) -> left instanceof Number && right instanceof Number, false), + (left, right) -> { + BigDecimal leftNum = getBigDecimalOrNull(left); + BigDecimal rightNum = getBigDecimal(right, ctx); + return leftNum != null && rightNum != null ? leftNum.multiply(rightNum, MathContext.DECIMAL128) : null; + } + ); + + // Number * Duration + map.put( + new CheckedPredicate((left, right) -> left instanceof Number && right instanceof Duration, false), + (left, right) -> Duration.ofSeconds( + getBigDecimalOrNull(left) + .multiply(BigDecimal.valueOf(((Duration) right).getSeconds()), MathContext.DECIMAL128) + .longValue() + ) + ); + + // Number * ChronoPeriod + map.put( + new CheckedPredicate((left, right) -> left instanceof Number && right instanceof ChronoPeriod, false), + (left, right) -> ComparablePeriod.ofMonths( + getBigDecimalOrNull(left) + .multiply(getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) right)), MathContext.DECIMAL128) + .intValue() + ) + ); + + // Duration * Number + map.put( + new CheckedPredicate((left, right) -> left instanceof Duration && right instanceof Number, false), + (left, right) -> { + BigDecimal durationNumericValue = BigDecimal.valueOf(((Duration) left).toNanos()); + BigDecimal rightDecimal = BigDecimal.valueOf(((Number) right).doubleValue()); + return Duration.ofNanos(durationNumericValue.multiply(rightDecimal).longValue()); + } + ); + // ChronoPeriod * Number + map.put( + new CheckedPredicate((left, right) -> left instanceof ChronoPeriod && right instanceof Number, false), + (left, right) -> { + BigDecimal rightNumber = getBigDecimal(right, ctx); + return ComparablePeriod.ofMonths( + getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) left)) + .multiply(rightNumber, MathContext.DECIMAL128) + .intValue() + ); + } + ); + + + // left or right == null + map.put( + new CheckedPredicate((left, right) -> left == null || right == null, false), + (left, right) -> null + ); + + return map; + } + + /** + * Builds the common 'Division' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Division' operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonDivisionOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + + // Number ÷ Number + map.put( + new CheckedPredicate((left,right) -> left instanceof Number && right instanceof Number + && getBigDecimalOrNull(right) != null + && getBigDecimalOrNull(right).compareTo(BigDecimal.ZERO) != 0, + false), + (left,right) -> { + BigDecimal leftBD = getBigDecimalOrNull(left); + BigDecimal rightBD = getBigDecimalOrNull(right); + return leftBD.divide(rightBD, MathContext.DECIMAL128); + } + ); + + // Number ÷ Number , Division by zero case → notify + map.put( + new CheckedPredicate((left,right) -> left instanceof Number && right instanceof Number + && getBigDecimalOrNull(right) != null + && getBigDecimalOrNull(right).compareTo(BigDecimal.ZERO) == 0, + true), + (left,right) -> null + ); + + // duration ÷ number + map.put(new CheckedPredicate((left,right) -> left instanceof Duration && right instanceof Number, false), + (left,right) -> { + Duration dur = (Duration) left; + BigDecimal nanos = BigDecimal.valueOf(dur.toNanos()); + BigDecimal divisor = getBigDecimalOrNull(right); + if (divisor==null || divisor.compareTo(BigDecimal.ZERO)==0) return null; + BigDecimal scaled = nanos.divide(divisor, 0, RoundingMode.HALF_EVEN); + return Duration.ofNanos(scaled.longValue()); + } + ); + + // duration ÷ duration → number + map.put(new CheckedPredicate((left,right) -> left instanceof Duration && right instanceof Duration, false), + (left,right) -> { + BigDecimal leftSecs = getBigDecimalOrNull(((Duration) left).getSeconds()); + BigDecimal rightSecs = getBigDecimalOrNull(((Duration) right).getSeconds()); + if (leftSecs==null || rightSecs==null || rightSecs.compareTo(BigDecimal.ZERO)==0) return null; + return leftSecs.divide(rightSecs, MathContext.DECIMAL128); + } + ); + + // period ÷ number + map.put(new CheckedPredicate((left,right) -> left instanceof ChronoPeriod && right instanceof Number, true), + (left,right) -> { + BigDecimal months = getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) left)); + BigDecimal divisor = getBigDecimalOrNull(right); + if (months==null || divisor==null || divisor.compareTo(BigDecimal.ZERO)==0) return null; + BigDecimal scaled = months.divide(divisor, MathContext.DECIMAL128); + return ComparablePeriod.ofMonths(scaled.intValue()); + } + ); + + // period ÷ period → number + map.put(new CheckedPredicate((left,right) -> left instanceof ChronoPeriod && right instanceof ChronoPeriod, false), + (left,right) -> { + BigDecimal leftMonths = getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) left)); + BigDecimal rightMonths = getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) right)); + if (leftMonths==null || rightMonths==null || rightMonths.compareTo(BigDecimal.ZERO)==0) return null; + return leftMonths.divide(rightMonths, MathContext.DECIMAL128); + } + ); + + // left or right == null --> null + map.put(new CheckedPredicate((left,right) -> left==null || right==null, false), (left,right) -> null); + + return map; + } + + /** + * A wrapper around a BiPredicate used to determine whether a given pair of operands + * Matches a particular operation rule, with an additional flag indicating whether an error + * notification should be raised when the operation result is null. + */ + public static class CheckedPredicate { + final BiPredicate<Object, Object> predicate; + final boolean toNotify; + + CheckedPredicate(BiPredicate<Object, Object> predicate, boolean toNotify) { + this.predicate = predicate; + this.toNotify = toNotify; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof CheckedPredicate)) + return false; + CheckedPredicate other = (CheckedPredicate) obj; + return Objects.equals(this.predicate, other.predicate); + } + + @Override + public int hashCode() { + return Objects.hash(predicate); + } + } + + @Override + public Object executeAdd(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getAddOperations(ctx)); + } + + @Override + public Object executeAnd(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getAndOperations(ctx)); + } + + @Override + public Object executeEqual(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getEqualOperations(ctx)); + } + + @Override + public Object executeGte(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getGteOperations(ctx)); + } + + @Override + public Object executeGt(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getGtOperations(ctx)); + } + + @Override + public Object executeLte(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getLteOperations(ctx)); + } + + @Override + public Object executeLt(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getLtOperations(ctx)); + } + + @Override + public Object executeNotEqual(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getNotEqualOperations(ctx)); + } + + @Override + public Object executeOr(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getOrOperations(ctx)); + } + + @Override + public Object executePow(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getPowOperations(ctx)); + } + + @Override + public Object executeSub(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getSubOperations(ctx)); + } + + @Override + public Object executeMult(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getMultOperations(ctx)); + } + + @Override + public Object executeDivision(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getDivisionOperations(ctx)); + } + + /** + * Executes a binary operation defined in the given operation map against the provided operands. + * @param left : the left operand of the operation; + * @param right : the right operand of the operation; + * @param ctx : the current EvaluationContext + * @param operationMap : the map of CheckedPredicate to BiFunction defining available operations + * @return : the result of applying the matched operation, or null if the operation is undefined or produces no result + */ + private Object executeOperation( + Object left, + Object right, + EvaluationContext ctx, + Map<CheckedPredicate, BiFunction<Object, Object, Object>> operationMap + ) { + Optional<Map.Entry<CheckedPredicate, BiFunction<Object, Object, Object>>> match = + operationMap.entrySet().stream() + .filter(entry -> entry.getKey().predicate.test(left, right)) + .findFirst(); + + if (match.isPresent()) { + Object result = match.get().getValue().apply(left, right); + if (result == null && match.get().getKey().toNotify) { + ctx.notifyEvt(() -> new InvalidParametersEvent( + FEELEvent.Severity.ERROR, + Msg.OPERATION_IS_UNDEFINED_FOR_PARAMETERS.getMask() + )); + } + return result; + } + + ctx.notifyEvt(() -> new InvalidParametersEvent( + FEELEvent.Severity.ERROR, + Msg.OPERATION_IS_UNDEFINED_FOR_PARAMETERS.getMask() + )); + return null; Review Comment: The `executeOperation` method in `DefaultDialectHandler` notifies with `FEELEvent.Severity.ERROR` when no operation matches (line 710-713), but this doesn't respect the BFEEL dialect which should use `WARN` severity. The method should use `commonManageInvalidParameters(ctx)` instead to handle dialect-specific severity. Should be: ```java commonManageInvalidParameters(ctx); ``` ```suggestion commonManageInvalidParameters(ctx); return null; ``` ########## kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/dialectHandlers/DefaultDialectHandler.java: ########## @@ -0,0 +1,716 @@ +/* + * 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.dialectHandlers; + +import ch.obermuhlner.math.big.BigDecimalMath; +import org.kie.dmn.api.feel.runtime.events.FEELEvent; +import org.kie.dmn.feel.lang.EvaluationContext; +import org.kie.dmn.feel.lang.FEELDialect; +import org.kie.dmn.feel.lang.ast.infixexecutors.InfixExecutorUtils; +import org.kie.dmn.feel.lang.types.impl.ComparablePeriod; +import org.kie.dmn.feel.runtime.events.InvalidParametersEvent; +import org.kie.dmn.feel.util.BooleanEvalHelper; +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.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.chrono.ChronoPeriod; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAmount; +import java.util.*; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; + +import static org.kie.dmn.feel.lang.ast.infixexecutors.InfixExecutorUtils.*; +import static org.kie.dmn.feel.util.BooleanEvalHelper.evalRight; +import static org.kie.dmn.feel.util.NumberEvalHelper.getBigDecimalOrNull; + +/** + * Base implementation of the DialectHandler interface providing common + * functionality for all dialects. + */ +public abstract class DefaultDialectHandler implements DialectHandler { + + /** + * Builds the common 'Addition' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Addition' operations + */ + protected Map<CheckedPredicate, BiFunction<Object, Object, Object>> getCommonAddOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + + // Number + Number + map.put( + new CheckedPredicate((left, right) -> left instanceof Number && right instanceof Number, false), + (left, right) -> { + BigDecimal leftNum = getBigDecimalOrNull(left); + BigDecimal rightNum = getBigDecimal(right, ctx); + return leftNum != null && rightNum != null ? leftNum.add(rightNum, MathContext.DECIMAL128) : null; + } + ); + + // Duration + LocalDate + map.put( + new CheckedPredicate((left, right) -> left instanceof Duration && right instanceof LocalDate, false), + (left, right) -> addLocalDateAndDuration((LocalDate) right, (Duration) left) + ); + + // LocalDate + Duration + map.put( + new CheckedPredicate((left, right) -> left instanceof LocalDate && right instanceof Duration, false), + (left, right) -> addLocalDateAndDuration((LocalDate) left, (Duration) right) + ); + + // Duration + Duration + map.put( + new CheckedPredicate((left, right) -> left instanceof Duration && right instanceof Duration, false), + (left, right) -> ((Duration) left).plus((Duration) right) + ); + + // Temporal + TemporalAmount + map.put( + new CheckedPredicate((left, right) -> left instanceof Temporal && right instanceof TemporalAmount, false), + (left, right) -> ((Temporal) left).plus((TemporalAmount) right) + ); + + // TemporalAmount + Temporal + map.put( + new CheckedPredicate((left, right) -> left instanceof TemporalAmount && right instanceof Temporal, false), + (left, right) -> ((Temporal) right).plus((TemporalAmount) left) + ); + + // TemporalAmount + ChronoPeriod + map.put( + new CheckedPredicate((left, right) -> left instanceof TemporalAmount && right instanceof ChronoPeriod, false), + (left, right) -> ((ChronoPeriod) right).plus((TemporalAmount) left) + ); + + // left or right -> null + map.put( + new CheckedPredicate((left, right) -> left == null || right == null, false), + (left, right) -> null + ); + return map; + } + + + /** + * Builds the common 'And' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'And' operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonAndOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + + // false AND anything → false (short‑circuit) + map.put( + new CheckedPredicate((left, right) -> Boolean.FALSE.equals(BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect)), false), + (left, right) -> Boolean.FALSE + ); + + // true AND true → true + map.put( + new CheckedPredicate((left, right) -> Boolean.TRUE.equals(BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect)) + && Boolean.TRUE.equals(evalRight(right, ctx)), false), + (left, right) -> Boolean.TRUE + ); + + // true AND false → false + map.put( + new CheckedPredicate((left, right) -> Boolean.TRUE.equals(BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect)) + && Boolean.FALSE.equals(evalRight(right, ctx)), false), + (left, right) -> Boolean.FALSE + ); + + // true AND otherwise → null + map.put( + new CheckedPredicate((left, right) -> Boolean.TRUE.equals(BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect)) + && evalRight(right, ctx) == null, false), + (left, right) -> null + ); + + // otherwise AND true → null + map.put( + new CheckedPredicate((left, right) -> BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect) == null + && Boolean.TRUE.equals(evalRight(right, ctx)), false), + (left, right) -> null + ); + + // otherwise AND false → false + map.put( + new CheckedPredicate((left, right) -> BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect) == null + && Boolean.FALSE.equals(evalRight(right, ctx)), false), + (left, right) -> Boolean.FALSE + ); + + // otherwise AND otherwise → null + map.put( + new CheckedPredicate((left, right) -> BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect) == null + && evalRight(right, ctx) == null, false), + (left, right) -> null + ); + + return map; + } + + /** + * Builds the common 'Equal' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Equal' operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonEqualOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + + map.put( + new CheckedPredicate((left, right) -> true, false), + (left, right) -> BooleanEvalHelper.isEqual(left, right, dialect) + ); + return map; + } + + /** + * Builds the common 'Greater than Or EqualTo' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Greater than Or EqualTo' operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonGteOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + + // left > right OR left == right + map.put( + new CheckedPredicate((left, right) -> true, false), + (left, right) -> InfixExecutorUtils.or( + BooleanEvalHelper.compare(left, right, dialect, (leftNum, rightNum) -> leftNum.compareTo(rightNum) > 0), + BooleanEvalHelper.isEqual(left, right, dialect), + ctx + ) + ); + return map; + } + + /** + * Builds the common 'Greater than' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Greater than' operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonGtOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + + // left > right + map.put( + new CheckedPredicate((left, right) -> true, false), + (left, right) -> BooleanEvalHelper.compare(left, right, dialect, (leftNum, rightNum) -> leftNum.compareTo(rightNum) > 0) + ); + return map; + } + + /** + * Builds the common 'Less than Or EqualTo' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Less than Or EqualTo' operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonLteOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + + // left < right OR left == right + map.put( + new CheckedPredicate((left, right) -> true, false), + (left, right) -> InfixExecutorUtils.or( + BooleanEvalHelper.compare(left, right, dialect, (leftNum, rightNum) -> leftNum.compareTo(rightNum) < 0), + BooleanEvalHelper.isEqual(left, right, dialect), + ctx + ) + ); + return map; + } + + /** + * Builds the common 'Less than' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Less than Or EqualTo' operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonLtOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + + // left < right + map.put( + new CheckedPredicate((left, right) -> true, false), + (left, right) -> BooleanEvalHelper.compare(left, right, dialect, (leftNum, rightNum) -> leftNum.compareTo(rightNum) < 0) + ); + return map; + } + + /** + * Builds the common 'Not EqualTo' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Not EqualTo' operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonNotEqualOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + + map.put( + new CheckedPredicate((left, right) -> true, false), + (left, right) -> { + Boolean result = BooleanEvalHelper.isEqual(left, right, dialect); + return result != null ? !result : null; + } + ); + return map; + } + + /** + * Builds the common 'Or' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Or operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonOrOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + + // true OR anything → true (short‑circuit) + map.put( + new CheckedPredicate((left, right) -> Boolean.TRUE.equals(BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect)), false), + (left, right) -> Boolean.TRUE + ); + + // false OR true → true + map.put( + new CheckedPredicate((left, right) -> Boolean.FALSE.equals(BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect)) + && Boolean.TRUE.equals(evalRight(right, ctx)), false), + (left, right) -> Boolean.TRUE + ); + + // false OR false → false + map.put( + new CheckedPredicate((left, right) -> Boolean.FALSE.equals(BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect)) + && Boolean.FALSE.equals(evalRight(right, ctx)), false), + (left, right) -> Boolean.FALSE + ); + + // false OR otherwise → null + map.put( + new CheckedPredicate((left, right) -> Boolean.FALSE.equals(BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect)) + && evalRight(right, ctx) == null, false), + (left, right) -> null + ); + + // otherwise OR true → true + map.put( + new CheckedPredicate((left, right) -> BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect) == null + && Boolean.TRUE.equals(evalRight(right, ctx)), false), + (left, right) -> Boolean.TRUE + ); + + // otherwise OR false → null + map.put( + new CheckedPredicate((left, right) -> BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect) == null + && Boolean.FALSE.equals(evalRight(right, ctx)), false), + (left, right) -> null + ); + + // otherwise OR otherwise → null + map.put( + new CheckedPredicate((left, right) -> BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect) == null + && evalRight(right, ctx) == null, false), + (left, right) -> null + ); + + return map; + } + + /** + * Builds the common 'Power of' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Power of' operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonPowOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + map.put( + new CheckedPredicate((left, right) -> true, false), + (left, right) -> math(left, right, ctx, + (l, r) -> BigDecimalMath.pow(l, r, MathContext.DECIMAL128)) + ); + return map; + } + + /** + * Builds the common 'Substraction' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Substraction' operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonSubOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + // null - Number + map.put( + new CheckedPredicate((left, right) -> + (left == null && right instanceof Number) || (right == null && left instanceof Number), false), + (left, right) -> { + BigDecimal leftNum = getBigDecimal(left, ctx); + BigDecimal rightNum = getBigDecimal(right, ctx); + return leftNum != null && rightNum != null ? leftNum.subtract(rightNum, MathContext.DECIMAL128) : null; + } + ); + + // Number - Number + map.put( + new CheckedPredicate((left, right) -> left instanceof Number && right instanceof Number, false), + (left, right) -> { + BigDecimal leftNum = getBigDecimal(left, ctx); + BigDecimal rightNum = getBigDecimal(right, ctx); + return leftNum != null && rightNum != null ? leftNum.subtract(rightNum, MathContext.DECIMAL128) : null; + } + ); + + // LocalDate - Duration + map.put( + new CheckedPredicate((left, right) -> left instanceof LocalDate && right instanceof Duration, false), + (left, right) -> { + LocalDateTime leftLDT = LocalDateTime.of((LocalDate) left, LocalTime.MIDNIGHT); + LocalDateTime evaluated = leftLDT.minus((Duration) right); + return LocalDate.of(evaluated.getYear(), evaluated.getMonth(), evaluated.getDayOfMonth()); + } + ); + + // Duration - Duration + map.put( + new CheckedPredicate((left, right) -> left instanceof Duration && right instanceof Duration, false), + (left, right) -> ((Duration) left).minus((Duration) right) + ); + + // Temporal - Temporal + map.put( + new CheckedPredicate((left, right) -> left instanceof Temporal && right instanceof Temporal, false), + (left, right) -> subtractTemporals((Temporal) left, (Temporal) right, ctx) + ); + + // Temporal - TemporalAmount + map.put( + new CheckedPredicate((left, right) -> left instanceof Temporal && right instanceof TemporalAmount, false), + (left, right) -> ((Temporal) left).minus((TemporalAmount) right) + ); + + // ChronoPeriod - ChronoPeriod + map.put( + new CheckedPredicate((left, right) -> left instanceof ChronoPeriod && right instanceof ChronoPeriod, false), + (left, right) -> new ComparablePeriod(((ChronoPeriod) left).minus((ChronoPeriod) right)) + ); + + + // left == null || right == null + map.put( + new CheckedPredicate((left, right) -> left == null || right == null, false), + (left, right) -> null + ); + + return map; + } + + /** + * Builds the common 'Multiplication' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Multiplication operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonMultOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + // Number * Number + map.put( + new CheckedPredicate((left, right) -> left instanceof Number && right instanceof Number, false), + (left, right) -> { + BigDecimal leftNum = getBigDecimalOrNull(left); + BigDecimal rightNum = getBigDecimal(right, ctx); + return leftNum != null && rightNum != null ? leftNum.multiply(rightNum, MathContext.DECIMAL128) : null; + } + ); + + // Number * Duration + map.put( + new CheckedPredicate((left, right) -> left instanceof Number && right instanceof Duration, false), + (left, right) -> Duration.ofSeconds( + getBigDecimalOrNull(left) + .multiply(BigDecimal.valueOf(((Duration) right).getSeconds()), MathContext.DECIMAL128) + .longValue() + ) + ); + + // Number * ChronoPeriod + map.put( + new CheckedPredicate((left, right) -> left instanceof Number && right instanceof ChronoPeriod, false), + (left, right) -> ComparablePeriod.ofMonths( + getBigDecimalOrNull(left) + .multiply(getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) right)), MathContext.DECIMAL128) + .intValue() + ) + ); + + // Duration * Number + map.put( + new CheckedPredicate((left, right) -> left instanceof Duration && right instanceof Number, false), + (left, right) -> { + BigDecimal durationNumericValue = BigDecimal.valueOf(((Duration) left).toNanos()); + BigDecimal rightDecimal = BigDecimal.valueOf(((Number) right).doubleValue()); + return Duration.ofNanos(durationNumericValue.multiply(rightDecimal).longValue()); + } + ); + // ChronoPeriod * Number + map.put( + new CheckedPredicate((left, right) -> left instanceof ChronoPeriod && right instanceof Number, false), + (left, right) -> { + BigDecimal rightNumber = getBigDecimal(right, ctx); + return ComparablePeriod.ofMonths( + getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) left)) + .multiply(rightNumber, MathContext.DECIMAL128) + .intValue() + ); + } + ); + + + // left or right == null + map.put( + new CheckedPredicate((left, right) -> left == null || right == null, false), + (left, right) -> null + ); + + return map; + } + + /** + * Builds the common 'Division' operation map used by the dialect handlers. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the common 'Division' operations + */ + Map<DefaultDialectHandler.CheckedPredicate, BiFunction<Object, Object, Object>> getCommonDivisionOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + + // Number ÷ Number + map.put( + new CheckedPredicate((left,right) -> left instanceof Number && right instanceof Number + && getBigDecimalOrNull(right) != null + && getBigDecimalOrNull(right).compareTo(BigDecimal.ZERO) != 0, + false), + (left,right) -> { + BigDecimal leftBD = getBigDecimalOrNull(left); + BigDecimal rightBD = getBigDecimalOrNull(right); + return leftBD.divide(rightBD, MathContext.DECIMAL128); + } + ); + + // Number ÷ Number , Division by zero case → notify + map.put( + new CheckedPredicate((left,right) -> left instanceof Number && right instanceof Number + && getBigDecimalOrNull(right) != null + && getBigDecimalOrNull(right).compareTo(BigDecimal.ZERO) == 0, + true), + (left,right) -> null + ); + + // duration ÷ number + map.put(new CheckedPredicate((left,right) -> left instanceof Duration && right instanceof Number, false), + (left,right) -> { + Duration dur = (Duration) left; + BigDecimal nanos = BigDecimal.valueOf(dur.toNanos()); + BigDecimal divisor = getBigDecimalOrNull(right); + if (divisor==null || divisor.compareTo(BigDecimal.ZERO)==0) return null; + BigDecimal scaled = nanos.divide(divisor, 0, RoundingMode.HALF_EVEN); + return Duration.ofNanos(scaled.longValue()); + } + ); + + // duration ÷ duration → number + map.put(new CheckedPredicate((left,right) -> left instanceof Duration && right instanceof Duration, false), + (left,right) -> { + BigDecimal leftSecs = getBigDecimalOrNull(((Duration) left).getSeconds()); + BigDecimal rightSecs = getBigDecimalOrNull(((Duration) right).getSeconds()); + if (leftSecs==null || rightSecs==null || rightSecs.compareTo(BigDecimal.ZERO)==0) return null; + return leftSecs.divide(rightSecs, MathContext.DECIMAL128); + } + ); + + // period ÷ number + map.put(new CheckedPredicate((left,right) -> left instanceof ChronoPeriod && right instanceof Number, true), + (left,right) -> { + BigDecimal months = getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) left)); + BigDecimal divisor = getBigDecimalOrNull(right); + if (months==null || divisor==null || divisor.compareTo(BigDecimal.ZERO)==0) return null; + BigDecimal scaled = months.divide(divisor, MathContext.DECIMAL128); + return ComparablePeriod.ofMonths(scaled.intValue()); + } + ); + + // period ÷ period → number + map.put(new CheckedPredicate((left,right) -> left instanceof ChronoPeriod && right instanceof ChronoPeriod, false), + (left,right) -> { + BigDecimal leftMonths = getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) left)); + BigDecimal rightMonths = getBigDecimalOrNull(ComparablePeriod.toTotalMonths((ChronoPeriod) right)); + if (leftMonths==null || rightMonths==null || rightMonths.compareTo(BigDecimal.ZERO)==0) return null; + return leftMonths.divide(rightMonths, MathContext.DECIMAL128); + } + ); + + // left or right == null --> null + map.put(new CheckedPredicate((left,right) -> left==null || right==null, false), (left,right) -> null); + + return map; + } + + /** + * A wrapper around a BiPredicate used to determine whether a given pair of operands + * Matches a particular operation rule, with an additional flag indicating whether an error + * notification should be raised when the operation result is null. + */ + public static class CheckedPredicate { + final BiPredicate<Object, Object> predicate; + final boolean toNotify; + + CheckedPredicate(BiPredicate<Object, Object> predicate, boolean toNotify) { + this.predicate = predicate; + this.toNotify = toNotify; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof CheckedPredicate)) + return false; + CheckedPredicate other = (CheckedPredicate) obj; + return Objects.equals(this.predicate, other.predicate); + } + + @Override + public int hashCode() { + return Objects.hash(predicate); + } + } + + @Override + public Object executeAdd(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getAddOperations(ctx)); + } + + @Override + public Object executeAnd(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getAndOperations(ctx)); + } + + @Override + public Object executeEqual(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getEqualOperations(ctx)); + } + + @Override + public Object executeGte(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getGteOperations(ctx)); + } + + @Override + public Object executeGt(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getGtOperations(ctx)); + } + + @Override + public Object executeLte(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getLteOperations(ctx)); + } + + @Override + public Object executeLt(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getLtOperations(ctx)); + } + + @Override + public Object executeNotEqual(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getNotEqualOperations(ctx)); + } + + @Override + public Object executeOr(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getOrOperations(ctx)); + } + + @Override + public Object executePow(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getPowOperations(ctx)); + } + + @Override + public Object executeSub(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getSubOperations(ctx)); + } + + @Override + public Object executeMult(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getMultOperations(ctx)); + } + + @Override + public Object executeDivision(Object left, Object right, EvaluationContext ctx) { + return executeOperation(left, right, ctx, getDivisionOperations(ctx)); + } + + /** + * Executes a binary operation defined in the given operation map against the provided operands. + * @param left : the left operand of the operation; + * @param right : the right operand of the operation; + * @param ctx : the current EvaluationContext + * @param operationMap : the map of CheckedPredicate to BiFunction defining available operations + * @return : the result of applying the matched operation, or null if the operation is undefined or produces no result + */ + private Object executeOperation( + Object left, + Object right, + EvaluationContext ctx, + Map<CheckedPredicate, BiFunction<Object, Object, Object>> operationMap + ) { + Optional<Map.Entry<CheckedPredicate, BiFunction<Object, Object, Object>>> match = + operationMap.entrySet().stream() + .filter(entry -> entry.getKey().predicate.test(left, right)) + .findFirst(); + + if (match.isPresent()) { + Object result = match.get().getValue().apply(left, right); + if (result == null && match.get().getKey().toNotify) { + ctx.notifyEvt(() -> new InvalidParametersEvent( + FEELEvent.Severity.ERROR, + Msg.OPERATION_IS_UNDEFINED_FOR_PARAMETERS.getMask() + )); Review Comment: In the `executeOperation` method, when `result == null && match.get().getKey().toNotify`, it always uses `FEELEvent.Severity.ERROR` (line 703). This doesn't respect the BFEEL dialect. The notification should use `commonManageInvalidParameters(ctx)` to handle dialect-specific severity. Should use: ```java if (result == null && match.get().getKey().toNotify) { commonManageInvalidParameters(ctx); } ``` ```suggestion commonManageInvalidParameters(ctx); ``` ########## kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/dialectHandlers/BFEELDialectHandler.java: ########## @@ -0,0 +1,326 @@ +/* + * 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.dialectHandlers; + +import org.kie.dmn.feel.lang.EvaluationContext; +import org.kie.dmn.feel.lang.FEELDialect; +import org.kie.dmn.feel.lang.types.impl.ComparablePeriod; +import org.kie.dmn.feel.util.BooleanEvalHelper; + +import java.math.BigDecimal; +import java.time.Duration; +import java.time.LocalDate; +import java.time.chrono.ChronoPeriod; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.BiFunction; + +import static org.kie.dmn.feel.lang.ast.infixexecutors.InfixExecutorUtils.*; +import static org.kie.dmn.feel.util.BooleanEvalHelper.evalRight; + +/** + * Handler implementation of the DialectHandler interface providing BFEEL specific + * functionalities + */ +public class BFEELDialectHandler extends DefaultDialectHandler implements DialectHandler { + + /** + * Builds the BFeel specific 'Addition' operations. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the BFeel specific 'Addition' operations + */ + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getAddOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + + // any String operand → concatenate both as strings + map.put( + new CheckedPredicate((left, right) -> left instanceof String || right instanceof String, false), + (left, right) -> { + String leftNum = getString(left); + String rightNum = getString(right); + return leftNum + rightNum; + } + ); + + // date + number → return the number + map.put( + new CheckedPredicate((left, right) -> left instanceof LocalDate && right instanceof Number, false), + (left, right) -> right + ); + map.put( + new CheckedPredicate((left, right) -> left instanceof Number && right instanceof LocalDate, false), + (left, right) -> left + ); + + // Number + null → number + map.put( + new CheckedPredicate((left, right) -> left instanceof Number && right == null, false), + (left, right) -> left + ); + + // null + Number → number + map.put( + new CheckedPredicate((left, right) -> left == null && right instanceof Number, false), + (left, right) -> right + ); + + map.putAll(getCommonAddOperations(ctx)); + return map; + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getAndOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + // Special case: true AND otherwise → false + map.put( + new CheckedPredicate((left, right) -> { + Boolean leftBool = BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect); + Object rightValue = evalRight(right, ctx); + Boolean rightBool = BooleanEvalHelper.getBooleanOrDialectDefault(rightValue, dialect); + return Boolean.TRUE.equals(leftBool) && Boolean.FALSE.equals(rightBool); + }, false), + (left, right) -> Boolean.FALSE + ); + + // Special case: otherwise AND true → false + map.put( + new CheckedPredicate((left, right) -> { + Boolean leftBool = BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect); + Object rightValue = evalRight(right, ctx); + Boolean rightBool = BooleanEvalHelper.getBooleanOrDialectDefault(rightValue, dialect); + return leftBool == null && Boolean.TRUE.equals(rightBool); + }, false), + (left, right) -> Boolean.FALSE + ); + + // Special case: otherwise AND otherwise → false + map.put( + new CheckedPredicate((left, right) -> { + Boolean leftBool = BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect); + Object rightValue = evalRight(right, ctx); + Boolean rightBool = BooleanEvalHelper.getBooleanOrDialectDefault(rightValue, dialect); + return leftBool == null && Boolean.FALSE.equals(rightBool); + }, false), + (left, right) -> Boolean.FALSE + ); + map.putAll(getCommonAddOperations(ctx)); + return map; + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getEqualOperations(EvaluationContext ctx) { + return new LinkedHashMap<>(getCommonEqualOperations(ctx)); + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getGteOperations(EvaluationContext ctx) { + return new LinkedHashMap<>(getCommonGteOperations(ctx)); + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getGtOperations(EvaluationContext ctx) { + return new LinkedHashMap<>(getCommonGtOperations(ctx)); + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getLteOperations(EvaluationContext ctx) { + return new LinkedHashMap<>(getCommonLteOperations(ctx)); + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getLtOperations(EvaluationContext ctx) { + return new LinkedHashMap<>(getCommonLtOperations(ctx)); + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getNotEqualOperations(EvaluationContext ctx) { + return new LinkedHashMap<>(getCommonNotEqualOperations(ctx)); + } + + /** + * Builds the BFeel specific 'OR' operations. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the BFeel specific 'OR' operations + */ + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getOrOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + + // Special case: false OR null/otherwise → false (BFEEL override) + map.put( + new CheckedPredicate((left, right) -> { + Boolean leftBool = BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect); + Object rightValue = evalRight(right, ctx); + Boolean rightBool = BooleanEvalHelper.getBooleanOrDialectDefault(rightValue, dialect); + return Boolean.FALSE.equals(leftBool) && Boolean.FALSE.equals(rightBool); + }, false), + (left, right) -> Boolean.FALSE + ); + // Fall back to FEEL semantics for all other cases + map.putAll(getCommonOrOperations(ctx)); + return map; + } + + /** + * Builds the BFeel specific 'Power' operations. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the BFeel specific 'Power' operations + */ + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getPowOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + //TODO Change pow behaviour for BFeel + map.put( + new CheckedPredicate((left, right) -> left instanceof String || right instanceof String, false), + (left, right) -> Boolean.FALSE Review Comment: The comment on line 191 says "TODO Change pow behaviour for BFeel" but the code returns `Boolean.FALSE` which is incorrect. This TODO should be resolved before merging - power operations should return numeric values, not booleans. ```suggestion (left, right) -> null ``` ########## kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/infixexecutors/AndExecutor.java: ########## @@ -37,23 +36,13 @@ public static AndExecutor instance() { @Override public Object evaluate(Object left, Object right, EvaluationContext ctx) { - return and(left, right, ctx); + DialectHandler handler = DialectHandlerFactory.getHandler(ctx); + return handler.executeAnd(left, right, ctx); } Review Comment: [nitpick] In `AndExecutor` and `OrExecutor`, the `evaluate(InfixOpNode, EvaluationContext)` method now passes `infixNode.getRight()` (the AST node) instead of `infixNode.getRight().evaluate(ctx)` (the evaluated value). While this enables short-circuit evaluation, it changes the contract and the handlers must now handle both evaluated values and AST nodes. This inconsistency should be documented or the handler interface should explicitly support this pattern. ```suggestion /** * Evaluates the AND operation with short-circuit logic. * <p> * Note: The right-hand side is passed as an AST node (not an evaluated value) * to enable short-circuit evaluation. The handler must be able to handle both * evaluated values and AST nodes for the right argument. * * @param infixNode the infix operation node * @param ctx the evaluation context * @return the result of the AND operation */ ``` ########## kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/dialectHandlers/BFEELDialectHandler.java: ########## @@ -0,0 +1,326 @@ +/* + * 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.dialectHandlers; + +import org.kie.dmn.feel.lang.EvaluationContext; +import org.kie.dmn.feel.lang.FEELDialect; +import org.kie.dmn.feel.lang.types.impl.ComparablePeriod; +import org.kie.dmn.feel.util.BooleanEvalHelper; + +import java.math.BigDecimal; +import java.time.Duration; +import java.time.LocalDate; +import java.time.chrono.ChronoPeriod; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.BiFunction; + +import static org.kie.dmn.feel.lang.ast.infixexecutors.InfixExecutorUtils.*; +import static org.kie.dmn.feel.util.BooleanEvalHelper.evalRight; + +/** + * Handler implementation of the DialectHandler interface providing BFEEL specific + * functionalities + */ +public class BFEELDialectHandler extends DefaultDialectHandler implements DialectHandler { + + /** + * Builds the BFeel specific 'Addition' operations. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the BFeel specific 'Addition' operations + */ + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getAddOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + + // any String operand → concatenate both as strings + map.put( + new CheckedPredicate((left, right) -> left instanceof String || right instanceof String, false), + (left, right) -> { + String leftNum = getString(left); + String rightNum = getString(right); + return leftNum + rightNum; + } + ); + + // date + number → return the number + map.put( + new CheckedPredicate((left, right) -> left instanceof LocalDate && right instanceof Number, false), + (left, right) -> right + ); + map.put( + new CheckedPredicate((left, right) -> left instanceof Number && right instanceof LocalDate, false), + (left, right) -> left + ); + + // Number + null → number + map.put( + new CheckedPredicate((left, right) -> left instanceof Number && right == null, false), + (left, right) -> left + ); + + // null + Number → number + map.put( + new CheckedPredicate((left, right) -> left == null && right instanceof Number, false), + (left, right) -> right + ); + + map.putAll(getCommonAddOperations(ctx)); + return map; + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getAndOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + // Special case: true AND otherwise → false + map.put( + new CheckedPredicate((left, right) -> { + Boolean leftBool = BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect); + Object rightValue = evalRight(right, ctx); + Boolean rightBool = BooleanEvalHelper.getBooleanOrDialectDefault(rightValue, dialect); + return Boolean.TRUE.equals(leftBool) && Boolean.FALSE.equals(rightBool); + }, false), + (left, right) -> Boolean.FALSE + ); + + // Special case: otherwise AND true → false + map.put( + new CheckedPredicate((left, right) -> { + Boolean leftBool = BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect); + Object rightValue = evalRight(right, ctx); + Boolean rightBool = BooleanEvalHelper.getBooleanOrDialectDefault(rightValue, dialect); + return leftBool == null && Boolean.TRUE.equals(rightBool); + }, false), + (left, right) -> Boolean.FALSE + ); + + // Special case: otherwise AND otherwise → false + map.put( + new CheckedPredicate((left, right) -> { + Boolean leftBool = BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect); + Object rightValue = evalRight(right, ctx); + Boolean rightBool = BooleanEvalHelper.getBooleanOrDialectDefault(rightValue, dialect); + return leftBool == null && Boolean.FALSE.equals(rightBool); + }, false), + (left, right) -> Boolean.FALSE + ); + map.putAll(getCommonAddOperations(ctx)); + return map; + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getEqualOperations(EvaluationContext ctx) { + return new LinkedHashMap<>(getCommonEqualOperations(ctx)); + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getGteOperations(EvaluationContext ctx) { + return new LinkedHashMap<>(getCommonGteOperations(ctx)); + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getGtOperations(EvaluationContext ctx) { + return new LinkedHashMap<>(getCommonGtOperations(ctx)); + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getLteOperations(EvaluationContext ctx) { + return new LinkedHashMap<>(getCommonLteOperations(ctx)); + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getLtOperations(EvaluationContext ctx) { + return new LinkedHashMap<>(getCommonLtOperations(ctx)); + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getNotEqualOperations(EvaluationContext ctx) { + return new LinkedHashMap<>(getCommonNotEqualOperations(ctx)); + } + + /** + * Builds the BFeel specific 'OR' operations. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the BFeel specific 'OR' operations + */ + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getOrOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + + // Special case: false OR null/otherwise → false (BFEEL override) + map.put( + new CheckedPredicate((left, right) -> { + Boolean leftBool = BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect); + Object rightValue = evalRight(right, ctx); + Boolean rightBool = BooleanEvalHelper.getBooleanOrDialectDefault(rightValue, dialect); + return Boolean.FALSE.equals(leftBool) && Boolean.FALSE.equals(rightBool); + }, false), + (left, right) -> Boolean.FALSE + ); + // Fall back to FEEL semantics for all other cases + map.putAll(getCommonOrOperations(ctx)); + return map; + } + + /** + * Builds the BFeel specific 'Power' operations. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the BFeel specific 'Power' operations + */ + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getPowOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + //TODO Change pow behaviour for BFeel + map.put( + new CheckedPredicate((left, right) -> left instanceof String || right instanceof String, false), + (left, right) -> Boolean.FALSE + ); + + map.putAll(getCommonPowOperations(ctx)); + return map; + } + + /** + * Builds the BFeel specific 'Substraction' operations. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the BFeel specific 'Substraction' operations + */ + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getSubOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + //subtraction with Strings → empty string + map.put( + new CheckedPredicate((left, right) -> + (left instanceof String || right instanceof String), false), + (left, right) -> "" Review Comment: [nitpick] In the `getSubOperations` method, the comment says "subtraction with Strings → empty string", but this behavior seems inconsistent with BFEEL addition operations where strings are concatenated. Consider if this is the correct default value for BFEEL or if it should return `BigDecimal.ZERO` for consistency with other numeric operations. ```suggestion // subtraction with Strings → BigDecimal.ZERO for consistency with numeric operations map.put( new CheckedPredicate((left, right) -> (left instanceof String || right instanceof String), false), (left, right) -> BigDecimal.ZERO ``` ########## kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/dialectHandlers/BFEELDialectHandler.java: ########## @@ -0,0 +1,326 @@ +/* + * 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.dialectHandlers; + +import org.kie.dmn.feel.lang.EvaluationContext; +import org.kie.dmn.feel.lang.FEELDialect; +import org.kie.dmn.feel.lang.types.impl.ComparablePeriod; +import org.kie.dmn.feel.util.BooleanEvalHelper; + +import java.math.BigDecimal; +import java.time.Duration; +import java.time.LocalDate; +import java.time.chrono.ChronoPeriod; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.BiFunction; + +import static org.kie.dmn.feel.lang.ast.infixexecutors.InfixExecutorUtils.*; +import static org.kie.dmn.feel.util.BooleanEvalHelper.evalRight; + +/** + * Handler implementation of the DialectHandler interface providing BFEEL specific + * functionalities + */ +public class BFEELDialectHandler extends DefaultDialectHandler implements DialectHandler { + + /** + * Builds the BFeel specific 'Addition' operations. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the BFeel specific 'Addition' operations + */ + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getAddOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + + // any String operand → concatenate both as strings + map.put( + new CheckedPredicate((left, right) -> left instanceof String || right instanceof String, false), + (left, right) -> { + String leftNum = getString(left); + String rightNum = getString(right); + return leftNum + rightNum; + } + ); + + // date + number → return the number + map.put( + new CheckedPredicate((left, right) -> left instanceof LocalDate && right instanceof Number, false), + (left, right) -> right + ); + map.put( + new CheckedPredicate((left, right) -> left instanceof Number && right instanceof LocalDate, false), + (left, right) -> left + ); + + // Number + null → number + map.put( + new CheckedPredicate((left, right) -> left instanceof Number && right == null, false), + (left, right) -> left + ); + + // null + Number → number + map.put( + new CheckedPredicate((left, right) -> left == null && right instanceof Number, false), + (left, right) -> right + ); + + map.putAll(getCommonAddOperations(ctx)); + return map; + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getAndOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + // Special case: true AND otherwise → false + map.put( + new CheckedPredicate((left, right) -> { + Boolean leftBool = BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect); + Object rightValue = evalRight(right, ctx); + Boolean rightBool = BooleanEvalHelper.getBooleanOrDialectDefault(rightValue, dialect); + return Boolean.TRUE.equals(leftBool) && Boolean.FALSE.equals(rightBool); + }, false), + (left, right) -> Boolean.FALSE + ); + + // Special case: otherwise AND true → false + map.put( + new CheckedPredicate((left, right) -> { + Boolean leftBool = BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect); + Object rightValue = evalRight(right, ctx); + Boolean rightBool = BooleanEvalHelper.getBooleanOrDialectDefault(rightValue, dialect); + return leftBool == null && Boolean.TRUE.equals(rightBool); + }, false), + (left, right) -> Boolean.FALSE + ); + + // Special case: otherwise AND otherwise → false + map.put( + new CheckedPredicate((left, right) -> { + Boolean leftBool = BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect); + Object rightValue = evalRight(right, ctx); + Boolean rightBool = BooleanEvalHelper.getBooleanOrDialectDefault(rightValue, dialect); + return leftBool == null && Boolean.FALSE.equals(rightBool); Review Comment: The predicate for "otherwise AND otherwise → false" is checking `Boolean.FALSE.equals(rightBool)`, but the comment says "otherwise AND otherwise". This logic appears incorrect - it should check if `rightBool == null` to match "otherwise" values. The predicate should be: ```java return leftBool == null && rightBool == null; ``` ```suggestion return leftBool == null && rightBool == null; ``` ########## kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/dialectHandlers/BFEELDialectHandler.java: ########## @@ -0,0 +1,326 @@ +/* + * 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.dialectHandlers; + +import org.kie.dmn.feel.lang.EvaluationContext; +import org.kie.dmn.feel.lang.FEELDialect; +import org.kie.dmn.feel.lang.types.impl.ComparablePeriod; +import org.kie.dmn.feel.util.BooleanEvalHelper; + +import java.math.BigDecimal; +import java.time.Duration; +import java.time.LocalDate; +import java.time.chrono.ChronoPeriod; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.BiFunction; + +import static org.kie.dmn.feel.lang.ast.infixexecutors.InfixExecutorUtils.*; +import static org.kie.dmn.feel.util.BooleanEvalHelper.evalRight; + +/** + * Handler implementation of the DialectHandler interface providing BFEEL specific + * functionalities + */ +public class BFEELDialectHandler extends DefaultDialectHandler implements DialectHandler { + + /** + * Builds the BFeel specific 'Addition' operations. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the BFeel specific 'Addition' operations + */ + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getAddOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + + // any String operand → concatenate both as strings + map.put( + new CheckedPredicate((left, right) -> left instanceof String || right instanceof String, false), + (left, right) -> { + String leftNum = getString(left); + String rightNum = getString(right); + return leftNum + rightNum; + } + ); + + // date + number → return the number + map.put( + new CheckedPredicate((left, right) -> left instanceof LocalDate && right instanceof Number, false), + (left, right) -> right + ); + map.put( + new CheckedPredicate((left, right) -> left instanceof Number && right instanceof LocalDate, false), + (left, right) -> left + ); + + // Number + null → number + map.put( + new CheckedPredicate((left, right) -> left instanceof Number && right == null, false), + (left, right) -> left + ); + + // null + Number → number + map.put( + new CheckedPredicate((left, right) -> left == null && right instanceof Number, false), + (left, right) -> right + ); + + map.putAll(getCommonAddOperations(ctx)); + return map; + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getAndOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + // Special case: true AND otherwise → false + map.put( + new CheckedPredicate((left, right) -> { + Boolean leftBool = BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect); + Object rightValue = evalRight(right, ctx); + Boolean rightBool = BooleanEvalHelper.getBooleanOrDialectDefault(rightValue, dialect); + return Boolean.TRUE.equals(leftBool) && Boolean.FALSE.equals(rightBool); + }, false), + (left, right) -> Boolean.FALSE + ); + + // Special case: otherwise AND true → false + map.put( + new CheckedPredicate((left, right) -> { + Boolean leftBool = BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect); + Object rightValue = evalRight(right, ctx); + Boolean rightBool = BooleanEvalHelper.getBooleanOrDialectDefault(rightValue, dialect); + return leftBool == null && Boolean.TRUE.equals(rightBool); + }, false), + (left, right) -> Boolean.FALSE + ); + + // Special case: otherwise AND otherwise → false + map.put( + new CheckedPredicate((left, right) -> { + Boolean leftBool = BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect); + Object rightValue = evalRight(right, ctx); + Boolean rightBool = BooleanEvalHelper.getBooleanOrDialectDefault(rightValue, dialect); + return leftBool == null && Boolean.FALSE.equals(rightBool); + }, false), + (left, right) -> Boolean.FALSE + ); + map.putAll(getCommonAddOperations(ctx)); + return map; + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getEqualOperations(EvaluationContext ctx) { + return new LinkedHashMap<>(getCommonEqualOperations(ctx)); + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getGteOperations(EvaluationContext ctx) { + return new LinkedHashMap<>(getCommonGteOperations(ctx)); + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getGtOperations(EvaluationContext ctx) { + return new LinkedHashMap<>(getCommonGtOperations(ctx)); + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getLteOperations(EvaluationContext ctx) { + return new LinkedHashMap<>(getCommonLteOperations(ctx)); + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getLtOperations(EvaluationContext ctx) { + return new LinkedHashMap<>(getCommonLtOperations(ctx)); + } + + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getNotEqualOperations(EvaluationContext ctx) { + return new LinkedHashMap<>(getCommonNotEqualOperations(ctx)); + } + + /** + * Builds the BFeel specific 'OR' operations. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the BFeel specific 'OR' operations + */ + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getOrOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + FEELDialect dialect = ctx.getFEELDialect(); + + // Special case: false OR null/otherwise → false (BFEEL override) + map.put( + new CheckedPredicate((left, right) -> { + Boolean leftBool = BooleanEvalHelper.getBooleanOrDialectDefault(left, dialect); + Object rightValue = evalRight(right, ctx); + Boolean rightBool = BooleanEvalHelper.getBooleanOrDialectDefault(rightValue, dialect); + return Boolean.FALSE.equals(leftBool) && Boolean.FALSE.equals(rightBool); + }, false), + (left, right) -> Boolean.FALSE + ); + // Fall back to FEEL semantics for all other cases + map.putAll(getCommonOrOperations(ctx)); + return map; + } + + /** + * Builds the BFeel specific 'Power' operations. + * @param ctx : Current Evaluation context + * @return : a Map of CheckedPredicate to BiFunction representing the BFeel specific 'Power' operations + */ + @Override + public Map<CheckedPredicate, BiFunction<Object, Object, Object>> getPowOperations(EvaluationContext ctx) { + Map<CheckedPredicate, BiFunction<Object, Object, Object>> map = new LinkedHashMap<>(); + //TODO Change pow behaviour for BFeel + map.put( + new CheckedPredicate((left, right) -> left instanceof String || right instanceof String, false), + (left, right) -> Boolean.FALSE Review Comment: The `getPowOperations` method returns `Boolean.FALSE` for string operands. However, power operations should return numeric types (BigDecimal) or null, not Boolean values. This type mismatch will likely cause issues. Should return `BigDecimal.ZERO` or `null` instead of `Boolean.FALSE`. ```suggestion (left, right) -> null ``` ########## kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/BooleanEvalHelper.java: ########## @@ -286,4 +289,15 @@ static Boolean isEqualObject(Object l, Object r) { return true; } + // Evaluate right operand if it’s a node + public static Object evalRight(Object right, EvaluationContext ctx) { + if (right instanceof InfixOpNode) { + return ((InfixOpNode) right).evaluate(ctx); + } else if (right instanceof BaseNode) { + return ((BaseNode) right).evaluate(ctx); + } else { + return right; + } + } Review Comment: [nitpick] The `evalRight` helper method is added to `BooleanEvalHelper` but is used specifically for evaluating AST nodes in dialect handlers. This creates a circular dependency concern and mixes evaluation logic with boolean helper utilities. Consider moving this to a more appropriate utility class or directly into the dialect handlers where it's used. ```suggestion ``` -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
