Skip to content
Permalink
Browse files
JEXL-366: compare will try and convert a string to number when one ar…
…gument is a number
  • Loading branch information
henrib committed May 5, 2022
1 parent 38c0b84 commit 33bc10c109403639aca7ef22dd7a883468fd6e30
Showing 2 changed files with 112 additions and 26 deletions.
@@ -497,7 +497,16 @@ protected boolean isNumberable(final Object o) {
}

/**
* 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 Object shiftRightUnsigned(Object left, Object right) {
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 @@ protected int compare(final Object left, final Object right, final String operat
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));
@@ -270,6 +270,7 @@ public void testRightNullOperand2() throws Exception {
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 void testAddWithStringsStrict() {
}

@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 void testOption() throws Exception {
}

@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 String toString() {
}
}

// 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 void testArithmeticPlus() throws Exception {
}

@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 Object call(final Integer... arg) {
}

@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 @@ private static int getJavaVersion() {
}

@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 void testEmptyLong() throws Exception {
}

@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 void testEmptyFloat() throws Exception {
}

@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 @@ void checkEmpty(final Object x, final boolean expect) {
}

@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 void testCoerceInteger() throws Exception {
}

@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 void testCoerceLong() throws Exception {
}

@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 void testCoerceDouble() throws Exception {
}

@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 void testCoerceBigInteger() throws Exception {
}

@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 void testCoerceBigDecimal() throws Exception {
}

@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 void testAtomicBoolean() throws Exception {
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);
}
}
}

0 comments on commit 33bc10c

Please sign in to comment.