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 cc963c9 JEXL-346: use namespace and variable names to disambiguate ternary expressions cc963c9 is described below commit cc963c93c1ded0238f1cb53cea69cfc0fd659b9c Author: henrib <hen...@apache.org> AuthorDate: Mon May 31 19:41:34 2021 +0200 JEXL-346: use namespace and variable names to disambiguate ternary expressions --- .../org/apache/commons/jexl3/JexlFeatures.java | 25 +++++++++++++ .../org/apache/commons/jexl3/internal/Engine.java | 15 ++++++-- .../apache/commons/jexl3/parser/JexlParser.java | 42 ++++++++++++++++++++++ .../apache/commons/jexl3/ContextNamespaceTest.java | 35 ++++++++++++++++++ 4 files changed, 114 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java index dc79c75..9da12df 100644 --- a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java +++ b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java @@ -18,9 +18,11 @@ package org.apache.commons.jexl3; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.Set; import java.util.TreeSet; import java.util.Objects; +import java.util.function.Predicate; /** * A set of language feature options. @@ -50,6 +52,10 @@ public final class JexlFeatures { private long flags; /** The set of reserved names, aka global variables that can not be masked by local variables or parameters. */ private Set<String> reservedNames; + /** The namespace names. */ + private Predicate<String> nameSpaces; + /** The false predicate. */ + public static final Predicate<String> TEST_STR_FALSE = (s)->false; /** Te feature names (for toString()). */ private static final String[] F_NAMES = { "register", "reserved variable", "local variable", "assign/modify", @@ -106,6 +112,7 @@ public final class JexlFeatures { | (1L << ANNOTATION) | (1L << SCRIPT); reservedNames = Collections.emptySet(); + nameSpaces = TEST_STR_FALSE; } /** @@ -115,6 +122,7 @@ public final class JexlFeatures { public JexlFeatures(final JexlFeatures features) { this.flags = features.flags; this.reservedNames = features.reservedNames; + this.nameSpaces = features.nameSpaces; } @Override @@ -187,6 +195,23 @@ public final class JexlFeatures { } /** + * Sets a test to determine namespace declaration. + * @param names the name predicate + * @return this features instance + */ + public JexlFeatures namespaceTest(final Predicate<String> names) { + nameSpaces = names == null? TEST_STR_FALSE : names; + return this; + } + + /** + * @return the declared namespaces test. + */ + public Predicate<String> namespaceTest() { + return nameSpaces; + } + + /** * Sets a feature flag. * @param feature the feature ordinal * @param flag turn-on, turn off 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 cc6959c..54cee50 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java +++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java @@ -55,6 +55,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; /** * A JexlEngine implementation. @@ -212,8 +213,13 @@ public class Engine extends JexlEngine { this.functions = conf.namespaces() == null ? Collections.emptyMap() : conf.namespaces(); // parsing & features: final JexlFeatures features = conf.features() == null? DEFAULT_FEATURES : conf.features(); - this.expressionFeatures = new JexlFeatures(features).script(false); - this.scriptFeatures = new JexlFeatures(features).script(true); + Predicate<String> nsTest = features.namespaceTest(); + final Set<String> nsNames = functions.keySet(); + if (!nsNames.isEmpty()) { + nsTest = nsTest == JexlFeatures.TEST_STR_FALSE ?nsNames::contains : nsTest.or(nsNames::contains); + } + this.expressionFeatures = new JexlFeatures(features).script(false).namespaceTest(nsTest); + this.scriptFeatures = new JexlFeatures(features).script(true).namespaceTest(nsTest); this.charset = conf.charset(); // caching: this.cache = conf.cache() <= 0 ? null : new SoftCache<Source, ASTJexlScript>(conf.cache()); @@ -840,7 +846,10 @@ public class Engine extends JexlEngine { */ protected ASTJexlScript parse(final JexlInfo info, final JexlFeatures parsingf, final String src, final Scope scope) { final boolean cached = src.length() < cacheThreshold && cache != null; - final JexlFeatures features = parsingf != null? parsingf : DEFAULT_FEATURES; + JexlFeatures features = parsingf != null? parsingf : DEFAULT_FEATURES; + // if (features.getNameSpaces().isEmpty() && !functions.isEmpty()) { + // features = new JexlFeatures(features).nameSpaces(functions.keySet()); + // } final Source source = cached? new Source(features, src) : null; ASTJexlScript script = null; if (source != null) { diff --git a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java index 396111d..b630cf4 100644 --- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java +++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java @@ -29,12 +29,15 @@ import java.io.StringReader; import java.lang.reflect.Constructor; import java.util.ArrayDeque; import java.util.Arrays; +import java.util.Collections; import java.util.Deque; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; +import java.util.TreeSet; +import java.util.function.Predicate; /** @@ -68,6 +71,10 @@ public abstract class JexlParser extends StringParser { */ protected Map<String, Object> pragmas = null; /** + * The known namespaces. + */ + protected Set<String> namespaces = null; + /** * The number of imbricated loops. */ protected int loopCount = 0; @@ -121,6 +128,7 @@ public abstract class JexlParser extends StringParser { frame = null; frames.clear(); pragmas = null; + namespaces = null; loopCounts.clear(); loopCount = 0; blocks.clear(); @@ -283,6 +291,10 @@ public abstract class JexlParser extends StringParser { return false; } + protected boolean isVariable(String name) { + return frame != null && frame.getSymbol(name) != null; + } + /** * Checks whether an identifier is a local variable or argument, ie a symbol, stored in a register. * @param identifier the identifier @@ -391,6 +403,11 @@ public abstract class JexlParser extends StringParser { } /** + * The prefix of a namespace pragma. + */ + protected static final String PRAGMA_JEXLNS = "jexl.namespace."; + + /** * Adds a pragma declaration. * @param key the pragma key * @param value the pragma value @@ -402,10 +419,35 @@ public abstract class JexlParser extends StringParser { if (pragmas == null) { pragmas = new TreeMap<String, Object>(); } + // declaring a namespace + Predicate<String> ns = getFeatures().namespaceTest(); + if (ns != null && key.startsWith(PRAGMA_JEXLNS)) { + // jexl.namespace.*** + final String nsname = key.substring(PRAGMA_JEXLNS.length()); + if (nsname != null && !nsname.isEmpty()) { + if (namespaces == null) { + namespaces = new HashSet<>(); + } + namespaces.add(nsname); + } + } pragmas.put(key, value); } /** + * Checks whether a name identifies a declared namespace. + * @param name the name + * @return true if the name qualifies a namespace + */ + protected boolean isDeclaredNamespace(String name) { + final Set<String> ns = namespaces; + if (ns != null && ns.contains(name)) { + return true; + } + return getFeatures().namespaceTest().test(name); + } + + /** * Declares a local parameter. * <p> This method creates an new entry in the symbol map. </p> * @param token the parameter name toekn diff --git a/src/test/java/org/apache/commons/jexl3/ContextNamespaceTest.java b/src/test/java/org/apache/commons/jexl3/ContextNamespaceTest.java index 09ca169..fb928cb 100644 --- a/src/test/java/org/apache/commons/jexl3/ContextNamespaceTest.java +++ b/src/test/java/org/apache/commons/jexl3/ContextNamespaceTest.java @@ -19,6 +19,11 @@ package org.apache.commons.jexl3; import org.junit.Assert; import org.junit.Test; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; + /** * Tests JexlContext (advanced) features. */ @@ -98,6 +103,36 @@ public class ContextNamespaceTest extends JexlTestCase { Assert.assertEquals(372., result); } + public static class Context346 extends MapContext { + public int func(int y) { return 42 * y;} + } + + @Test + public void testNamespace346a() throws Exception { + JexlContext ctxt = new Context346(); + final JexlEngine jexl = new JexlBuilder().safe(false).create(); + String src = "x != null ? x : func(y)"; + final JexlScript script = jexl.createScript(src,"x","y"); + Object result = script.execute(ctxt, null, 1); + Assert.assertEquals(42, result); + result = script.execute(ctxt, 169, -169); + Assert.assertEquals(169, result); + } + + @Test + public void testNamespace346b() throws Exception { + JexlContext ctxt = new MapContext(); + Map<String, Object> ns = new HashMap<String, Object>(); + ns.put("x", Math.class); + ns.put(null, Math.class); + final JexlEngine jexl = new JexlBuilder().safe(false).namespaces(ns).create(); + String src = "x != null ? x : abs(y)"; + final JexlScript script = jexl.createScript(src,"x","y"); + Object result = script.execute(ctxt, null, 42); + Assert.assertEquals(42, result); + result = script.execute(ctxt, 169, -169); + Assert.assertEquals(169, result); + } @Test public void testNamespacePragmaString() throws Exception {