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);