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 0fdb020c JEXL: getting ready for 3.4; - added tests to (re)improve coverage; - removed useless code; - updated doc, changes, release notes 0fdb020c is described below commit 0fdb020c2eeec9f03b8615062620fc98c8ab65d1 Author: Henri Biestro <hbies...@cloudera.com> AuthorDate: Fri May 24 19:02:58 2024 +0200 JEXL: getting ready for 3.4; - added tests to (re)improve coverage; - removed useless code; - updated doc, changes, release notes --- RELEASE-NOTES.txt | 2 + src/changes/changes.xml | 6 + .../org/apache/commons/jexl3/JexlArithmetic.java | 4 +- .../commons/jexl3/internal/IntegerRange.java | 10 - .../apache/commons/jexl3/internal/Interpreter.java | 4 +- .../apache/commons/jexl3/internal/LongRange.java | 10 - .../commons/jexl3/internal/TemplateEngine.java | 20 -- src/site/xdoc/reference/syntax.xml | 36 +++- .../org/apache/commons/jexl3/ArithmeticTest.java | 226 ++++++++++++++++++++- .../apache/commons/jexl3/internal/RangeTest.java | 67 ++++++ .../jexl3/scripting/JexlScriptEngineTest.java | 17 +- 11 files changed, 342 insertions(+), 60 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 2492472d..e075f82c 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -38,6 +38,8 @@ more permissive structured literals. See the JIRA tickets for more details. New Features in 3.4: ==================== +* JEXL-423: Add support for instanceof / !instanceof +* JEXL-422 Add strict equality (===) and inequality (!==) operators * JEXL-421: ArrayBuilder: array type should reflect common class of its entries * JEXL-419: Add permission syntax to allow class/method/field * JEXL-418: Add try-catch-finally support diff --git a/src/changes/changes.xml b/src/changes/changes.xml index b6d65c71..234f41e9 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -29,6 +29,12 @@ <body> <release version="3.4" date="YYYY-MM-DD"> <!-- ADD --> + <action dev="henrib" type="add" issue="JEXL-423"> + Add support for instanceof / !instanceof + </action> + <action dev="henrib" type="add" issue="JEXL-422"> + Add strict equality (===) and inequality (!==) operators + </action> <action dev="henrib" type="add" issue="JEXL-421"> ArrayBuilder: array type should reflect common class of its entries </action> diff --git a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java index cd499a19..5d5c72b1 100644 --- a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java +++ b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java @@ -2086,8 +2086,8 @@ public class JexlArithmetic { if (left == null || right == null) { return false; } - if (left.getClass().equals(right.getClass()) && left instanceof Comparable<?>) { - return ((Comparable) left).compareTo((Comparable) right) == 0; + if (left.getClass().equals(right.getClass())) { + return left.equals(right); } return false; } diff --git a/src/main/java/org/apache/commons/jexl3/internal/IntegerRange.java b/src/main/java/org/apache/commons/jexl3/internal/IntegerRange.java index 9bec4b16..ce8cff42 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/IntegerRange.java +++ b/src/main/java/org/apache/commons/jexl3/internal/IntegerRange.java @@ -261,11 +261,6 @@ final class AscIntegerIterator implements Iterator<Integer> { } throw new NoSuchElementException(); } - - @Override - public void remove() { - throw new UnsupportedOperationException("Not supported."); - } } /** @@ -301,9 +296,4 @@ final class DescIntegerIterator implements Iterator<Integer> { } throw new NoSuchElementException(); } - - @Override - public void remove() { - throw new UnsupportedOperationException("Not supported."); - } } diff --git a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java index 4cbfa331..7621a848 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java +++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java @@ -299,7 +299,7 @@ public class Interpreter extends InterpreterBase { final Object result = operators.tryOverload(node, JexlOperator.SHIFTRIGHTU, left, right); return result != JexlEngine.TRY_FAILED ? result : arithmetic.shiftRightUnsigned(left, right); } catch (final ArithmeticException xrt) { - throw new JexlException(findNullOperand(node, left, right), ">> error", xrt); + throw new JexlException(findNullOperand(node, left, right), ">>> error", xrt); } } @@ -519,7 +519,7 @@ public class Interpreter extends InterpreterBase { } return number; } catch (final ArithmeticException xrt) { - throw new JexlException(valNode, "- error", xrt); + throw new JexlException(valNode, "+ error", xrt); } } diff --git a/src/main/java/org/apache/commons/jexl3/internal/LongRange.java b/src/main/java/org/apache/commons/jexl3/internal/LongRange.java index d504c7ca..0233e4fc 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/LongRange.java +++ b/src/main/java/org/apache/commons/jexl3/internal/LongRange.java @@ -265,11 +265,6 @@ final class AscLongIterator implements Iterator<Long> { } throw new NoSuchElementException(); } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } } /** @@ -306,9 +301,4 @@ final class DescLongIterator implements Iterator<Long> { } throw new NoSuchElementException(); } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } } diff --git a/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java b/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java index 5d74b16d..5441cde0 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java +++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java @@ -1036,21 +1036,6 @@ public final class TemplateEngine extends JxltEngine { return body; } - @Override - public String toString() { - if (BlockType.VERBATIM.equals(type)) { - return body; - } - // CHECKSTYLE:OFF - final StringBuilder strb = new StringBuilder(64); // CSOFF: MagicNumber - // CHECKSTYLE:ON - final Iterator<CharSequence> lines = readLines(new StringReader(body)); - while (lines.hasNext()) { - strb.append("$$").append(lines.next()); - } - return strb.toString(); - } - /** * Appends this block string representation to a builder. * @param strb the string builder to append to @@ -1137,11 +1122,6 @@ public final class TemplateEngine extends JxltEngine { } return current; } - - @Override - public void remove() { - throw new UnsupportedOperationException("Not supported."); - } }; } diff --git a/src/site/xdoc/reference/syntax.xml b/src/site/xdoc/reference/syntax.xml index e7b6bde3..92c2839b 100644 --- a/src/site/xdoc/reference/syntax.xml +++ b/src/site/xdoc/reference/syntax.xml @@ -707,10 +707,9 @@ <tr> <td>Equality</td> <td> - The usual <code>==</code> operator can be used as well as the abbreviation <code>eq</code>. - For example - <code>val1 == val2</code> and - <code>val1 eq val2</code> are equivalent. + The equality <code>==</code> operator checks whether its two operands are equal, returning a + Boolean result. Unlike the strict equality operator, it attempts to convert and compare + operands that are of different types. <ol> <li> <code>null</code> is only ever equal to null, that is if you compare null @@ -723,10 +722,24 @@ <tr> <td>Inequality</td> <td> - The usual <code>!=</code> operator can be used as well as the abbreviation <code>ne</code>. - For example - <code>val1 != val2</code> and - <code>val1 ne val2</code> are equivalent. + The inequality <code>!=</code> operator checks whether its two operands are not equal, + returning a Boolean result. Unlike the strict inequality operator, it attempts to convert and + compare operands that are of different types. + </td> + </tr> + <tr> + <td>Strict-Equality</td> + <td>The strict equality <code>===</code> operator checks whether its two operands are equal, + returning a Boolean result. Unlike the equality operator, the strict equality operator always + considers operands of different types to be different. + </td> + </tr> + <tr> + <td>Strict Inequality</td> + <td> + The strict inequality <code>!==</code> operator checks whether its two operands are not equal, + returning a Boolean result. Unlike the inequality operator, the strict inequality operator + always considers operands of different types to be different </td> </tr> <tr> @@ -918,6 +931,13 @@ For example <code>size [1,2,3]</code> and <code>size([1,2,3])</code> are equivalent </td> </tr> + <tr> + <td>instanceof/!instanceof</td> + <td>The instanceof operator allows to check whether an object belongs to a certain class. + It is using Class.isInstance to perform the check. As a convenience, {{ !instanceof }} is + supported as well. + </td> + </tr> </table> </section> <section name="Access"> diff --git a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java index 473cd2a8..9a47da09 100644 --- a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java +++ b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java @@ -27,6 +27,7 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import javax.xml.parsers.DocumentBuilder; @@ -246,6 +247,196 @@ public class ArithmeticTest extends JexlTestCase { return new Var(lhs.value ^ rhs.value); } } + + // an arithmetic that fail systematically with vars + public static class ArithmeticFail extends JexlArithmetic { + public ArithmeticFail(final boolean strict) { + super(strict); + } + + @Override + public Object add(final Object lhs, final Object rhs) { + throw new ArithmeticException(lhs + " o " + rhs); + } + + @Override + public Object and(final Object lhs, final Object rhs) { + throw new ArithmeticException(lhs + " o " + rhs); + } + + @Override + public Object complement(final Object arg) { + throw new ArithmeticException(Objects.toString(arg)); + } + + @Override + public Object decrement(final Object arg) { + throw new ArithmeticException(Objects.toString(arg)); + } + + @Override + public Object increment(final Object arg) { + throw new ArithmeticException(Objects.toString(arg)); + } + + @Override + public Boolean contains(final Object lhs, final Object rhs) { + throw new ArithmeticException(lhs + " o " + rhs); + } + + @Override + public Object divide(final Object lhs, final Object rhs) { + throw new ArithmeticException(lhs + " o " + rhs); + } + + @Override + public Boolean endsWith(final Object lhs, final Object rhs) { + throw new ArithmeticException(lhs + " o " + rhs); + } + + @Override + public boolean equals(final Object lhs, final Object rhs) { + throw new ArithmeticException(lhs + " o " + rhs); + } + + @Override + public boolean strictEquals(final Object lhs, final Object rhs) { + throw new ArithmeticException(lhs + " o " + rhs); + } + + @Override + public boolean greaterThan(final Object lhs, final Object rhs) { + throw new ArithmeticException(lhs + " o " + rhs); + } + + @Override + public boolean greaterThanOrEqual(final Object lhs, final Object rhs) { + throw new ArithmeticException(lhs + " o " + rhs); + } + + @Override + public boolean lessThan(final Object lhs, final Object rhs) { + throw new ArithmeticException(lhs + " o " + rhs); + } + + @Override + public boolean lessThanOrEqual(final Object lhs, final Object rhs) { + throw new ArithmeticException(lhs + " o " + rhs); + } + + @Override + public Object mod(final Object lhs, final Object rhs) { + throw new ArithmeticException(lhs + " o " + rhs); + } + + @Override + public Object multiply(final Object lhs, final Object rhs) { + throw new ArithmeticException(lhs + " o " + rhs); + } + + @Override + public Object negate(final Object arg) { + throw new ArithmeticException(Objects.toString(arg)); + } + + @Override + public Object not(final Object arg) { + throw new ArithmeticException(Objects.toString(arg)); + } + + @Override + public Object or(final Object lhs, final Object rhs) { + throw new ArithmeticException(lhs + " o " + rhs); + } + + @Override + public Object shiftLeft(final Object lhs, final Object rhs) { + throw new ArithmeticException(lhs + " o " + rhs); + } + + @Override + public Object shiftRight(final Object lhs, final Object rhs) { + throw new ArithmeticException(lhs + " o " + rhs); + } + + @Override + public Object shiftRightUnsigned(final Object lhs, final Object rhs) { + throw new ArithmeticException(lhs + " o " + rhs); + } + + @Override + public Boolean startsWith(final Object lhs, final Object rhs) { + throw new ArithmeticException(lhs + " o " + rhs); + } + + @Override + public Object subtract(final Object lhs, final Object rhs) { + throw new ArithmeticException(lhs + " o " + rhs); + } + + @Override + public Object xor(final Object lhs, final Object rhs) { + throw new ArithmeticException(lhs + " o " + rhs); + } + + @Override + public boolean toBoolean(Object x) { + throw new ArithmeticException(Objects.toString(x)); + } + } + + @Test + public void testFailAllOperators() { + String[] scripts = new String[]{ + "(x, y)->{ x < y }", + "(x, y)->{ x <= y }", + "(x, y)->{ x > y }", + "(x, y)->{ x >= y }", + "(x, y)->{ x == y }", + "(x, y)->{ x != y }", + "(x, y)->{ x === y }", + "(x, y)->{ x !== y }", + "(x, y)->{ x % y }", + "(x, y)->{ x * y }", + "(x, y)->{ x + y }", + "(x, y)->{ x - y }", + "(x, y)->{ x ^ y }", + "(x, y)->bitwiseXor(x,y)", + "(x, y)->{ x || y }", + "(x, y)->{ x | y }", + "(x, y)->bitwiseOr(x,y)", + "(x, y)->{ x << y }", + "(x, y)->{ x >> y }", + "(x, y)->{ x >>> y }", + "(x, y)->{ x & y }", + "(x, y)->{ x && y }", + "(x, y)->bitwiseAnd(x,y)", + "(x, y)->{ x =^ y }", + "(x, y)->{ x !^ y }", + "(x, y)->{ x =$ y }", + "(x, y)->{ x !$ y }", + "(x, y)->{ x =~ y }", + "(x, y)->{ x !~ y }", + "(x, ignore)->{ -x }", + "(x, ignore)->{ +x }", + "(x, ignore)->{ --x }", + "(x, ignore)->{ ++x }", + "(x, ignore)->{ x-- }", + "(x, ignore)->{ x++ }" + }; + final JexlEngine jexl = new JexlBuilder().cache(64).arithmetic(new ArithmeticFail(true)).create(); + final JexlContext jc = new EmptyTestContext(); + for(String src : scripts) { + JexlScript script = jexl.createScript(src); + try { + Object result = script.execute(jc, new Var(42), new Var(43)); + Assert.fail(src); + } catch(JexlException xjexl) { + Assert.assertNotNull(jexl); + } + } + } + public static class Callable173 { public Object call(final Integer... arg) { return arg[0] * arg[1]; @@ -1934,22 +2125,43 @@ public class ArithmeticTest extends JexlTestCase { asserter.failExpression("objects[1].status", ".*variable 'objects' is undefined.*"); } + public static class InstanceofContext extends MapContext implements JexlContext.ClassNameResolver { + @Override + public String resolveClassName(String name) { + if ("Double".equals(name)) { + return Double.class.getName(); + } + return null; + } + } + + @Test + public void testInstanceOf0() throws Exception { + final JexlEngine jexl = new JexlBuilder().strict(true).safe(false).create(); + final JexlContext ctxt = new InstanceofContext(); + runInstanceof(jexl, ctxt); + } + @Test - public void testInstanceOf() throws Exception { + public void testInstanceOf1() throws Exception { final JexlEngine jexl = new JexlBuilder().strict(true).safe(false).imports("java.lang").create(); - Object r = jexl.createExpression("3.0 instanceof 'Double'").evaluate(null); + runInstanceof(jexl, null); + } + + private void runInstanceof(JexlEngine jexl, JexlContext ctxt) { + Object r = jexl.createExpression("3.0 instanceof 'Double'").evaluate(ctxt); Assert.assertTrue((Boolean) r); - r = jexl.createExpression("'3.0' !instanceof 'Double'").evaluate(null); + r = jexl.createExpression("'3.0' !instanceof 'Double'").evaluate(ctxt); Assert.assertTrue((Boolean) r); JexlScript script = jexl.createScript("x instanceof y", "x", "y"); - r = script.execute(null, "foo", String.class); + r = script.execute(ctxt, "foo", String.class); Assert.assertTrue((Boolean) r); - r = script.execute(null, 42.0, Double.class); + r = script.execute(ctxt, 42.0, Double.class); Assert.assertTrue((Boolean) r); script = jexl.createScript("x !instanceof y", "x", "y"); - r = script.execute(null, "foo", Double.class); + r = script.execute(ctxt, "foo", Double.class); Assert.assertTrue((Boolean) r); - r = script.execute(null, 42.0, String.class); + r = script.execute(ctxt, 42.0, String.class); Assert.assertTrue((Boolean) r); } diff --git a/src/test/java/org/apache/commons/jexl3/internal/RangeTest.java b/src/test/java/org/apache/commons/jexl3/internal/RangeTest.java index e9f9069e..5eca5d61 100644 --- a/src/test/java/org/apache/commons/jexl3/internal/RangeTest.java +++ b/src/test/java/org/apache/commons/jexl3/internal/RangeTest.java @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.NoSuchElementException; +import org.apache.commons.jexl3.JexlFeatures; import org.apache.commons.jexl3.JexlTestCase; import org.junit.After; import org.junit.Assert; @@ -169,7 +170,73 @@ public class RangeTest extends JexlTestCase { } catch (final NoSuchElementException xns) { // ok } + } + + @Test + public void testAscIterator() { + Iterator<Integer> ii = new AscIntegerIterator(3, 5); + Integer i = 3; + while(ii.hasNext()) { + Assert.assertEquals(i, ii.next()); + i += 1; + } + try { + ii.next(); + Assert.fail("iterator exhausted"); + } catch(NoSuchElementException e) { + Assert.assertNotNull(e); + } + try { + ii.remove(); + Assert.fail("remove not implemented"); + } catch(UnsupportedOperationException e) { + Assert.assertNotNull(e); + } + } + + @Test + public void testAscLongIterator() { + Iterator<Long> ii = new AscLongIterator(3L, 5L); + Long i = 3L; + while(ii.hasNext()) { + Assert.assertEquals(i, ii.next()); + i += 1; + } + try { + ii.next(); + Assert.fail("iterator exhausted"); + } catch(NoSuchElementException e) { + Assert.assertNotNull(e); + } + try { + ii.remove(); + Assert.fail("remove not implemented"); + } catch(UnsupportedOperationException e) { + Assert.assertNotNull(e); + } + } + + + @Test + public void testSource() { + JexlFeatures features = JexlFeatures.createDefault(); + Source src0 = new Source(features, "x -> -x"); + Source src0b = new Source(features, "x -> -x"); + Source src1 = new Source(features, "x -> +x"); + Assert.assertEquals(7, src0.length()); + Assert.assertEquals(src0, src0); + Assert.assertEquals(src0, src0b); + Assert.assertNotEquals(src0, src1); + Assert.assertEquals(src0.hashCode(), src0b.hashCode()); + Assert.assertNotEquals(src0.hashCode(), src1.hashCode()); + Assert.assertTrue(src0.compareTo(src0b) == 0); + Assert.assertTrue(src0.compareTo(src1) > 0); + Assert.assertTrue(src1.compareTo(src0) < 0); + } + @Test public void testMisc() { + Assert.assertEquals("?", Scope.UNDEFINED.toString()); + Assert.assertEquals("??", Scope.UNDECLARED.toString()); } } diff --git a/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java b/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java index 65357afa..1293fc1b 100644 --- a/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java +++ b/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java @@ -18,6 +18,7 @@ package org.apache.commons.jexl3.scripting; import java.io.Reader; +import java.io.StringReader; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -71,7 +72,7 @@ public class JexlScriptEngineTest { final JexlScriptEngine engine = (JexlScriptEngine) manager.getEngineByName("JEXL"); final ScriptContext ctxt = engine.getContext(); final String str = null; - final String reader = null; + final Reader reader = null; try { final CompiledScript script0 = engine.compile(str); Assert.fail("should have thrown npe"); @@ -84,6 +85,20 @@ public class JexlScriptEngineTest { } catch (final NullPointerException npe) { Assert.assertNotNull(npe); } + try { + final CompiledScript script0 = engine.compile(new StringReader("3 + 4")); + Assert.assertEquals(engine, script0.getEngine()); + Object result = script0.eval(); + Assert.assertEquals(7, result); + result = script0.eval(); + Assert.assertEquals(7, result); + result = engine.eval(new StringReader("38 + 4")); + Assert.assertEquals(42, result); + result = engine.eval("38 + 4"); + Assert.assertEquals(42, result); + } catch (final ScriptException xscript) { + Assert.assertTrue(xscript.getCause() instanceof NullPointerException); + } try { final CompiledScript script0 = engine.compile("3 + 4"); Assert.assertEquals(engine, script0.getEngine());