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
commit a3c210cfc5eabd1bbb1d6ae4b009fe831e2f1d9b Author: Henri Biestro <hbies...@gmail.com> AuthorDate: Sat Jun 8 15:20:45 2019 +0200 JEXL-302: added jexl builder flag to control variable collecting behavior (collectAll); fixed wrong collecting behavior on array references Task #JEXL-302 - JexlScript.getVariables returns strange values for array access --- .../java/org/apache/commons/jexl3/JexlBuilder.java | 27 ++++++++++-- .../org/apache/commons/jexl3/internal/Engine.java | 50 +++++++++++++++++++--- .../commons/jexl3/internal/TemplateEngine.java | 4 +- .../commons/jexl3/internal/TemplateScript.java | 2 +- .../org/apache/commons/jexl3/JexlTestCase.java | 8 ++-- .../java/org/apache/commons/jexl3/VarTest.java | 48 +++++++++++++++++++++ 6 files changed, 121 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java index 38786a8..ad69d67 100644 --- a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java +++ b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java @@ -84,13 +84,16 @@ public class JexlBuilder { /** Whether this engine is in tolerant mode. */ private Boolean safe = false; - + /** Whether error messages will carry debugging information. */ private Boolean debug = null; /** Whether interrupt throws JexlException.Cancel. */ private Boolean cancellable = null; + /** Whether getVariables considers all potential equivalent syntactic forms. */ + private Boolean collectAll = null; + /** The map of 'prefix:function' to object implementing the namespaces. */ private Map<String, Object> namespaces = null; @@ -99,7 +102,7 @@ public class JexlBuilder { /** The cache size. */ private int cache = -1; - + /** The stack overflow limit. */ private int stackOverflow = Integer.MAX_VALUE; @@ -309,7 +312,7 @@ public class JexlBuilder { public Boolean safe() { return this.safe; } - + /** * Sets whether the engine will report debugging information when error occurs. * @@ -348,6 +351,22 @@ public class JexlBuilder { } /** + * Sets whether the engine variable collectors considers all potential forms of variable syntaxes. + * + * @param flag true means var collections considers constant array accesses equivalent to dotted references + * @return this builder + */ + public JexlBuilder collectAll(boolean flag) { + this.collectAll = flag; + return this; + } + + /** @return true if collect all, false otherwise */ + public Boolean collectAll() { + return this.collectAll; + } + + /** * Sets the default namespaces map the engine will use. * <p> * Each entry key is used as a prefix, each entry value used as a bean implementing @@ -442,7 +461,7 @@ public class JexlBuilder { public int stackOverflow() { return stackOverflow; } - + /** * @return a {@link JexlEngine} instance */ 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 475bfcb..a98af2e 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java +++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java @@ -149,6 +149,10 @@ public class Engine extends JexlEngine { * The default jxlt engine. */ protected volatile TemplateEngine jxlt = null; + /** + * Collect all or only dot references. + */ + protected final boolean collectAll; /** * Creates an engine with default arguments. @@ -163,11 +167,12 @@ 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(); + this.strict = option(conf.strict(), true); + this.safe = option(conf.safe(), false); + this.silent = option(conf.silent(), false); + this.cancellable = option(conf.cancellable(), !silent && strict ); + this.debug = option(conf.debug(), true); + this.collectAll = option(conf.collectAll(), true); this.stackOverflow = conf.stackOverflow() > 0? conf.stackOverflow() : Integer.MAX_VALUE; // core properties: JexlUberspect uber = conf.uberspect() == null ? getUberspect(conf.logger(), conf.strategy()) : conf.uberspect(); @@ -198,6 +203,16 @@ public class Engine extends JexlEngine { } /** + * Solves an optional option. + * @param conf the option as configured, may be null + * @param def the default value if null + * @return true or false + */ + private boolean option(Boolean conf, boolean def) { + return conf == null? def : conf; + } + + /** * Gets the default instance of Uberspect. * <p>This is lazily initialized to avoid building a default instance if there * is no use for it. The main reason for not using the default Uberspect instance is to @@ -493,12 +508,20 @@ public class Engine extends JexlEngine { * or the empty set if no variables are used */ protected Set<List<String>> getVariables(ASTJexlScript script) { - VarCollector collector = new VarCollector(); + VarCollector collector = varCollector(); getVariables(script, script, collector); return collector.collected(); } /** + * Creates a collector instance. + * @return a collector instance + */ + protected VarCollector varCollector() { + return new VarCollector(this.collectAll); + } + + /** * Utility class to collect variables. */ protected static class VarCollector { @@ -514,6 +537,18 @@ public class Engine extends JexlEngine { * The node that started the collect. */ private JexlNode root = null; + /** + * Whether constant array-access is considered equivalent to dot-access. + */ + private boolean semantic = true; + + /** + * Constructor. + * @param constaa whether constant array-access is considered equivalent to dot-access + */ + protected VarCollector(boolean constaa) { + semantic = constaa; + } /** * Starts/stops a variable collect. @@ -585,7 +620,7 @@ public class Engine extends JexlEngine { if (collector.isCollecting()) { collector.add(((ASTIdentifierAccess) node).getName()); } - } else if (node instanceof ASTArrayAccess) { + } else if (node instanceof ASTArrayAccess && collector.semantic) { int num = node.jjtGetNumChildren(); // collect only if array access is const and follows an identifier boolean collecting = collector.isCollecting(); @@ -598,6 +633,7 @@ public class Engine extends JexlEngine { collecting = false; collector.collect(null); getVariables(script, child, collector); + collector.collect(null); } } } else { 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 da05161..076ba1d 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java +++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java @@ -428,7 +428,7 @@ public final class TemplateEngine extends JxltEngine { @Override public Set<List<String>> getVariables() { - Engine.VarCollector collector = new Engine.VarCollector(); + Engine.VarCollector collector = jexl.varCollector(); getVariables(collector); return collector.collected(); } @@ -591,7 +591,7 @@ public final class TemplateEngine extends JxltEngine { @Override public Set<List<String>> getVariables() { - Engine.VarCollector collector = new Engine.VarCollector(); + Engine.VarCollector collector = jexl.varCollector(); for (TemplateExpression expr : exprs) { expr.getVariables(collector); } diff --git a/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java b/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java index 321d2b8..23ab623 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java +++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java @@ -195,7 +195,7 @@ public final class TemplateScript implements JxltEngine.Template { @Override public Set<List<String>> getVariables() { - Engine.VarCollector collector = new Engine.VarCollector(); + Engine.VarCollector collector = jxlt.getEngine().varCollector(); for (TemplateExpression expr : exprs) { expr.getVariables(collector); } diff --git a/src/test/java/org/apache/commons/jexl3/JexlTestCase.java b/src/test/java/org/apache/commons/jexl3/JexlTestCase.java index 666b706..2662fc7 100644 --- a/src/test/java/org/apache/commons/jexl3/JexlTestCase.java +++ b/src/test/java/org/apache/commons/jexl3/JexlTestCase.java @@ -32,9 +32,9 @@ import org.junit.Assert; */ public class JexlTestCase { /** No parameters signature for test run. */ - private static final Class<?>[] noParms = {}; + private static final Class<?>[] NO_PARMS = {}; /** String parameter signature for test run. */ - private static final Class<?>[] stringParm = {String.class}; + private static final Class<?>[] STRING_PARM = {String.class}; /** A default JEXL engine instance. */ protected final JexlEngine JEXL; @@ -86,7 +86,7 @@ public class JexlTestCase { } Method method = null; try { - method = this.getClass().getDeclaredMethod(name, noParms); + method = this.getClass().getDeclaredMethod(name, NO_PARMS); } catch(Exception xany) { Assert.fail("no such test: " + name); @@ -127,7 +127,7 @@ public class JexlTestCase { // find ctor & instantiate Constructor<JexlTestCase> ctor = null; try { - ctor = clazz.getConstructor(stringParm); + ctor = clazz.getConstructor(STRING_PARM); test = ctor.newInstance("debug"); } catch(NoSuchMethodException xctor) { diff --git a/src/test/java/org/apache/commons/jexl3/VarTest.java b/src/test/java/org/apache/commons/jexl3/VarTest.java index df2c440..f0f3126 100644 --- a/src/test/java/org/apache/commons/jexl3/VarTest.java +++ b/src/test/java/org/apache/commons/jexl3/VarTest.java @@ -298,6 +298,54 @@ public class VarTest extends JexlTestCase { vars = e.getVariables(); expect = mkref(new String[][]{{"a", "b"}, {"c"}}); Assert.assertTrue(eq(expect, vars)); + + e = JEXL.createScript("a[b].c"); + vars = e.getVariables(); + expect = mkref(new String[][]{{"a"}, {"b"}}); + Assert.assertTrue(eq(expect, vars)); + + e = JEXL.createScript("a[b].c[d]"); + vars = e.getVariables(); + expect = mkref(new String[][]{{"a"}, {"b"}, {"d"}}); + Assert.assertTrue(eq(expect, vars)); + + e = JEXL.createScript("a[b][e].c[d][f]"); + vars = e.getVariables(); + expect = mkref(new String[][]{{"a"}, {"b"}, {"d"}, {"e"}, {"f"}}); + Assert.assertTrue(eq(expect, vars)); + } + + @Test + public void testVarCollectNotAll() throws Exception { + JexlScript e; + Set<List<String>> vars; + Set<List<String>> expect; + JexlEngine jexl = new JexlBuilder().strict(true).silent(false).cache(32).collectAll(false).create(); + + e = jexl.createScript("a['b'][c]"); + vars = e.getVariables(); + expect = mkref(new String[][]{{"a"}, {"c"}}); + Assert.assertTrue(eq(expect, vars)); + + e = jexl.createScript(" A + B[C] + D[E[F]] + x[y[z]] "); + vars = e.getVariables(); + expect = mkref(new String[][]{{"A"}, {"B"}, {"C"}, {"D"}, {"E"}, {"F"}, {"x"} , {"y"}, {"z"}}); + Assert.assertTrue(eq(expect, vars)); + + e = jexl.createScript("e['f']['g']"); + vars = e.getVariables(); + expect = mkref(new String[][]{{"e"}}); + Assert.assertTrue(eq(expect, vars)); + + e = jexl.createScript("a[b][e].c[d][f]"); + vars = e.getVariables(); + expect = mkref(new String[][]{{"a"}, {"b"}, {"d"}, {"e"}, {"f"}}); + Assert.assertTrue(eq(expect, vars)); + + e = jexl.createScript("a + b.c + b.c.d + e['f']"); + vars = e.getVariables(); + expect = mkref(new String[][]{{"a"}, {"b", "c"}, {"b", "c", "d"}, {"e"}}); + Assert.assertTrue(eq(expect, vars)); } @Test