You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2022/05/05 22:21:17 UTC

[commons-jexl] branch master updated: JEXL-366: compare will try and convert a string to number when one argument is a number

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

henrib pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git


The following commit(s) were added to refs/heads/master by this push:
     new 33bc10c1 JEXL-366: compare will try and convert a string to number when one argument is a number
33bc10c1 is described below

commit 33bc10c109403639aca7ef22dd7a883468fd6e30
Author: henrib <he...@apache.org>
AuthorDate: Fri May 6 00:19:17 2022 +0200

    JEXL-366: compare will try and convert a string to number when one argument is a number
---
 .../org/apache/commons/jexl3/JexlArithmetic.java   | 46 ++++++++---
 .../org/apache/commons/jexl3/ArithmeticTest.java   | 92 ++++++++++++++++++----
 2 files changed, 112 insertions(+), 26 deletions(-)

diff --git a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
index b34d3678..54afcf93 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
@@ -497,7 +497,16 @@ public class JexlArithmetic {
     }
 
     /**
-     * Given a Number, return back the value using the smallest type the result
+     * The last method called before returning a result from a script execution.
+     * @param returned the returned value
+     * @return the controlled returned value
+     */
+    public Object controlReturn(Object returned) {
+        return returned;
+    }
+
+    /**
+     * Given a Number, return the value using the smallest type the result
      * will fit into.
      * <p>This works hand in hand with parameter 'widening' in java
      * method calls, e.g. a call to substring(int,int) with an int and a long
@@ -1347,6 +1356,21 @@ public class JexlArithmetic {
         return l >>> r;
     }
 
+    /**
+     * Converts an arg to a long for comparison purpose.
+     * @param arg the arg
+     * @return a long
+     * @throws NumberFormatException if the
+     */
+    private long comparableLong(Object arg) throws NumberFormatException {
+        if (arg instanceof String) {
+            String s = (String) arg;
+            return s.isEmpty()? 0 :(long) Double.parseDouble((String) arg);
+        } else {
+            return toLong(arg);
+        }
+    }
+
     /**
      * Performs a comparison.
      *
@@ -1390,15 +1414,19 @@ public class JexlArithmetic {
                 return 0;
             }
             if (isNumberable(left) || isNumberable(right)) {
-                final long lhs = toLong(left);
-                final long rhs = toLong(right);
-                if (lhs < rhs) {
-                    return -1;
-                }
-                if (lhs > rhs) {
-                    return +1;
+                try {
+                    final long lhs = comparableLong(left);
+                    final long rhs = comparableLong(right);
+                    if (lhs < rhs) {
+                        return -1;
+                    }
+                    if (lhs > rhs) {
+                        return +1;
+                    }
+                    return 0;
+                } catch(NumberFormatException xformat) {
+                    // ignore it, continue in sequence
                 }
-                return 0;
             }
             if (left instanceof String || right instanceof String) {
                 return toString(left).compareTo(toString(right));
diff --git a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
index 8b8cacc5..3eb611fb 100644
--- a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
@@ -270,6 +270,7 @@ public class ArithmeticTest extends JexlTestCase {
         asserter.failExpression("left > y.right", ".*null.*");
         asserter.failExpression("left >= y.right", ".*null.*");
     }
+
     @Test
     public void testNullOperands() throws Exception {
         asserter.setVariable("left", null);
@@ -781,20 +782,20 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testNullArgs() throws Exception {
+    public void testNullArgs() {
         JexlEngine jexl =  new JexlBuilder().arithmetic(new JexlArithmetic(true) {
             @Override public boolean isStrict(JexlOperator op) {
                 return JexlOperator.ADD == op? false: super.isStrict(op);
             }
         }).create();
         JexlScript script = jexl.createScript("'1.2' + x ", "x");
-        Object result = script.execute(null, null);
+        Object result = script.execute(null);
         Assert.assertEquals("1.2", result);
     }
 
     @Test
-    public void testOption() throws Exception {
-        final Map<String, Object> vars = new HashMap<String, Object>();
+    public void testOption() {
+        final Map<String, Object> vars = new HashMap<>();
         final JexlEvalContext context = new JexlEvalContext(vars);
         final JexlOptions options = context.getEngineOptions();
         options.setStrictArithmetic(true);
@@ -811,7 +812,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testIsFloatingPointPattern() throws Exception {
+    public void testIsFloatingPointPattern() {
         final JexlArithmetic ja = new JexlArithmetic(true);
 
         Assert.assertFalse(ja.isFloatingPointNumber("floating point"));
@@ -918,7 +919,7 @@ public class ArithmeticTest extends JexlTestCase {
         }
     }
 
-    // an arithmetic that know how to subtract strings
+    // an arithmetic that knows how to deal with vars
     public static class ArithmeticPlus extends JexlArithmetic {
         public ArithmeticPlus(final boolean strict) {
             super(strict);
@@ -1041,7 +1042,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testArithmeticPlusNoCache() throws Exception {
+    public void testArithmeticPlusNoCache() {
         final JexlEngine jexl = new JexlBuilder().cache(0).arithmetic(new ArithmeticPlus(false)).create();
         final JexlContext jc = new EmptyTestContext();
         runOverload(jexl, jc);
@@ -1258,7 +1259,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testJexl173() throws Exception {
+    public void testJexl173() {
         final JexlEngine jexl = new JexlBuilder().create();
         final JexlContext jc = new MapContext();
         final Callable173 c173 = new Callable173();
@@ -1522,7 +1523,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testEmptyLong() throws Exception {
+    public void testEmptyLong()  {
         Object x;
         x = JEXL.createScript("new('java.lang.Long', 4294967296)").execute(null);
         Assert.assertEquals(4294967296L, ((Long) x).longValue());
@@ -1539,7 +1540,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testEmptyFloat() throws Exception {
+    public void testEmptyFloat()  {
         Object x;
         x = JEXL.createScript("4294967296.f").execute(null);
         Assert.assertEquals(4294967296.0f, (Float) x, EPSILON);
@@ -1555,7 +1556,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testEmptyDouble() throws Exception {
+    public void testEmptyDouble()  {
         Object x;
         x = JEXL.createScript("4294967296.d").execute(null);
         Assert.assertEquals(4294967296.0d, (Double) x, EPSILON);
@@ -1584,7 +1585,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testCoerceInteger() throws Exception {
+    public void testCoerceInteger() {
         final JexlArithmetic ja = JEXL.getArithmetic();
         final JexlEvalContext ctxt = new JexlEvalContext();
         final JexlOptions options = ctxt.getEngineOptions();
@@ -1603,7 +1604,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testCoerceLong() throws Exception {
+    public void testCoerceLong() {
         final JexlArithmetic ja = JEXL.getArithmetic();
         final JexlEvalContext ctxt = new JexlEvalContext();
         final JexlOptions options = ctxt.getEngineOptions();
@@ -1622,7 +1623,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testCoerceDouble() throws Exception {
+    public void testCoerceDouble() {
         final JexlArithmetic ja = JEXL.getArithmetic();
         final JexlEvalContext ctxt = new JexlEvalContext();
         final JexlOptions options = ctxt.getEngineOptions();
@@ -1641,7 +1642,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testCoerceBigInteger() throws Exception {
+    public void testCoerceBigInteger() {
         final JexlArithmetic ja = JEXL.getArithmetic();
         final JexlEvalContext ctxt = new JexlEvalContext();
         final JexlOptions options = ctxt.getEngineOptions();
@@ -1660,7 +1661,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testCoerceBigDecimal() throws Exception {
+    public void testCoerceBigDecimal() {
         final JexlArithmetic ja = JEXL.getArithmetic();
         final JexlEvalContext ctxt = new JexlEvalContext();
         final JexlOptions options = ctxt.getEngineOptions();
@@ -1679,7 +1680,7 @@ public class ArithmeticTest extends JexlTestCase {
     }
 
     @Test
-    public void testAtomicBoolean() throws Exception {
+    public void testAtomicBoolean() {
         // in a condition
         JexlScript e = JEXL.createScript("if (x) 1 else 2;", "x");
         final JexlContext jc = new MapContext();
@@ -1748,4 +1749,61 @@ public class ArithmeticTest extends JexlTestCase {
         o = e.execute(jc, ab);
         Assert.assertTrue((Boolean) o);
     }
+
+    @Test
+    public void testCompare() {
+        // JEXL doesn't support more than one operator in the same expression, for example: 1 == 1 == 1
+        final Object[] EXPRESSIONS = {
+                // Basic compare
+                "1 == 1", true,
+                "1 != 1", false,
+                "1 != 2", true,
+                "1 > 2", false,
+                "1 >= 2", false,
+                "1 < 2", true,
+                "1 <= 2", true,
+                // Int <-> Float Coercion
+                "1.0 == 1", true,
+                "1 == 1.0", true,
+                "1.1 != 1", true,
+                "1.1 < 2", true,
+                // Big Decimal <-> Big Integer Coercion
+                "1.0b == 1h", true,
+                "1h == 1.0b", true,
+                "1.1b != 1h", true,
+                "1.1b < 2h", true,
+                // Mix all type of numbers
+                "1l == 1.0", true, // long and int
+                "1.0d == 1.0f", true, // double and float
+                "1l == 1.0b", true,
+                "1l == 1h", true,
+                "1.0d == 1.0b", true,
+                "1.0f == 1.0b", true,
+                "1.0d == 1h", true,
+                "1.0f == 1h", true,
+                // numbers and strings
+                "'1' == 1", true,
+                "'1' == 1l", true,
+                "'1' == 1h", true,
+                "'' == 0", true, // empty string is coerced to zero (ECMA compliance)
+                "'1.0' == 1", true,
+                "'1.0' == 1.0f", true,
+                "'1.0' == 1.0d", true,
+                "'1.0' == 1.0b", true,
+                "'1.01' == 1.01", true,
+                "1.0 >= '1'", true,
+                "1.0 > '1'", false,
+        };
+        final JexlEngine jexl = new JexlBuilder().create();
+        final JexlContext jc = new EmptyTestContext();
+        JexlExpression expression;
+
+        for (int e = 0; e < EXPRESSIONS.length; e += 2) {
+            final String stext = (String) EXPRESSIONS[e];
+            final Object expected = EXPRESSIONS[e + 1];
+            expression = jexl.createExpression(stext);
+            final Object result = expression.evaluate(jc);
+            Assert.assertEquals("failed on " + stext, expected, result);
+        }
+    }
 }