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 2015/07/25 18:05:49 UTC

svn commit: r1692633 - in /commons/proper/jexl/trunk/src: main/java/org/apache/commons/jexl3/JexlArithmetic.java site/xdoc/changes.xml test/java/org/apache/commons/jexl3/ArithmeticTest.java test/java/org/apache/commons/jexl3/ForEachTest.java

Author: henrib
Date: Sat Jul 25 16:05:49 2015
New Revision: 1692633

URL: http://svn.apache.org/r1692633
Log:
JEXL:
JEXL-168: JexlArithmetic.add(...) updated so that if the arguments are 2 strings or the arithmetic is strict and one of them is a string, add performs a string concatenation. 
JEXL-169: JexlArithmetic.isFloatingPointNumber(...)  now checks much more precisely whether a string is amenable to float coercion.

Modified:
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
    commons/proper/jexl/trunk/src/site/xdoc/changes.xml
    commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
    commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ForEachTest.java

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java?rev=1692633&r1=1692632&r2=1692633&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java Sat Jul 25 16:05:49 2015
@@ -23,6 +23,7 @@ import java.math.BigInteger;
 import java.math.MathContext;
 import java.util.Collection;
 import java.util.Map;
+import java.util.regex.Pattern;
 
 /**
  * Perform arithmetic.
@@ -351,7 +352,8 @@ public class JexlArithmetic {
 
     /**
      * Checks whether this JexlArithmetic instance
-     * strictly considers null as an error when used as operand unexpectedly.
+     * strictly considers null as an error when used as operand unexpectedly
+     * and forces add(...) to concatenate strings (instead of attempting number conversion).
      * @return true if strict, false if lenient
      */
     public boolean isStrict() {
@@ -411,6 +413,10 @@ public class JexlArithmetic {
     }
 
     /**
+     * The float regular expression pattern.
+     */
+    public static final Pattern FLOAT_PATTERN = Pattern.compile("^[+-]?\\d*(\\.\\d*)?([eE]?[+-]?\\d*)?$");
+    /**
      * Test if the passed value is a floating point number, i.e. a float, double
      * or string with ( "." | "E" | "e").
      *
@@ -422,8 +428,18 @@ public class JexlArithmetic {
             return true;
         }
         if (val instanceof String) {
-            String string = (String) val;
-            return string.indexOf('.') != -1 || string.indexOf('e') != -1 || string.indexOf('E') != -1;
+            String str = (String) val;
+            for(int c = 0; c < str.length(); ++c) {
+                char ch = str.charAt(c);
+                // we need at least a marker that says it is a float
+                if (ch == '.' || ch == 'E' || ch == 'e') {
+                    return FLOAT_PATTERN.matcher(str).matches();
+                }
+                // and it must be a number
+                if (ch != '+' && ch != '-' && ch < '0' && ch > '9') {
+                    break;
+                }
+            }
         }
         return false;
     }
@@ -640,32 +656,37 @@ public class JexlArithmetic {
         if (left == null && right == null) {
             return controlNullNullOperands();
         }
-        try {
-            // if either are bigdecimal use that type
-            if (left instanceof BigDecimal || right instanceof BigDecimal) {
-                BigDecimal l = toBigDecimal(left);
-                BigDecimal r = toBigDecimal(right);
-                BigDecimal result = l.add(r, getMathContext());
-                return narrowBigDecimal(left, right, result);
-            }
-            // if either are floating point (double or float) use double
-            if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
-                double l = toDouble(left);
-                double r = toDouble(right);
-                return new Double(l + r);
-            }
-            // otherwise treat as integers
-            BigInteger l = toBigInteger(left);
-            BigInteger r = toBigInteger(right);
-            BigInteger result = l.add(r);
-            return narrowBigInteger(left, right, result);
-        } catch (java.lang.NumberFormatException nfe) {
-            // Well, use strings!
-            if (left == null || right == null) {
-                controlNullOperand();
+        boolean strconcat = strict
+                            ? left instanceof String || right instanceof String
+                            : left instanceof String && right instanceof String;
+        if (!strconcat) {
+            try {
+                // if either are bigdecimal use that type
+                if (left instanceof BigDecimal || right instanceof BigDecimal) {
+                    BigDecimal l = toBigDecimal(left);
+                    BigDecimal r = toBigDecimal(right);
+                    BigDecimal result = l.add(r, getMathContext());
+                    return narrowBigDecimal(left, right, result);
+                }
+                // if either are floating point (double or float) use double
+                if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
+                    double l = toDouble(left);
+                    double r = toDouble(right);
+                    return new Double(l + r);
+                }
+                // otherwise treat as integers
+                BigInteger l = toBigInteger(left);
+                BigInteger r = toBigInteger(right);
+                BigInteger result = l.add(r);
+                return narrowBigInteger(left, right, result);
+            } catch (java.lang.NumberFormatException nfe) {
+                // Well, use strings!
+                if (left == null || right == null) {
+                    controlNullOperand();
+                }
             }
-            return toString(left).concat(toString(right));
         }
+        return toString(left).concat(toString(right));
     }
 
     /**

Modified: commons/proper/jexl/trunk/src/site/xdoc/changes.xml
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/site/xdoc/changes.xml?rev=1692633&r1=1692632&r2=1692633&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/site/xdoc/changes.xml (original)
+++ commons/proper/jexl/trunk/src/site/xdoc/changes.xml Sat Jul 25 16:05:49 2015
@@ -26,6 +26,12 @@
     </properties>
     <body>
         <release version="3.0" date="unreleased">
+            <action dev="henrib" type="fix" issue="JEXL-169" due-to="Robert Neßelrath">
+                A string is wrongly identified as FloatingPointNumber
+            </action>
+            <action dev="henrib" type="fix" issue="JEXL-168" due-to="Dmitri Blinov">
+                Dedicated operator for String concatenation
+            </action>
             <action dev="henrib" type="fix" issue="JEXL-79" due-to="sebb">
                 Add support for growable arrays (ArrayLists)
             </action>

Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java?rev=1692633&r1=1692632&r2=1692633&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java (original)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java Sat Jul 25 16:05:49 2015
@@ -27,6 +27,7 @@ import java.math.BigDecimal;
 import java.math.BigInteger;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
+import static org.apache.commons.jexl3.JexlArithmetic.FLOAT_PATTERN;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -411,6 +412,111 @@ public class ArithmeticTest extends Jexl
         Assert.assertTrue((Boolean) result);
     }
 
+
+    @Test
+    public void testAddWithStringsLenient() throws Exception {
+        JexlEngine jexl = new JexlBuilder().arithmetic(new JexlArithmetic(false)).create();
+        JexlScript script;
+        Object result;
+        script = jexl.createScript("'a' + 0");
+        result = script.execute(null);
+        Assert.assertEquals("a0", result);
+
+        script = jexl.createScript("0 + 'a' ");
+        result = script.execute(null);
+        Assert.assertEquals("0a", result);
+
+        script = jexl.createScript("0 + '1.2' ");
+        result = script.execute(null);
+        Assert.assertEquals(1.2d, (Double) result, EPSILON);
+
+        script = jexl.createScript("'1.2' + 1.2 ");
+        result = script.execute(null);
+        Assert.assertEquals(2.4d, (Double) result, EPSILON);
+
+        script = jexl.createScript("1.2 + 1.2 ");
+        result = script.execute(null);
+        Assert.assertEquals(2.4d, (Double) result, EPSILON);
+
+        script = jexl.createScript("1.2 + '1.2' ");
+        result = script.execute(null);
+        Assert.assertEquals(2.4d, (Double) result, EPSILON);
+
+        script = jexl.createScript("'1.2' + 0 ");
+        result = script.execute(null);
+        Assert.assertEquals(1.2d, (Double) result, EPSILON);
+
+        script = jexl.createScript("'1.2' + '1.2' ");
+        result = script.execute(null);
+        Assert.assertEquals("1.21.2", result);
+    }
+
+    @Test
+    public void testAddWithStringsStrict() throws Exception {
+        JexlEngine jexl = new JexlBuilder().arithmetic(new JexlArithmetic(true)).create();
+        JexlScript script;
+        Object result;
+        script = jexl.createScript("'a' + 0");
+        result = script.execute(null);
+        Assert.assertEquals("a0", result);
+
+        script = jexl.createScript("0 + 'a' ");
+        result = script.execute(null);
+        Assert.assertEquals("0a", result);
+
+        script = jexl.createScript("0 + '1.2' ");
+        result = script.execute(null);
+        Assert.assertEquals("01.2", result);
+
+        script = jexl.createScript("'1.2' + 1.2 ");
+        result = script.execute(null);
+        Assert.assertEquals("1.21.2", result);
+
+        script = jexl.createScript("1.2 + 1.2 ");
+        result = script.execute(null);
+        Assert.assertEquals(2.4d, (Double) result, EPSILON);
+
+        script = jexl.createScript("1.2 + '1.2' ");
+        result = script.execute(null);
+        Assert.assertEquals("1.21.2", result);
+
+        script = jexl.createScript("'1.2' + 0 ");
+        result = script.execute(null);
+        Assert.assertEquals("1.20", result);
+
+        script = jexl.createScript("'1.2' + '1.2' ");
+        result = script.execute(null);
+        Assert.assertEquals("1.21.2", result);
+    }
+
+    @Test
+    public void testIsFloatingPointPattern() throws Exception {
+        JexlArithmetic ja = new JexlArithmetic(true);
+
+        Assert.assertFalse(ja.isFloatingPointNumber("floating point"));
+        Assert.assertFalse(ja.isFloatingPointNumber("a1."));
+        Assert.assertFalse(ja.isFloatingPointNumber("b1.2"));
+        Assert.assertFalse(ja.isFloatingPointNumber("-10.2a-34"));
+        Assert.assertFalse(ja.isFloatingPointNumber("+10.2a+34"));
+        Assert.assertFalse(ja.isFloatingPointNumber("0"));
+        Assert.assertFalse(ja.isFloatingPointNumber("1"));
+
+        Assert.assertTrue(ja.isFloatingPointNumber("0."));
+        Assert.assertTrue(ja.isFloatingPointNumber("1."));
+        Assert.assertTrue(ja.isFloatingPointNumber("1.2"));
+        Assert.assertTrue(ja.isFloatingPointNumber("1.2e3"));
+        Assert.assertTrue(ja.isFloatingPointNumber("1.2e34"));
+        Assert.assertTrue(ja.isFloatingPointNumber("10.2e34"));
+        Assert.assertTrue(ja.isFloatingPointNumber("+10.2e34"));
+        Assert.assertTrue(ja.isFloatingPointNumber("-10.2e34"));
+        Assert.assertTrue(ja.isFloatingPointNumber("10.2e-34"));
+        Assert.assertTrue(ja.isFloatingPointNumber("10.2e+34"));
+        Assert.assertTrue(ja.isFloatingPointNumber("-10.2e-34"));
+        Assert.assertTrue(ja.isFloatingPointNumber("+10.2e+34"));
+        Assert.assertTrue(ja.isFloatingPointNumber("-10.2E-34"));
+        Assert.assertTrue(ja.isFloatingPointNumber("+10.2E+34"));
+    }
+
     public static class EmptyTestContext extends MapContext implements JexlContext.NamespaceResolver {
         public static int log(Object fmt, Object... arr) {
             //System.out.println(String.format(fmt.toString(), arr));

Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ForEachTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ForEachTest.java?rev=1692633&r1=1692632&r2=1692633&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ForEachTest.java (original)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ForEachTest.java Sat Jul 25 16:05:49 2015
@@ -109,7 +109,7 @@ public class ForEachTest extends JexlTes
         JexlScript exs1 = JEXL.createScript("foreach(item in list) { x = x + item; }");
         JexlScript[] exs = {exs0, exs1};
         JexlContext jc = new MapContext();
-        jc.set("list", new Object[]{"2", "3"});
+        jc.set("list", new Object[]{2, 3});
         for (int ex = 0; ex < exs.length; ++ex) {
             jc.set("x", new Integer(1));
             Object o = exs[ex].execute(jc);