This is an automated email from the ASF dual-hosted git repository.

mariofusco pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-drools.git


The following commit(s) were added to refs/heads/main by this push:
     new d04ab13454 [DROOLS-7631] unify coercion checks between plain drl and 
executable model (#6086)
d04ab13454 is described below

commit d04ab13454f2bce80c4259e16584c6d71f425bf7
Author: Mario Fusco <[email protected]>
AuthorDate: Wed Sep 18 08:25:15 2024 +0200

    [DROOLS-7631] unify coercion checks between plain drl and executable model 
(#6086)
---
 .../generator/drlxparse/CoercedExpression.java     | 21 +++++-
 .../generator/drlxparse/CoercedExpressionTest.java |  4 +-
 .../org/drools/mvel/MVELConstraintBuilder.java     | 80 ++++------------------
 .../mvel/integrationtests/DateCoercionTest.java    | 36 ++++++++++
 .../main/java/org/drools/util/CoercionUtil.java    | 51 +++++++++++++-
 5 files changed, 120 insertions(+), 72 deletions(-)

diff --git 
a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/CoercedExpression.java
 
b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/CoercedExpression.java
index 4bbd12d963..e26c133273 100644
--- 
a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/CoercedExpression.java
+++ 
b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/CoercedExpression.java
@@ -52,6 +52,8 @@ import static 
org.drools.model.codegen.execmodel.generator.DrlxParseUtil.toClass
 import static 
org.drools.model.codegen.execmodel.generator.DrlxParseUtil.toJavaParserType;
 import static 
org.drools.model.codegen.execmodel.generator.DrlxParseUtil.toStringLiteral;
 import static org.drools.util.ClassUtils.toNonPrimitiveType;
+import static org.drools.util.CoercionUtil.areComparisonCompatible;
+import static org.drools.util.CoercionUtil.areEqualityCompatible;
 
 public class CoercedExpression {
 
@@ -151,9 +153,22 @@ public class CoercedExpression {
             coercedLeft = left;
         }
 
+        checkCoercion(coercedLeft, coercedRight, leftClass, rightClass);
         return new CoercedExpressionResult(coercedLeft, coercedRight, 
rightAsStaticField);
     }
 
+    private void checkCoercion(TypedExpression coercedLeft, TypedExpression 
coercedRight, Class<?> leftClass, Class<?> rightClass) {
+        if (equalityExpr) {
+            if (!areEqualityCompatible(coercedLeft.getRawClass(), 
coercedRight.getRawClass())) {
+                throw new CoercedExpressionException(new 
InvalidExpressionErrorResult("Equality operation requires compatible types. 
Found " + leftClass + " and " + rightClass));
+            }
+        } else {
+            if (!areComparisonCompatible(coercedLeft.getRawClass(), 
coercedRight.getRawClass())) {
+                throw new CoercedExpressionException(new 
InvalidExpressionErrorResult("Comparison operation requires compatible types. 
Found " + leftClass + " and " + rightClass));
+            }
+        }
+    }
+
     private boolean isBoolean(Class<?> leftClass) {
         return Boolean.class.isAssignableFrom(leftClass) || 
boolean.class.isAssignableFrom(leftClass);
     }
@@ -163,12 +178,14 @@ public class CoercedExpression {
     }
 
     private boolean canCoerce() {
-        final Class<?> leftClass = left.getRawClass();
+        return canCoerce(left.getRawClass(), right.getRawClass());
+    }
+
+    private static boolean canCoerce(Class<?> leftClass, Class<?> rightClass) {
         if (!leftClass.isPrimitive() || 
!canCoerceLiteralNumberExpr(leftClass)) {
             return true;
         }
 
-        final Class<?> rightClass = right.getRawClass();
         return rightClass.isPrimitive()
                 || Number.class.isAssignableFrom(rightClass)
                 || Boolean.class == rightClass
diff --git 
a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/generator/drlxparse/CoercedExpressionTest.java
 
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/generator/drlxparse/CoercedExpressionTest.java
index 1264a09698..83e1a2c3a6 100644
--- 
a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/generator/drlxparse/CoercedExpressionTest.java
+++ 
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/generator/drlxparse/CoercedExpressionTest.java
@@ -127,9 +127,9 @@ public class CoercedExpressionTest {
     @Test
     public void castMaps() {
         final TypedExpression left = expr(THIS_PLACEHOLDER + ".getAge()", 
Integer.class);
-        final TypedExpression right = expr("$m.get(\"age\")", 
java.util.Map.class);
+        final TypedExpression right = expr("$m.get(\"age\")", Object.class);
         final CoercedExpression.CoercedExpressionResult coerce = new 
CoercedExpression(left, right, false).coerce();
-        
assertThat(coerce.getCoercedRight()).isEqualTo(expr("(java.lang.Integer)$m.get(\"age\")",
 Map.class));
+        assertThat(coerce.getCoercedRight()).isEqualTo(expr("$m.get(\"age\")", 
Object.class));
     }
 
     @Test
diff --git 
a/drools-mvel/src/main/java/org/drools/mvel/MVELConstraintBuilder.java 
b/drools-mvel/src/main/java/org/drools/mvel/MVELConstraintBuilder.java
index 38809896b7..2b9fb57ed1 100644
--- a/drools-mvel/src/main/java/org/drools/mvel/MVELConstraintBuilder.java
+++ b/drools-mvel/src/main/java/org/drools/mvel/MVELConstraintBuilder.java
@@ -97,6 +97,8 @@ import org.drools.mvel.expr.MVELCompileable;
 import org.drools.mvel.expr.MVELObjectExpression;
 import org.drools.mvel.expr.MvelEvaluator;
 import org.drools.mvel.java.JavaForMvelDialectConfiguration;
+import org.drools.util.CoercionUtil;
+import org.drools.util.MethodUtils;
 import org.kie.api.definition.rule.Rule;
 import org.mvel2.ConversionHandler;
 import org.mvel2.DataConversion;
@@ -426,8 +428,6 @@ public class MVELConstraintBuilder implements 
ConstraintBuilder {
             }
         }
 
-        MVELDialect dialect = (MVELDialect) context.getDialect( "mvel" );
-
         MVELCompilationUnit unit = null;
 
         try {
@@ -439,16 +439,8 @@ public class MVELConstraintBuilder implements 
ConstraintBuilder {
                         ((ClassObjectType) p.getObjectType()).getClassType() );
             }
 
-            unit = dialect.getMVELCompilationUnit( (String) 
predicateDescr.getContent(),
-                                                   analysis,
-                                                   previousDeclarations,
-                                                   localDeclarations,
-                                                   null,
-                                                   context,
-                                                   "drools",
-                                                   KnowledgeHelper.class,
-                                                   context.isInXpath(),
-                                                   
MVELCompilationUnit.Scope.CONSTRAINT );
+            unit = MVELDialect.getMVELCompilationUnit( (String) 
predicateDescr.getContent(), analysis, previousDeclarations, localDeclarations,
+                                                   null, context, "drools", 
KnowledgeHelper.class, context.isInXpath(), 
MVELCompilationUnit.Scope.CONSTRAINT );
         } catch ( final Exception e ) {
             copyErrorLocation(e, predicateDescr);
             context.addError( new DescrBuildError( context.getParentDescr(),
@@ -486,48 +478,14 @@ public class MVELConstraintBuilder implements 
ConstraintBuilder {
 
         @Override
         public boolean areEqualityCompatible(Class<?> c1, Class<?> c2) {
-            if (c1 == NullType.class || c2 == NullType.class) {
-                return true;
-            }
-            if (c1 == String.class || c2 == String.class) {
-                return true;
-            }
-            Class<?> boxed1 = convertFromPrimitiveType(c1);
-            Class<?> boxed2 = convertFromPrimitiveType(c2);
-            if (boxed1.isAssignableFrom(boxed2) || 
boxed2.isAssignableFrom(boxed1)) {
-                return true;
-            }
-            if (Number.class.isAssignableFrom(boxed1) && 
Number.class.isAssignableFrom(boxed2)) {
-                return true;
-            }
-            if (areEqualityCompatibleEnums(boxed1, boxed2)) {
-                return true;
-            }
-            return !Modifier.isFinal(c1.getModifiers()) && 
!Modifier.isFinal(c2.getModifiers());
-        }
-
-        protected boolean areEqualityCompatibleEnums(final Class<?> boxed1,
-                                                     final Class<?> boxed2) {
-            return boxed1.isEnum() && boxed2.isEnum() && 
boxed1.getName().equals(boxed2.getName())
-                    && equalEnumConstants(boxed1.getEnumConstants(), 
boxed2.getEnumConstants());
-        }
-
-        private boolean equalEnumConstants(final Object[] aa,
-                                           final Object[] bb) {
-            if (aa.length != bb.length) {
-                return false;
-            }
-            for (int i = 0; i < aa.length; i++) {
-                if (!Objects.equals(aa[i].getClass().getName(), 
bb[i].getClass().getName())) {
-                    return false;
-                }
-            }
-            return true;
+            return CoercionUtil.areEqualityCompatible(c1 == NullType.class ? 
MethodUtils.NullType.class : c1,
+                                                      c2 == NullType.class ? 
MethodUtils.NullType.class : c2);
         }
 
         @Override
         public boolean areComparisonCompatible(Class<?> c1, Class<?> c2) {
-            return areEqualityCompatible(c1, c2);
+            return CoercionUtil.areComparisonCompatible(c1 == NullType.class ? 
MethodUtils.NullType.class : c1,
+                                                        c2 == NullType.class ? 
MethodUtils.NullType.class : c2);
         }
     }
 
@@ -558,16 +516,8 @@ public class MVELConstraintBuilder implements 
ConstraintBuilder {
             }
             Arrays.sort(previousDeclarations, SortDeclarations.instance);
 
-            MVELCompilationUnit unit = dialect.getMVELCompilationUnit( 
expression,
-                    analysis,
-                    previousDeclarations,
-                    null,
-                    null,
-                    context,
-                    "drools",
-                    KnowledgeHelper.class,
-                    false,
-                    MVELCompilationUnit.Scope.EXPRESSION );
+            MVELCompilationUnit unit = MVELDialect.getMVELCompilationUnit( 
expression, analysis, previousDeclarations, null, null,
+                    context, "drools", KnowledgeHelper.class, false, 
MVELCompilationUnit.Scope.EXPRESSION );
 
             MVELObjectExpression expr = new MVELObjectExpression( unit, 
dialect.getId() );
 
@@ -578,9 +528,7 @@ public class MVELConstraintBuilder implements 
ConstraintBuilder {
             return expr;
         } catch ( final Exception e ) {
             AsmUtil.copyErrorLocation(e, context.getRuleDescr());
-            context.addError( new DescrBuildError( context.getParentDescr(),
-                    context.getRuleDescr(),
-                    null,
+            context.addError( new DescrBuildError( context.getParentDescr(), 
context.getRuleDescr(), null,
                     "Unable to build expression : " + e.getMessage() + "'" + 
expression + "'" ) );
             return null;
         } finally {
@@ -595,10 +543,8 @@ public class MVELConstraintBuilder implements 
ConstraintBuilder {
         return analyzeExpression( expr, conf, new BoundIdentifiers( thisClass 
) );
     }
 
-    private static MVELAnalysisResult analyzeExpression(String expr,
-                                                        ParserConfiguration 
conf,
-                                                        BoundIdentifiers 
availableIdentifiers) {
-        if (expr.trim().length() == 0) {
+    private static MVELAnalysisResult analyzeExpression(String expr, 
ParserConfiguration conf, BoundIdentifiers availableIdentifiers) {
+        if (expr.trim().isEmpty()) {
             MVELAnalysisResult result = analyze( (Set<String> ) 
Collections.EMPTY_SET, availableIdentifiers );
             result.setMvelVariables( new HashMap<>() );
             result.setTypesafe( true );
diff --git 
a/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/DateCoercionTest.java
 
b/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/DateCoercionTest.java
index 5d6a715704..1dd24026b5 100644
--- 
a/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/DateCoercionTest.java
+++ 
b/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/DateCoercionTest.java
@@ -25,11 +25,14 @@ import java.util.List;
 
 import org.drools.testcoverage.common.util.KieBaseTestConfiguration;
 import org.drools.testcoverage.common.util.KieBaseUtil;
+import org.drools.testcoverage.common.util.KieUtil;
 import org.drools.testcoverage.common.util.TestParametersUtil;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.kie.api.KieBase;
+import org.kie.api.builder.KieBuilder;
+import org.kie.api.builder.Message;
 import org.kie.api.runtime.KieSession;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -176,4 +179,37 @@ public class DateCoercionTest {
         assertThat(list.size()).isEqualTo(1);
         assertThat(list.get(0)).isEqualTo("working");
     }
+
+    @Test
+    public void testLocalDateTimeCoercion() {
+        // DROOLS-7631
+        String drl = "import java.util.Date\n" +
+                     "import java.time.LocalDateTime\n" +
+                     "global java.util.List list\n" +
+                     "declare DateContainer\n" +
+                     "     date: Date\n" +
+                     "end\n" +
+                     "declare LocalDateTimeContainer\n" +
+                     "     date: LocalDateTime\n" +
+                     "end\n" +
+                     "\n" +
+                     "rule Init when\n" +
+                     "then\n" +
+                     "    insert(new DateContainer(new Date( 1439882189744L 
)));" +
+                     "    insert(new LocalDateTimeContainer( 
LocalDateTime.now() ));" +
+                     "end\n" +
+                     "\n" +
+                     "rule \"Test rule\"\n" +
+                     "when\n" +
+                     "    DateContainer( $date: date )\n" +
+                     "    LocalDateTimeContainer( date > $date )\n" +
+                     "then\n" +
+                     "    list.add(\"working\");\n" +
+                     "end\n";
+
+        KieBuilder kieBuilder = 
KieUtil.getKieBuilderFromDrls(kieBaseTestConfiguration, false, drl);
+        List<Message> errors = 
kieBuilder.getResults().getMessages(Message.Level.ERROR);
+        assertThat(errors).hasSize(1);
+        assertThat(errors.get(0).getText()).contains("Comparison operation 
requires compatible types");
+    }
 }
diff --git a/drools-util/src/main/java/org/drools/util/CoercionUtil.java 
b/drools-util/src/main/java/org/drools/util/CoercionUtil.java
index f91b2bd235..bb7d5ac0e7 100644
--- a/drools-util/src/main/java/org/drools/util/CoercionUtil.java
+++ b/drools-util/src/main/java/org/drools/util/CoercionUtil.java
@@ -19,10 +19,14 @@
 
 package org.drools.util;
 
+import java.lang.reflect.Modifier;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.time.chrono.ChronoLocalDateTime;
+import java.time.temporal.Temporal;
+import java.util.Objects;
 
-import org.drools.util.MathUtils;
+import static org.drools.util.ClassUtils.convertFromPrimitiveType;
 
 public class CoercionUtil {
 
@@ -194,4 +198,49 @@ public class CoercionUtil {
         }
         return ret;
     }
+
+    public static boolean areEqualityCompatible(Class<?> c1, Class<?> c2) {
+        if (c1 == MethodUtils.NullType.class || c2 == 
MethodUtils.NullType.class) {
+            return true;
+        }
+        if (c1 == String.class || c2 == String.class) {
+            return true;
+        }
+        if (Temporal.class.isAssignableFrom(c1) && 
Temporal.class.isAssignableFrom(c2)) {
+            return true;
+        }
+        Class<?> boxed1 = convertFromPrimitiveType(c1);
+        Class<?> boxed2 = convertFromPrimitiveType(c2);
+        if (boxed1.isAssignableFrom(boxed2) || 
boxed2.isAssignableFrom(boxed1)) {
+            return true;
+        }
+        if (Number.class.isAssignableFrom(boxed1) && 
Number.class.isAssignableFrom(boxed2)) {
+            return true;
+        }
+        if (areEqualityCompatibleEnums(boxed1, boxed2)) {
+            return true;
+        }
+        return !Modifier.isFinal(c1.getModifiers()) && 
!Modifier.isFinal(c2.getModifiers());
+    }
+
+    protected static boolean areEqualityCompatibleEnums(Class<?> boxed1, 
Class<?> boxed2) {
+        return boxed1.isEnum() && boxed2.isEnum() && 
boxed1.getName().equals(boxed2.getName())
+                && equalEnumConstants(boxed1.getEnumConstants(), 
boxed2.getEnumConstants());
+    }
+
+    private static boolean equalEnumConstants(Object[] aa, Object[] bb) {
+        if (aa.length != bb.length) {
+            return false;
+        }
+        for (int i = 0; i < aa.length; i++) {
+            if (!Objects.equals(aa[i].getClass().getName(), 
bb[i].getClass().getName())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static boolean areComparisonCompatible(Class<?> c1, Class<?> c2) {
+        return areEqualityCompatible(c1, c2);
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to