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 <hen...@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;