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 2020/06/11 18:24:56 UTC

[commons-jexl] branch master updated: JEXL-333: added namespace handling to pragma processing; added test Task #JEXL-333 - Allow declaration of namespace within script

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 826d3d3  JEXL-333: added namespace handling to pragma processing; added test Task #JEXL-333 - Allow declaration of namespace within script
826d3d3 is described below

commit 826d3d3b29c9ad5507d50135c4f9dd1804dac96d
Author: henrib <he...@apache.org>
AuthorDate: Thu Jun 11 20:24:20 2020 +0200

    JEXL-333: added namespace handling to pragma processing; added test
    Task #JEXL-333 - Allow declaration of namespace within script
---
 RELEASE-NOTES.txt                                  |  1 +
 .../java/org/apache/commons/jexl3/JexlBuilder.java |  7 +--
 .../java/org/apache/commons/jexl3/JexlOptions.java | 21 +++++++++
 .../org/apache/commons/jexl3/internal/Engine.java  | 25 +++++++++-
 .../commons/jexl3/internal/InterpreterBase.java    |  7 +--
 .../jexl3/internal/introspection/Introspector.java |  6 +--
 src/site/xdoc/changes.xml                          |  3 ++
 .../apache/commons/jexl3/ContextNamespaceTest.java | 53 +++++++++++++++++++---
 8 files changed, 104 insertions(+), 19 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 6d1bca8..ae6f5e3 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -48,6 +48,7 @@ What's new in 3.2:
 
 New Features in 3.2:
 ====================
+* JEXL-333:      Allow declaration of namespace within script
 * JEXL-317:      Support script cancellation through less invasive API
 * JEXL-307:      Variable redeclaration option
 * JEXL-295:      Add unary plus operator
