Repository: mvel/mvel
Pull request #337 (2023-09-06 10:22:48)

[DROOLS-7540] Coercion from Interger literal to BigDecimal bind variable
https://issues.redhat.com/browse/DROOLS-7540

This is a fix to solve test cases in https://github.com/kiegroup/drools/pull/5498

Drools uses `SimpleVariableSpaceModel` to create `IndexedVariableResolverFactory`. What this PR does are

* Use `SimpleSTValueResolver` instead of `SimpleValueResolver` to enable coercion
* Enhance `IndexVariableResolver` to enable coercion



Patch:
diff --git a/src/main/java/org/mvel2/integration/impl/ClassImportResolverFactory.java b/src/main/java/org/mvel2/integration/impl/ClassImportResolverFactory.java
index 577cd348..dbae4d88 100644
--- a/src/main/java/org/mvel2/integration/impl/ClassImportResolverFactory.java
+++ b/src/main/java/org/mvel2/integration/impl/ClassImportResolverFactory.java
@@ -59,7 +59,7 @@ public class ClassImportResolverFactory extends BaseVariableResolverFactory {
       nextFactory = new MapVariableResolverFactory(new HashMap());
     }
 
-    return nextFactory.createVariable(name, value);
+    return nextFactory.createVariable(name, value, type);
   }
 
   public Class addClass(Class clazz) {
diff --git a/src/main/java/org/mvel2/integration/impl/IndexVariableResolver.java b/src/main/java/org/mvel2/integration/impl/IndexVariableResolver.java
index 7f439903..ce5ee641 100644
--- a/src/main/java/org/mvel2/integration/impl/IndexVariableResolver.java
+++ b/src/main/java/org/mvel2/integration/impl/IndexVariableResolver.java
@@ -2,13 +2,18 @@ package org.mvel2.integration.impl;
 
 import org.mvel2.integration.VariableResolver;
 
+import static org.mvel2.integration.impl.SimpleSTValueResolver.handleTypeCoercion;
+import static org.mvel2.util.ParseTools.isNumeric;
+
 public class IndexVariableResolver implements VariableResolver {
   private int indexPos;
   private Object[] vars;
+  private Class type;
 
   public IndexVariableResolver(int indexPos, Object[] vars) {
     this.indexPos = indexPos;
     this.vars = vars;
+    initializeType(vars[indexPos]);
   }
 
   public String getName() {
@@ -16,10 +21,11 @@ public class IndexVariableResolver implements VariableResolver {
   }
 
   public Class getType() {
-    return null;
+    return type;
   }
 
   public void setStaticType(Class type) {
+      this.type = type;
   }
 
   public int getFlags() {
@@ -31,6 +37,27 @@ public class IndexVariableResolver implements VariableResolver {
   }
 
   public void setValue(Object value) {
-    vars[indexPos] = value;
+    initializeType(value); // Initialize type if not yet done
+
+    if (type == Object.class || type == null) {
+      // IndexVariableResolver doesn't know the original declared type, so cannot deal with polymorphism
+      // Just accept the value
+      vars[indexPos] = value;
+    } else {
+      // Coerce numeric types
+      vars[indexPos] = handleTypeCoercion(type, value);
+    }
+  }
+
+  private void initializeType(Object value) {
+    if (type == null && value != null) {
+      if (isNumeric(value)) {
+        // Use type only when numeric
+        type = value.getClass();
+      } else {
+        // Otherwise, accept any type
+        type = Object.class;
+      }
+    }
   }
 }
diff --git a/src/main/java/org/mvel2/integration/impl/IndexedVariableResolverFactory.java b/src/main/java/org/mvel2/integration/impl/IndexedVariableResolverFactory.java
index f6fb84fe..75243062 100644
--- a/src/main/java/org/mvel2/integration/impl/IndexedVariableResolverFactory.java
+++ b/src/main/java/org/mvel2/integration/impl/IndexedVariableResolverFactory.java
@@ -75,6 +75,9 @@ public class IndexedVariableResolverFactory extends BaseVariableResolverFactory
   public VariableResolver createVariable(String name, Object value, Class<?> type) {
     VariableResolver vr = getResolver(name);
     if (vr != null) {
+      if (vr instanceof SimpleSTValueResolver) {
+          ((SimpleSTValueResolver) vr).setStaticType(type);
+      }
       vr.setValue(value);
     }
     return vr;
diff --git a/src/main/java/org/mvel2/integration/impl/SimpleSTValueResolver.java b/src/main/java/org/mvel2/integration/impl/SimpleSTValueResolver.java
index 043b23de..bc9e020d 100644
--- a/src/main/java/org/mvel2/integration/impl/SimpleSTValueResolver.java
+++ b/src/main/java/org/mvel2/integration/impl/SimpleSTValueResolver.java
@@ -64,7 +64,7 @@ public class SimpleSTValueResolver implements VariableResolver {
     this.value = handleTypeCoercion(type, value);
   }
 
-  private static Object handleTypeCoercion(Class type, Object value) {
+  public static Object handleTypeCoercion(Class type, Object value) {
     if (type != null && value != null && value.getClass() != type) {
       if (!canConvert(type, value.getClass())) {
         throw new RuntimeException("cannot assign " + value.getClass().getName() + " to type: "
diff --git a/src/main/java/org/mvel2/util/SimpleVariableSpaceModel.java b/src/main/java/org/mvel2/util/SimpleVariableSpaceModel.java
index f3304ae6..51bc2b45 100644
--- a/src/main/java/org/mvel2/util/SimpleVariableSpaceModel.java
+++ b/src/main/java/org/mvel2/util/SimpleVariableSpaceModel.java
@@ -4,7 +4,7 @@ import org.mvel2.integration.VariableResolver;
 import org.mvel2.integration.VariableResolverFactory;
 import org.mvel2.integration.impl.IndexVariableResolver;
 import org.mvel2.integration.impl.IndexedVariableResolverFactory;
-import org.mvel2.integration.impl.SimpleValueResolver;
+import org.mvel2.integration.impl.SimpleSTValueResolver;
 
 /**
  * @author Mike Brock .
@@ -18,7 +18,7 @@ public class SimpleVariableSpaceModel extends VariableSpaceModel {
     VariableResolver[] resolvers = new VariableResolver[allVars.length];
     for (int i = 0; i < resolvers.length; i++) {
       if (i >= vals.length) {
-        resolvers[i] = new SimpleValueResolver(null);
+        resolvers[i] = new SimpleSTValueResolver(null, null);
       }
       else {
         resolvers[i] = new IndexVariableResolver(i, vals);


Test patch:
diff --git a/src/test/java/org/mvel2/tests/core/TypesAndInferenceTests.java b/src/test/java/org/mvel2/tests/core/TypesAndInferenceTests.java
index 8847491b..966f4b4a 100644
--- a/src/test/java/org/mvel2/tests/core/TypesAndInferenceTests.java
+++ b/src/test/java/org/mvel2/tests/core/TypesAndInferenceTests.java
@@ -1,5 +1,23 @@
 package org.mvel2.tests.core;
 
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.math.BigDecimal;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
 import org.mvel2.CompileException;
 import org.mvel2.ConversionHandler;
 import org.mvel2.DataConversion;
@@ -9,6 +27,7 @@ import org.mvel2.ParserContext;
 import org.mvel2.compiler.CompiledExpression;
 import org.mvel2.compiler.ExecutableStatement;
 import org.mvel2.compiler.ExpressionCompiler;
+import org.mvel2.integration.VariableResolverFactory;
 import org.mvel2.integration.impl.DefaultLocalVariableResolverFactory;
 import org.mvel2.integration.impl.StaticMethodImportResolverFactory;
 import org.mvel2.optimizers.OptimizerFactory;
@@ -20,23 +39,14 @@ import org.mvel2.tests.core.res.MyInterface2;
 import org.mvel2.tests.core.res.MyInterface3;
 import org.mvel2.tests.core.res.SampleBean;
 import org.mvel2.util.MethodStub;
+import org.mvel2.util.SimpleVariableSpaceModel;
 
-import java.io.Serializable;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.Formatter;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-
-import static org.mvel2.MVEL.*;
+import static org.mvel2.MVEL.analyze;
+import static org.mvel2.MVEL.compileExpression;
+import static org.mvel2.MVEL.compileSetExpression;
+import static org.mvel2.MVEL.executeExpression;
+import static org.mvel2.MVEL.executeSetExpression;
+import static org.mvel2.MVEL.setProperty;
 
 /**
  * @author Mike Brock .
@@ -1606,4 +1616,91 @@ public class TypesAndInferenceTests extends AbstractTest {
     Class resultType =  analyze(str, pctx);
     assertEquals(java.util.Optional.class, resultType);
   }
+
+    public void testBigDecimalVariableCoercionWithSimpleSTValueResolver_shouldCoerce() {
+        String expression = "BigDecimal $bdValue = 5;";
+
+        ParserContext ctx = createParserContext();
+        Serializable compileExpression = MVEL.compileExpression(expression, ctx);
+
+        String[] varNames = new String[]{"$bdValue"};
+        SimpleVariableSpaceModel simpleVariableSpaceModel = new SimpleVariableSpaceModel(varNames);
+        VariableResolverFactory factory = simpleVariableSpaceModel.createFactory(new Object[]{}); // $bdValue is a local variable. Use SimpleSTValueResolver
+        MVEL.executeExpression(compileExpression, ctx, factory);
+        Object value = factory.getVariableResolver("$bdValue").getValue();
+        assertEquals(BigDecimal.class, value.getClass()); // coercion
+    }
+
+    private ParserContext createParserContext() {
+        ParserConfiguration conf = new ParserConfiguration();
+        conf.addImport(BigDecimal.class);
+        conf.addImport(Collection.class);
+        conf.addImport(ArrayList.class);
+        conf.addImport(HashSet.class);
+        ParserContext ctx = new ParserContext(conf);
+        ctx.setStrictTypeEnforcement(true);
+        ctx.setStrongTyping(true);
+        ctx.setIndexAllocation(true);
+        return ctx;
+    }
+
+    public void testBigDecimalVariableCoercionTwiceWithSimpleSTValueResolver_shouldCoerce() {
+        String expression = "BigDecimal $bdValue = 0; $bdValue = 5;";
+
+        ParserContext ctx = createParserContext();
+        Serializable compileExpression = MVEL.compileExpression(expression, ctx);
+
+        String[] varNames = new String[]{"$bdValue"};
+        SimpleVariableSpaceModel simpleVariableSpaceModel = new SimpleVariableSpaceModel(varNames);
+        VariableResolverFactory factory = simpleVariableSpaceModel.createFactory(new Object[]{}); // $bdValue is a local variable. Use SimpleSTValueResolver
+        MVEL.executeExpression(compileExpression, ctx, factory);
+        Object value = factory.getVariableResolver("$bdValue").getValue();
+        assertEquals(BigDecimal.class, value.getClass()); // coercion
+    }
+
+    public void testCollectionVariableAssignmentTwiceWithSimpleSTValueResolver_shouldReplace() {
+        String expression = "Collection $collection = new ArrayList(); $collection = new HashSet();";
+
+        ParserContext ctx = createParserContext();
+        Serializable compileExpression = MVEL.compileExpression(expression, ctx);
+
+        String[] varNames = new String[]{"$collection"};
+        SimpleVariableSpaceModel simpleVariableSpaceModel = new SimpleVariableSpaceModel(varNames);
+        VariableResolverFactory factory = simpleVariableSpaceModel.createFactory(new Object[]{}); // $collection is a local variable. Use SimpleSTValueResolver
+        MVEL.executeExpression(compileExpression, ctx, factory);
+        Object value = factory.getVariableResolver("$collection").getValue();
+        assertEquals(HashSet.class, value.getClass()); // replaced
+    }
+
+    public void testBigDecimalBindVariableCoercionWithIndexVariableResolver_shouldCoerce() {
+        String expression = "$bdValue = 5;";
+
+        ParserContext ctx = createParserContext();
+        String[] varNames = new String[]{"$bdValue"};
+        ctx.addIndexedInput(varNames);
+        ctx.addInput("$bdValue", BigDecimal.class); // bind variable
+        Serializable compileExpression = MVEL.compileExpression(expression, ctx);
+
+        SimpleVariableSpaceModel simpleVariableSpaceModel = new SimpleVariableSpaceModel(varNames);
+        VariableResolverFactory factory = simpleVariableSpaceModel.createFactory(new Object[]{new BigDecimal("0")}); // $bdValue has a value. Use IndexVariableResolver
+        MVEL.executeExpression(compileExpression, ctx, factory);
+        Object value = factory.getVariableResolver("$bdValue").getValue();
+        assertEquals(BigDecimal.class, value.getClass()); // coercion
+    }
+
+    public void testCollectionBindVariableAssignmentWithIndexVariableResolver_shouldReplace() {
+        String expression = "$collection = new HashSet();";
+
+        ParserContext ctx = createParserContext();
+        String[] varNames = new String[]{"$collection"};
+        ctx.addIndexedInput(varNames);
+        ctx.addInput("$collection", Collection.class); // bind variable
+        Serializable compileExpression = MVEL.compileExpression(expression, ctx);
+
+        SimpleVariableSpaceModel simpleVariableSpaceModel = new SimpleVariableSpaceModel(varNames);
+        VariableResolverFactory factory = simpleVariableSpaceModel.createFactory(new Object[]{new ArrayList()}); // $collection has a value. Use IndexVariableResolver
+        MVEL.executeExpression(compileExpression, ctx, factory);
+        Object value = factory.getVariableResolver("$collection").getValue();
+        assertEquals(HashSet.class, value.getClass()); // replaced
+    }
 }