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 2021/05/31 17:41:40 UTC

[commons-jexl] branch master updated: JEXL-346: use namespace and variable names to disambiguate ternary expressions

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 <he...@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 {