diff --git a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
index c9212e5..2a5b006 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
@@ -85,9 +85,6 @@ public class JexlBuilder {
     /** Whether getVariables considers all potential equivalent syntactic forms. */
     private int collectMode = 1;
 
-    /** The map of 'prefix:function' to object implementing the namespaces. */
-    private Map<String, Object> namespaces = null;
-
     /** The {@link JexlArithmetic} instance. */
     private JexlArithmetic arithmetic = null;
 
@@ -475,7 +472,7 @@ public class JexlBuilder {
      * @return this builder
      */
     public JexlBuilder namespaces(Map<String, Object> ns) {
-        this.namespaces = ns;
+        options.setNamespaces(ns);
         return this;
     }
 
@@ -483,7 +480,7 @@ public class JexlBuilder {
      * @return the map of namespaces.
      */
     public Map<String, Object> namespaces() {
-        return this.namespaces;
+        return options.getNamespaces();
     }
 
     /**
diff --git a/src/main/java/org/apache/commons/jexl3/JexlOptions.java b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
index 943aa72..cf316e1 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlOptions.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
@@ -18,6 +18,8 @@
 package org.apache.commons.jexl3;
 
 import java.math.MathContext;
+import java.util.Collections;
+import java.util.Map;
 import org.apache.commons.jexl3.internal.Engine;
 
 /**
@@ -68,6 +70,8 @@ public final class JexlOptions {
     private boolean strictArithmetic = true;
     /** The default flags, all but safe. */
     private int flags = DEFAULT;
+    /** The namespaces .*/
+    private Map<String, Object> namespaces = Collections.emptyMap();
 
     /**
      * Sets the value of a flag in a mask.
@@ -376,10 +380,27 @@ public final class JexlOptions {
         mathScale = src.mathScale;
         strictArithmetic = src.strictArithmetic;
         flags = src.flags;
+        namespaces = src.namespaces;
         return this;
     }
+    
+    /**
+     * Gets the optional map of namespaces.
+     * @return the map of namespaces, may be empty, not null
+     */
+    public Map<String, Object> getNamespaces() {
+        return namespaces;
+    }
 
     /**
+     * Sets the optional map of namespaces
+     * @param ns a namespaces map
+     */
+    public void setNamespaces(Map<String, Object> ns) {
+        this.namespaces = ns == null? Collections.emptyMap() : ns;
+    }
+    
+    /**
      * Creates a copy of this instance.
      * @return a copy
      */
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 ae00865..d458139 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
@@ -49,6 +49,7 @@ import java.io.StringReader;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -81,6 +82,10 @@ public class Engine extends JexlEngine {
      */
     protected static final String PRAGMA_OPTIONS = "jexl.options";
     /**
+     * The prefix of a namespace pragma.
+     */
+    protected static final String PRAGMA_JEXLNS = "jexl.namespace.";
+    /**
      * The Log to which all JexlEngine messages will be logged.
      */
     protected final Log logger;
@@ -380,19 +385,35 @@ public class Engine extends JexlEngine {
                     context instanceof JexlContext.PragmaProcessor
                     ? (JexlContext.PragmaProcessor) context
                     : null;
+            Map<String, Object> ns = null;
             for(Map.Entry<String, Object> pragma : pragmas.entrySet()) {
                 String key = pragma.getKey();
                 Object value = pragma.getValue();
                 if (PRAGMA_OPTIONS.equals(key)) {
+                    // jexl.options
                     if (value instanceof String) {
                         String[] vs = ((String) value).split(" ");
                         opts.setFlags(vs);
                     }
                 }
+                else if (key.startsWith(PRAGMA_JEXLNS) && value instanceof String) {
+                    // jexl.namespace.***
+                    String nsname = key.substring(PRAGMA_JEXLNS.length());
+                    if (nsname != null && !nsname.isEmpty()) {
+                        String nsclass = value.toString();
+                        if (ns == null) {
+                            ns = new HashMap<>(functions);
+                        }
+                        ns.put(nsname, nsclass);
+                    }
+                }
                 if (processor != null) {
                     processor.processPragma(key, value);
                 }
             }
+            if (ns != null) {
+                opts.setNamespaces(ns);
+            }
         }
     }
     
@@ -479,7 +500,7 @@ public class Engine extends JexlEngine {
             final ASTJexlScript script = parse(null, PROPERTY_FEATURES, src, scope);
             final JexlNode node = script.jjtGetChild(0);
             final Frame frame = script.createFrame(bean);
-            final Interpreter interpreter = createInterpreter(context, frame, null);
+            final Interpreter interpreter = createInterpreter(context, frame, options);
             return interpreter.visitLexicalNode(node, null);
         } catch (JexlException xjexl) {
             if (silent) {
@@ -508,7 +529,7 @@ public class Engine extends JexlEngine {
             final ASTJexlScript script = parse(null, PROPERTY_FEATURES, src, scope);
             final JexlNode node = script.jjtGetChild(0);
             final Frame frame = script.createFrame(bean, value);
-            final Interpreter interpreter = createInterpreter(context, frame, null);
+            final Interpreter interpreter = createInterpreter(context, frame, options);
             interpreter.visitLexicalNode(node, null);
         } catch (JexlException xjexl) {
             if (silent) {
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 0b05b4d..b56245a 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
@@ -69,7 +69,7 @@ public abstract class InterpreterBase extends ParserVisitor {
     protected final AtomicBoolean cancelled;
     /** Empty parameters for method matching. */
     protected static final Object[] EMPTY_PARAMS = new Object[0];
-    /** The context to store/retrieve variables. */
+    /** The namespace resolver. */
     protected final JexlContext.NamespaceResolver ns;
     /** The operators evaluation delegate. */
     protected final Operators operators;
@@ -108,7 +108,8 @@ public abstract class InterpreterBase extends ParserVisitor {
             acancel = ((JexlContext.CancellationHandle) context).getCancellation();
         }
         this.cancelled = acancel != null? acancel : new AtomicBoolean(false);
-        this.functions = jexl.functions;
+        Map<String,Object> ons = options.getNamespaces();
+        this.functions = ons.isEmpty()? jexl.functions : ons;
         this.functors = null;
         this.operators = new Operators(this);
     }
@@ -228,7 +229,7 @@ public abstract class InterpreterBase extends ParserVisitor {
         }
         return namespace;
     }
-    
+
     /**
      * Defines a variable.
      * @param var the variable to define
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
index b442aa3..88c460a 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
@@ -226,8 +226,8 @@ public final class Introspector {
      */
     public Constructor<?> getConstructor(final Class<?> c, final MethodKey key) {
         Constructor<?> ctor;
+        lock.readLock().lock();
         try {
-            lock.readLock().lock();
             ctor = constructorsMap.get(key);
             if (ctor != null) {
                 // miss or not?
@@ -237,8 +237,8 @@ public final class Introspector {
             lock.readLock().unlock();
         }
         // let's introspect...
+        lock.writeLock().lock();
         try {
-            lock.writeLock().lock();
             // again for kicks
             ctor = constructorsMap.get(key);
             if (ctor != null) {
@@ -259,7 +259,7 @@ public final class Introspector {
                     // add it to list of known loaded classes
                     constructibleClasses.put(cname, clazz);
                 }
-                List<Constructor<?>> l = new ArrayList<Constructor<?>>();
+                List<Constructor<?>> l = new ArrayList<>();
                 for (Constructor<?> ictor : clazz.getConstructors()) {
                     if (permissions.allow(ictor)) {
                         l.add(ictor);
diff --git a/src/site/xdoc/changes.xml b/src/site/xdoc/changes.xml
index fbf12b5..ef34341 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-333">
+                Allow declaration of namespace within script
+            </action>
             <action dev="henrib" type="fix" issue="JEXL-331" due-to="David Costanzo">
                 Please document \uXXXX escape sequence
             </action>
diff --git a/src/test/java/org/apache/commons/jexl3/ContextNamespaceTest.java b/src/test/java/org/apache/commons/jexl3/ContextNamespaceTest.java
index 48f41d6..6f38994 100644
--- a/src/test/java/org/apache/commons/jexl3/ContextNamespaceTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ContextNamespaceTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.commons.jexl3;
 
-import org.apache.commons.jexl3.internal.Engine;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -34,9 +33,18 @@ public class ContextNamespaceTest extends JexlTestCase {
      * Accesses the thread context and cast it.
      */
     public static class Taxes {
+        private final double vat;
+        
+        public Taxes(TaxesContext ctxt) {
+            vat = ctxt.getVAT();
+        } 
+        
+        public Taxes(double d) {
+            vat = d;
+        }
+        
         public double vat(double n) {
-            TaxesContext context = (TaxesContext) JexlEngine.getThreadContext();
-            return n * context.getVAT() / 100.;
+            return (n * vat) / 100.;
         }
     }
 
@@ -44,7 +52,7 @@ public class ContextNamespaceTest extends JexlTestCase {
      * A thread local context carrying a namespace and some inner constants.
      */
     public static class TaxesContext extends MapContext implements JexlContext.ThreadLocal, JexlContext.NamespaceResolver {
-        private final Taxes taxes = new Taxes();
+        private Taxes taxes = null;
         private final double vat;
 
         TaxesContext(double vat) {
@@ -53,7 +61,13 @@ public class ContextNamespaceTest extends JexlTestCase {
 
         @Override
         public Object resolveNamespace(String name) {
-            return "taxes".equals(name) ? taxes : null;
+            if ("taxes".equals(name)) {
+                if (taxes == null) {
+                    taxes = new Taxes(vat);
+                }
+                return taxes;
+            }
+            return null;
         }
 
         public double getVAT() {
@@ -63,13 +77,40 @@ public class ContextNamespaceTest extends JexlTestCase {
 
     @Test
     public void testThreadedContext() throws Exception {
-        JexlEngine jexl = new Engine();
+        JexlEngine jexl = new JexlBuilder().create();
         TaxesContext context = new TaxesContext(18.6);
         String strs = "taxes:vat(1000)";
         JexlScript staxes = jexl.createScript(strs);
         Object result = staxes.execute(context);
         Assert.assertEquals(186., result);
     }
+    
+    @Test
+    public void testNamespacePragma() throws Exception {
+        JexlEngine jexl = new JexlBuilder().create();
+        JexlContext context = new TaxesContext(18.6);
+        // local namespace tax declared
+        String strs =
+                  "#pragma jexl.namespace.tax org.apache.commons.jexl3.ContextNamespaceTest$Taxes\n"
+                + "tax:vat(2000)";
+        JexlScript staxes = jexl.createScript(strs);
+        Object result = staxes.execute(context);
+        Assert.assertEquals(372., result);
+    }
+
+        
+    @Test
+    public void testNamespacePragmaString() throws Exception {
+        JexlEngine jexl = new JexlBuilder().create();
+        JexlContext context = new MapContext();
+        // local namespace str declared
+        String strs =
+                  "#pragma jexl.namespace.str java.lang.String\n"
+                + "str:format('%04d', 42)";
+        JexlScript staxes = jexl.createScript(strs);
+        Object result = staxes.execute(context);
+        Assert.assertEquals("0042", result);
+    }
 
     public static class Vat {
         private double vat;