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 2018/09/17 20:09:54 UTC
[commons-jexl] branch master updated: JEXL-275: added option,
tests and changes
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 650575a JEXL-275: added option, tests and changes
650575a is described below
commit 650575a978fec23872cc01472ac0757123d16905
Author: henrib <he...@apache.org>
AuthorDate: Mon Sep 17 22:09:15 2018 +0200
JEXL-275: added option, tests and changes
---
RELEASE-NOTES.txt | 1 +
.../java/org/apache/commons/jexl3/JexlBuilder.java | 22 +++
.../java/org/apache/commons/jexl3/JexlEngine.java | 2 +-
.../org/apache/commons/jexl3/internal/Engine.java | 5 +
.../apache/commons/jexl3/internal/Interpreter.java | 11 +-
.../commons/jexl3/internal/InterpreterBase.java | 8 +
.../org/apache/commons/jexl3/parser/JexlNode.java | 10 +-
src/site/xdoc/changes.xml | 3 +
.../org/apache/commons/jexl3/Issues200Test.java | 173 +-------------------
.../apache/commons/jexl3/PropertyAccessTest.java | 181 +++++++++++++++++++++
10 files changed, 236 insertions(+), 180 deletions(-)
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 4d049d8..d791ca1 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -39,6 +39,7 @@ What's new in 3.2:
New Features in 3.2:
====================
+* JEXL-275: Allow safe navigation as option
* JEXL-273: Add do...while(...) loops
* JEXL-264: Allow space, quote & double-quote in identifiers
* JEXL-260: Automatically inject JexlContext in constructor call when possible
diff --git a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
index 7332f57..928069e 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
@@ -82,6 +82,9 @@ public class JexlBuilder {
/** Whether this engine is in lenient or strict mode; if unspecified, use the arithmetic lenient property. */
private Boolean strict = null;
+ /** Whether this engine is in tolerant mode. */
+ private Boolean safe = false;
+
/** Whether error messages will carry debugging information. */
private Boolean debug = null;
@@ -286,6 +289,25 @@ public class JexlBuilder {
}
/**
+ * Sets whether the engine considers dereferencing null in navigation expressions
+ * as errors or evaluates them as null.
+ * <p><code>x.y()</code> if x is null throws an exception when not safe,
+ * return null and warns if it is.<p>
+ *
+ * @param flag true means safe navigation, false throws exception when dereferencing null
+ * @return this builder
+ */
+ public JexlBuilder safe(boolean flag) {
+ this.safe = flag;
+ return this;
+ }
+
+ /** @return true if safe, false otherwise */
+ public Boolean safe() {
+ return this.safe;
+ }
+
+ /**
* Sets whether the engine will report debugging information when error occurs.
*
* @param flag true implies debug is on, false implies debug is off.
diff --git a/src/main/java/org/apache/commons/jexl3/JexlEngine.java b/src/main/java/org/apache/commons/jexl3/JexlEngine.java
index deafdf8..06202f0 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlEngine.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlEngine.java
@@ -130,7 +130,7 @@ public abstract class JexlEngine {
* @return true if strict, false otherwise
*/
Boolean isStrict();
-
+
/**
* Checks whether the arithmetic triggers errors during evaluation when null is used as an operand.
*
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Engine.java b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
index e3de73d..bc9735a 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
@@ -94,6 +94,10 @@ public class Engine extends JexlEngine {
*/
protected final boolean strict;
/**
+ * Whether this engine considers null in navigation expression as errors.
+ */
+ protected final boolean safe;
+ /**
* Whether expressions evaluated by this engine will throw exceptions (false) or return null (true) on errors.
* Default is false.
*/
@@ -156,6 +160,7 @@ public class Engine extends JexlEngine {
public Engine(JexlBuilder conf) {
// options:
this.strict = conf.strict() == null ? true : conf.strict();
+ this.safe = conf.safe() == null ? false : conf.safe();
this.silent = conf.silent() == null ? false : conf.silent();
this.cancellable = conf.cancellable() == null ? !silent && strict : conf.cancellable();
this.debug = conf.debug() == null ? true : conf.debug();
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 39094c0..4837bc7 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -114,6 +114,7 @@ import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.concurrent.Callable;
+import org.apache.commons.jexl3.JxltEngine;
/**
@@ -1125,13 +1126,13 @@ public class Interpreter extends InterpreterBase {
if (antish && ant != null) {
boolean undefined = !(context.has(ant.toString()) || isLocalVariable(node, 0));
// variable unknown in context and not a local
- return node.isSafeLhs()
+ return node.isSafeLhs(jexl.safe)
? null
: unsolvableVariable(node, ant.toString(), undefined);
}
if (ptyNode != null) {
// am I the left-hand side of a safe op ?
- return ptyNode.isSafeLhs()
+ return ptyNode.isSafeLhs(jexl.safe)
? null
: unsolvableProperty(node, stringifyProperty(ptyNode), false, null);
}
@@ -1447,7 +1448,7 @@ public class Interpreter extends InterpreterBase {
} else if (context.has(methodName)) {
functor = context.get(methodName);
isavar = functor != null;
- }
+ }
// name is a variable, cant be cached
cacheable &= !isavar;
}
@@ -1465,7 +1466,7 @@ public class Interpreter extends InterpreterBase {
} else {
return unsolvableMethod(node, "?");
}
-
+
// solving the call site
CallDispatcher call = new CallDispatcher(node, cacheable);
try {
@@ -1473,7 +1474,7 @@ public class Interpreter extends InterpreterBase {
Object eval = call.tryEval(target, methodName, argv);
if (JexlEngine.TRY_FAILED != eval) {
return eval;
- }
+ }
boolean functorp = false;
boolean narrow = false;
// pseudo loop to try acquiring methods without and with argument narrowing
diff --git a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
index 6658594..e8a0287 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
@@ -25,6 +25,7 @@ import org.apache.commons.jexl3.JexlOperator;
import org.apache.commons.jexl3.introspection.JexlMethod;
import org.apache.commons.jexl3.introspection.JexlUberspect;
import org.apache.commons.jexl3.parser.ASTArrayAccess;
+import org.apache.commons.jexl3.parser.ASTFunctionNode;
import org.apache.commons.jexl3.parser.ASTMethodNode;
import org.apache.commons.jexl3.parser.JexlNode;
import org.apache.commons.jexl3.parser.ParserVisitor;
@@ -250,6 +251,13 @@ public abstract class InterpreterBase extends ParserVisitor {
}
return ".???(...)";
}
+ if (node instanceof ASTFunctionNode) {
+ if (node.jjtGetChild(0) != null) {
+ return node.jjtGetChild(0).toString()
+ + "(...)";
+ }
+ return "???(...)";
+ }
return node.toString();
}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java b/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
index cd4706d..2a91f5e 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
@@ -195,11 +195,12 @@ public abstract class JexlNode extends SimpleNode {
/**
* Whether this node is the left-hand side of a safe access identifier as in.
* For instance, in 'x?.y' , 'x' is safe.
+ * @param safe whether the engine is in safe-navigation mode
* @return true if safe lhs, false otherwise
*/
- public boolean isSafeLhs() {
+ public boolean isSafeLhs(boolean safe) {
if (this instanceof ASTReference) {
- return jjtGetChild(0).isSafeLhs();
+ return jjtGetChild(0).isSafeLhs(safe);
}
JexlNode parent = this.jjtGetParent();
if (parent == null) {
@@ -219,10 +220,11 @@ public abstract class JexlNode extends SimpleNode {
// seek next child in parent
if (rhs >= 0 && rhs < nsiblings) {
JexlNode rsibling = parent.jjtGetChild(rhs);
- if (rsibling instanceof ASTMethodNode) {
+ if (rsibling instanceof ASTMethodNode || rsibling instanceof ASTFunctionNode) {
rsibling = rsibling.jjtGetChild(0);
}
- if (rsibling instanceof ASTIdentifierAccess && ((ASTIdentifierAccess) rsibling).isSafe()) {
+ if (rsibling instanceof ASTIdentifierAccess
+ && (((ASTIdentifierAccess) rsibling).isSafe() || safe)) {
return true;
}
}
diff --git a/src/site/xdoc/changes.xml b/src/site/xdoc/changes.xml
index 61baaf0..96c1ab3 100644
--- a/src/site/xdoc/changes.xml
+++ b/src/site/xdoc/changes.xml
@@ -26,6 +26,9 @@
</properties>
<body>
<release version="3.2" date="unreleased">
+ <action dev="henrib" type="add" issue="JEXL-275">
+ Allow safe navigation as option
+ </action>
<action dev="Dmitri Blinov" type="add" issue="JEXL-175" due-to="Dmitri Blinov">
Add do...while(...) loops
</action>
diff --git a/src/test/java/org/apache/commons/jexl3/Issues200Test.java b/src/test/java/org/apache/commons/jexl3/Issues200Test.java
index ff44930..a4cfe4f 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues200Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues200Test.java
@@ -320,7 +320,7 @@ public class Issues200Test extends JexlTestCase {
Foo245 foo245 = new Foo245();
ctx.set("foo", foo245);
- JexlEngine engine = new JexlBuilder().strict(true).silent(false).create();
+ JexlEngine engine = new JexlBuilder().strict(true).safe(false).silent(false).create();
JexlExpression foobar = engine.createExpression("foo.bar");
JexlExpression foobaz = engine.createExpression("foo.baz");
JexlExpression foobarbaz = engine.createExpression("foo.bar.baz");
@@ -333,14 +333,14 @@ public class Issues200Test extends JexlTestCase {
// fail level 1
try {
foobaz.evaluate(ctx);
- Assert.fail("foo.baz is not solvable");
+ Assert.fail("foo.baz is not solvable, exception expected");
} catch(JexlException xp) {
Assert.assertTrue(xp instanceof JexlException.Property);
}
// fail level 2
try {
foobarbaz.evaluate(ctx);
- Assert.fail("foo.bar.baz is not solvable");
+ Assert.fail("foo.bar.baz is not solvable, exception expected");
} catch(JexlException xp) {
Assert.assertTrue(xp instanceof JexlException.Property);
}
@@ -348,91 +348,6 @@ public class Issues200Test extends JexlTestCase {
}
@Test
- public void test250() throws Exception {
- MapContext ctx = new MapContext();
- HashMap<Object, Object> x = new HashMap<Object, Object>();
- x.put(2, "123456789");
- ctx.set("x", x);
- JexlEngine engine = new JexlBuilder().strict(true).silent(false).create();
- String stmt = "x.2.class.name";
- JexlScript script = engine.createScript(stmt);
- Object result = script.execute(ctx);
- Assert.assertEquals("java.lang.String", result);
-
- try {
- stmt = "x.3?.class.name";
- script = engine.createScript(stmt);
- result = script.execute(ctx);
- Assert.assertNull(result);
- } catch (JexlException xany) {
- Assert.fail("Should have evaluated to null");
- }
- try {
- stmt = "x?.3.class.name";
- script = engine.createScript(stmt);
- result = script.execute(ctx);
- Assert.fail("Should have thrown, fail on 3");
- Assert.assertNull(result);
- } catch (JexlException xany) {
- Assert.assertTrue(xany.detailedMessage().contains("3"));
- }
- try {
- stmt = "x?.3?.class.name";
- script = engine.createScript(stmt);
- result = script.execute(ctx);
- Assert.assertNull(result);
- } catch (JexlException xany) {
- Assert.fail("Should have evaluated to null");
- }
- try {
- stmt = "y?.3.class.name";
- script = engine.createScript(stmt);
- result = script.execute(ctx);
- Assert.assertNull(result);
- } catch (JexlException xany) {
- Assert.fail("Should have evaluated to null");
- }
- try {
- stmt = "x?.y?.z";
- script = engine.createScript(stmt);
- result = script.execute(ctx);
- Assert.assertNull(result);
- } catch (JexlException xany) {
- Assert.fail("Should have evaluated to null");
- }
- try {
- stmt = "x? (x.y? (x.y.z ?: null) :null) : null";
- script = engine.createScript(stmt);
- result = script.execute(ctx);
- Assert.assertNull(result);
- } catch (JexlException xany) {
- Assert.fail("Should have evaluated to null");
- }
- }
-
- @Test
- public void test252() throws Exception {
- MapContext ctx = new MapContext();
- JexlEngine engine = new JexlBuilder().strict(true).silent(false).create();
- String stmt = "(x, dflt)->{ x?.class1 ?? dflt }";
- JexlScript script = engine.createScript(stmt);
- Object result = script.execute(ctx, "querty", "default");
- Assert.assertEquals("default", result);
- try {
- stmt = "(x, al, dflt)->{ x.`c${al}ss` ?? dflt }";
- script = engine.createScript(stmt);
- result = script.execute(ctx, "querty", "la", "default");
- Assert.assertEquals(stmt.getClass(), result);
- stmt = "(x, al, dflt)->{ x?.`c${al}ss` ?? dflt }";
- script = engine.createScript(stmt);
- result = script.execute(ctx, "querty", "la", "default");
- Assert.assertEquals(stmt.getClass(), result);
- } catch(JexlException xany) {
- String xanystr = xany.toString();
- }
- }
-
- @Test
public void test256() throws Exception {
MapContext ctx = new MapContext() {
@Override public void set(String name, Object value) {
@@ -653,86 +568,4 @@ public class Issues200Test extends JexlTestCase {
Assert.assertTrue(result instanceof JexlScript);
}
- public static class Prompt {
- private final Map<String, PromptValue> values = new HashMap<String, PromptValue>();
-
- public Object get(String name) {
- PromptValue v = values.get(name);
- return v != null? v.getValue() : null;
- }
-
- public void set(String name, Object value) {
- values.put(name, new PromptValue(value));
- }
- }
-
- /**
- * A valued prompt.
- */
- public static class PromptValue {
-
- /** Prompt value. */
- private Object value;
-
- public PromptValue(Object v) {
- value = v;
- }
-
- public Object getValue() {
- return value;
- }
-
- public void setValue(Object value) {
- this.value = value;
- }
- }
-
- @Test
- public void test272() throws Exception {
- JexlEngine jexl = new JexlBuilder().strict(true).create();
- JexlContext ctxt = new MapContext();
- JexlScript script;
- Object result = null;
- Prompt p0 = new Prompt();
- p0.set("stuff", 42);
- ctxt.set("$in", p0);
-
- // unprotected navigation
- script = jexl.createScript("$in[p].intValue()", "p");
- try {
- result = script.execute(ctxt, "fail");
- Assert.fail("should have thrown a " + JexlException.Property.class);
- } catch (JexlException xany) {
- Assert.assertEquals(JexlException.Property.class, xany.getClass());
- }
- Assert.assertEquals(null, result);
- result = script.execute(ctxt, "stuff");
- Assert.assertEquals(42, result);
-
- // protected navigation
- script = jexl.createScript("$in[p]?.intValue()", "p");
- result = script.execute(ctxt, "fail");
- Assert.assertEquals(null, result);
- result = script.execute(ctxt, "stuff");
- Assert.assertEquals(42, result);
-
- // unprotected navigation
- script = jexl.createScript("$in.`${p}`.intValue()", "p");
- try {
- result = script.execute(ctxt, "fail");
- Assert.fail("should have thrown a " + JexlException.Property.class);
- } catch (JexlException xany) {
- Assert.assertEquals(JexlException.Property.class, xany.getClass());
- }
- result = script.execute(ctxt, "stuff");
- Assert.assertEquals(42, result);
-
- // protected navigation
- script = jexl.createScript("$in.`${p}`?.intValue()", "p");
- result = script.execute(ctxt, "fail");
- Assert.assertEquals(null, result);
- result = script.execute(ctxt, "stuff");
- Assert.assertEquals(42, result);
-
- }
}
diff --git a/src/test/java/org/apache/commons/jexl3/PropertyAccessTest.java b/src/test/java/org/apache/commons/jexl3/PropertyAccessTest.java
index ca76014..4af7a5a 100644
--- a/src/test/java/org/apache/commons/jexl3/PropertyAccessTest.java
+++ b/src/test/java/org/apache/commons/jexl3/PropertyAccessTest.java
@@ -387,4 +387,185 @@ public class PropertyAccessTest extends JexlTestCase {
Assert.assertTrue(xjexl.getMessage().contains("c${la--ss"));
}
}
+
+ @Test
+ public void test250() throws Exception {
+ MapContext ctx = new MapContext();
+ HashMap<Object, Object> x = new HashMap<Object, Object>();
+ x.put(2, "123456789");
+ ctx.set("x", x);
+ JexlEngine engine = new JexlBuilder().strict(true).silent(false).create();
+ String stmt = "x.2.class.name";
+ JexlScript script = engine.createScript(stmt);
+ Object result = script.execute(ctx);
+ Assert.assertEquals("java.lang.String", result);
+
+ try {
+ stmt = "x.3?.class.name";
+ script = engine.createScript(stmt);
+ result = script.execute(ctx);
+ Assert.assertNull(result);
+ } catch (JexlException xany) {
+ Assert.fail("Should have evaluated to null");
+ }
+ try {
+ stmt = "x?.3.class.name";
+ script = engine.createScript(stmt);
+ result = script.execute(ctx);
+ Assert.fail("Should have thrown, fail on 3");
+ Assert.assertNull(result);
+ } catch (JexlException xany) {
+ Assert.assertTrue(xany.detailedMessage().contains("3"));
+ }
+ try {
+ stmt = "x?.3?.class.name";
+ script = engine.createScript(stmt);
+ result = script.execute(ctx);
+ Assert.assertNull(result);
+ } catch (JexlException xany) {
+ Assert.fail("Should have evaluated to null");
+ }
+ try {
+ stmt = "y?.3.class.name";
+ script = engine.createScript(stmt);
+ result = script.execute(ctx);
+ Assert.assertNull(result);
+ } catch (JexlException xany) {
+ Assert.fail("Should have evaluated to null");
+ }
+ try {
+ stmt = "x?.y?.z";
+ script = engine.createScript(stmt);
+ result = script.execute(ctx);
+ Assert.assertNull(result);
+ } catch (JexlException xany) {
+ Assert.fail("Should have evaluated to null");
+ }
+ try {
+ stmt = "x? (x.y? (x.y.z ?: null) :null) : null";
+ script = engine.createScript(stmt);
+ result = script.execute(ctx);
+ Assert.assertNull(result);
+ } catch (JexlException xany) {
+ Assert.fail("Should have evaluated to null");
+ }
+ }
+
+ public static class Prompt {
+ private final Map<String, PromptValue> values = new HashMap<String, PromptValue>();
+
+ public Object get(String name) {
+ PromptValue v = values.get(name);
+ return v != null? v.getValue() : null;
+ }
+
+ public void set(String name, Object value) {
+ values.put(name, new PromptValue(value));
+ }
+ }
+
+ /**
+ * A valued prompt.
+ */
+ public static class PromptValue {
+
+ /** Prompt value. */
+ private Object value;
+
+ public PromptValue(Object v) {
+ value = v;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ public void setValue(Object value) {
+ this.value = value;
+ }
+ }
+
+ @Test
+ public void test275a() throws Exception {
+ JexlEngine jexl = new JexlBuilder().strict(true).safe(false).create();
+ JexlContext ctxt = new MapContext();
+ JexlScript script;
+ Object result = null;
+ Prompt p0 = new Prompt();
+ p0.set("stuff", 42);
+ ctxt.set("$in", p0);
+
+ // unprotected navigation
+ script = jexl.createScript("$in[p].intValue()", "p");
+ try {
+ result = script.execute(ctxt, "fail");
+ Assert.fail("should have thrown a " + JexlException.Property.class);
+ } catch (JexlException xany) {
+ Assert.assertEquals(JexlException.Property.class, xany.getClass());
+ }
+ Assert.assertEquals(null, result);
+ result = script.execute(ctxt, "stuff");
+ Assert.assertEquals(42, result);
+
+ // protected navigation
+ script = jexl.createScript("$in[p]?.intValue()", "p");
+ result = script.execute(ctxt, "fail");
+ Assert.assertEquals(null, result);
+ result = script.execute(ctxt, "stuff");
+ Assert.assertEquals(42, result);
+
+ // unprotected navigation
+ script = jexl.createScript("$in.`${p}`.intValue()", "p");
+ try {
+ result = script.execute(ctxt, "fail");
+ Assert.fail("should have thrown a " + JexlException.Property.class);
+ } catch (JexlException xany) {
+ Assert.assertEquals(JexlException.Property.class, xany.getClass());
+ }
+ result = script.execute(ctxt, "stuff");
+ Assert.assertEquals(42, result);
+
+ // protected navigation
+ script = jexl.createScript("$in.`${p}`?.intValue()", "p");
+ result = script.execute(ctxt, "fail");
+ Assert.assertEquals(null, result);
+ result = script.execute(ctxt, "stuff");
+ Assert.assertEquals(42, result);
+
+ }
+ @Test
+ public void test275b() throws Exception {
+ JexlEngine jexl = new JexlBuilder().strict(true).safe(true).create();
+ JexlContext ctxt = new MapContext();
+ JexlScript script;
+ Object result = null;
+ Prompt p0 = new Prompt();
+ p0.set("stuff", 42);
+ ctxt.set("$in", p0);
+
+ // unprotected navigation
+ script = jexl.createScript("$in[p].intValue()", "p");
+ result = script.execute(ctxt, "fail");
+ Assert.assertEquals(null, result);
+
+ result = script.execute(ctxt, "stuff");
+ Assert.assertEquals(42, result);
+
+
+ // unprotected navigation
+ script = jexl.createScript("$in.`${p}`.intValue()", "p");
+ result = script.execute(ctxt, "fail");
+ Assert.assertEquals(null, result);
+ result = script.execute(ctxt, "stuff");
+ Assert.assertEquals(42, result);
+
+ // protected navigation
+ script = jexl.createScript("$in.`${p}`?.intValue()", "p");
+ result = script.execute(ctxt, "fail");
+ Assert.assertEquals(null, result);
+ result = script.execute(ctxt, "stuff");
+ Assert.assertEquals(42, result);
+
+ }
+
}
\ No newline at end of file