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 2023/02/03 14:07:20 UTC

[commons-jexl] 01/01: JEXL-392: initial drop;

This is an automated email from the ASF dual-hosted git repository.

henrib pushed a commit to branch JEXL-392
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git

commit cc999410c0b2d62af5628c09eca9b243be2d953e
Author: henrib <he...@apache.org>
AuthorDate: Fri Feb 3 15:07:13 2023 +0100

    JEXL-392: initial drop;
---
 pom.xml                                            |  41 +++++++-
 .../java/org/apache/commons/jexl3/JexlContext.java |  19 ++++
 .../java/org/apache/commons/jexl3/JexlOptions.java |   4 +-
 .../org/apache/commons/jexl3/internal/Engine.java  | 115 +++++++++++++++------
 .../apache/commons/jexl3/parser/JexlParser.java    |   4 +
 .../java/org/apache/commons/jexl3/PragmaTest.java  |  64 ++++++++++++
 6 files changed, 214 insertions(+), 33 deletions(-)

diff --git a/pom.xml b/pom.xml
index b655a9ee..79f9d919 100644
--- a/pom.xml
+++ b/pom.xml
@@ -185,7 +185,46 @@
                     </execution>
                 </executions>
             </plugin>
-
+<!--
+            <plugin>
+                <artifactId>maven-antrun-plugin</artifactId>
+                <version>3.1.0</version>
+                <executions>
+                    <execution>
+                        <phase>generate-sources</phase>
+                        <configuration>
+                            <target>
+                                <copy todir="/Users/henri.biestro/kShuttle/trunk/trunk/Jexl/src" overwrite='true' verbose='true'>
+                                    -overwrite='true' verbose='true'-
+                                    <fileset dir="src">
+                                        <include name="**/*.java"/>
+                                    </fileset>
+                                    <regexpmapper from="(.*)/org/apache/commons/jexl3/(.*)\.java$$" to="\1/com/nellarmonia/jexl/\2.java"/>
+                                    <filterchain>
+                                        <replacestring from="org.apache.commons.jexl3" to="com.nellarmonia.jexl"/>
+                                        <replacestring from="org.apache.whatever" to="com.nellarmonia.whatever"/>
+                                    </filterchain>
+                                </copy>
+                                <copy todir="/Users/henri.biestro/kShuttle/trunk/trunk/Jexl/src" overwrite='true' verbose='true'>
+                                    -overwrite='true' verbose='true'-
+                                    <fileset dir="src">
+                                        <include name="**/*.jjt"/>
+                                    </fileset>
+                                    <regexpmapper from="(.*)/org/apache/commons/jexl3/(.*)\.jjt$$" to="\1/com/nellarmonia/jexl/\2.jjt"/>
+                                    <filterchain>
+                                        <replacestring from="org.apache.commons.jexl3" to="com.nellarmonia.jexl"/>
+                                        <replacestring from="org.apache.whatever.logging" to="com.nellarmonia.whatever"/>
+                                    </filterchain>
+                                </copy>
+                            </target>
+                        </configuration>
+                        <goals>
+                            <goal>run</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+-->
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-jar-plugin</artifactId>
diff --git a/src/main/java/org/apache/commons/jexl3/JexlContext.java b/src/main/java/org/apache/commons/jexl3/JexlContext.java
index 4eb810cb..397d4e88 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlContext.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlContext.java
@@ -158,6 +158,25 @@ public interface JexlContext {
         Object processAnnotation(String name, Object[] args, Callable<Object> statement) throws Exception;
     }
 
+    /**
+     * A marker interface of the JexlContext that processes module definitions.
+     * It is used by the interpreter during evaluation of the pragma module definitions.
+     * @since 3.3
+     */
+    interface ModuleProcessor {
+        /**
+         * Defines a module.
+         * The module name will be the namespace mapped to the object returned by the evaluation
+         * of its body.
+         * @param engine the engine evaluating this module pragma
+         * @param info the info at the pragma location
+         * @param name the module name
+         * @param body the module definition which can be its location or source
+         * @return the module object
+         */
+        Object processModule(JexlEngine engine, JexlInfo info, String name, String body);
+    }
+
     /**
      * A marker interface of the JexlContext that exposes runtime evaluation options.
      * @since 3.2
diff --git a/src/main/java/org/apache/commons/jexl3/JexlOptions.java b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
index c889e468..2f85b5c1 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlOptions.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
@@ -411,7 +411,7 @@ public final class JexlOptions {
      * @param ns a namespaces map
      */
     public void setNamespaces(final Map<String, Object> ns) {
-        this.namespaces = ns == null? Collections.emptyMap() : ns;
+        this.namespaces = ns == null || ns.isEmpty()? Collections.emptyMap() : ns;
     }
 
     /**
@@ -427,7 +427,7 @@ public final class JexlOptions {
      * @param imports the imported packages
      */
     public void setImports(final Collection<String> imports) {
-        this.imports = imports == null? Collections.emptySet() : imports;
+        this.imports = imports == null || imports.isEmpty()? Collections.emptySet() : imports;
     }
 
     /**
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 c27d88a6..bfd2a786 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
@@ -48,15 +48,17 @@ import org.apache.commons.logging.LogFactory;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 import static org.apache.commons.jexl3.parser.JexlParser.PRAGMA_IMPORT;
+import static org.apache.commons.jexl3.parser.JexlParser.PRAGMA_MODULE;
 import static org.apache.commons.jexl3.parser.JexlParser.PRAGMA_JEXLNS;
 import static org.apache.commons.jexl3.parser.JexlParser.PRAGMA_OPTIONS;
 
@@ -422,7 +424,6 @@ public class Engine extends JexlEngine {
                             ? (JexlContext.PragmaProcessor) context
                             : null;
             Map<String, Object> ns = null;
-            Set<String> is = null;
             for (final Map.Entry<String, Object> pragma : pragmas.entrySet()) {
                 final String key = pragma.getKey();
                 final Object value = pragma.getValue();
@@ -434,45 +435,99 @@ public class Engine extends JexlEngine {
                     }
                 }  else if (PRAGMA_IMPORT.equals(key)) {
                     // jexl.import, may use a set
-                    final Set<?> values = value instanceof Set<?>
-                            ? (Set<?>) value
-                            : Collections.singleton(value);
-                    for (final Object o : values) {
+                    final Set<String> is = new LinkedHashSet<>();
+                    withValueSet(value, (o)->{
                         if (o instanceof String) {
-                            if (is == null) {
-                                is = new LinkedHashSet<>();
-                            }
                             is.add(o.toString());
                         }
+                    });
+                    if (!is.isEmpty()) {
+                        opts.setImports(is);
                     }
                 } else if (key.startsWith(PRAGMA_JEXLNS)) {
-                    if (value instanceof String) {
-                        // jexl.namespace.***
-                        final String nsname = key.substring(PRAGMA_JEXLNS.length());
-                        if (!nsname.isEmpty()) {
-                            if (ns == null) {
-                                ns = new HashMap<>(functions);
-                            }
-                            final String nsclass = value.toString();
-                            final Class<?> clazz = uberspect.getClassByName(nsclass);
-                            if (clazz == null) {
-                                logger.warn(key + ": unable to find class " + nsclass);
-                            } else {
-                                ns.put(nsname, clazz);
-                            }
-                        }
-                    }
+                    if (ns == null)  ns = new LinkedHashMap<>(functions);
+                    processPragmaNamespace(ns, key, value);
+                } else if (key.startsWith(PRAGMA_MODULE)) {
+                    if (ns == null)  ns = new LinkedHashMap<>(functions);
+                    processModulePragma(ns, key, value, script.jexlInfo(), context);
                 }
                 if (processor != null) {
                     processor.processPragma(opts, key, value);
                 }
             }
-            if (ns != null) {
-                opts.setNamespaces(ns);
-            }
-            if (is != null) {
-                opts.setImports(is);
+            opts.setNamespaces(ns);
+        }
+    }
+
+    /**
+     * Utility to deal with single value or set of values.
+     * @param value the value or the set
+     * @param vfunc the consumer of values
+     */
+    private void withValueSet(Object value, Consumer<Object> vfunc) {
+        final Set<?> values = value instanceof Set<?>
+                ? (Set<?>) value
+                : Collections.singleton(value);
+        for (final Object o : values) {
+            vfunc.accept(o);
+        }
+    }
+
+    /**
+     * Processes jexl.namespace.ns pragma.
+     * @param ns the namespace map
+     * @param key the key
+     * @param value the value, ie the class
+     */
+    private void processPragmaNamespace(Map<String, Object> ns, String key, Object value) {
+        if (value instanceof String) {
+            // jexl.namespace.***
+            final String nsname = key.substring(PRAGMA_JEXLNS.length());
+            if (!nsname.isEmpty()) {
+                final String nsclass = value.toString();
+                final Class<?> clazz = uberspect.getClassByName(nsclass);
+                if (clazz == null) {
+                    logger.warn(key + ": unable to find class " + nsclass);
+                } else {
+                    ns.put(nsname, clazz);
+                }
             }
+        } else {
+            logger.warn(key + ": ambiguous declaration " + value);
+        }
+    }
+
+    /**
+     * Processes jexl.module.ns pragma.
+     * <p>If the value is empty, the namespace will be cleared which may be useful to debug and force unload
+     * the object bound to the namespace.</p>
+     * @param ns the namespace map
+     * @param key the key the namespace
+     * @param value the value, ie the expression to evaluate and its result bound to the namespace
+     * @param info the expression info
+     * @param context the value-as-expression evaluation context
+     */
+    private void processModulePragma(Map<String, Object> ns, String key, Object value, JexlInfo info, JexlContext context) {
+        // jexl.module.***
+        final String module = key.substring(PRAGMA_MODULE.length());
+        if (module.isEmpty()) {
+            logger.warn(module + ": invalid module declaration");
+        } else {
+            withValueSet(value, (o)->{
+                if (!(o instanceof CharSequence)) {
+                    logger.warn(module + ": unable to define module from " + value);
+                } else {
+                    final String moduleSrc = o.toString();
+                    final Object functor = context instanceof JexlContext.ModuleProcessor
+                            ? ((JexlContext.ModuleProcessor) context).processModule(this, info, module, moduleSrc)
+                            : createExpression(info, moduleSrc).evaluate(context);
+                    if (functor != null) {
+                        ns.put(module, functor);
+                    } else {
+                        ns.remove(module);
+                    }
+                }
+            });
         }
     }
 
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 00b470a4..e771da58 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -505,6 +505,10 @@ public abstract class JexlParser extends StringParser {
      * The prefix of a namespace pragma.
      */
     public static final String PRAGMA_JEXLNS = "jexl.namespace.";
+    /**
+     * The prefix of a module pragma.
+     */
+    public static final String PRAGMA_MODULE = "jexl.module.";
     /**
      * The import pragma.
      */
diff --git a/src/test/java/org/apache/commons/jexl3/PragmaTest.java b/src/test/java/org/apache/commons/jexl3/PragmaTest.java
index 602465af..40ba6ea8 100644
--- a/src/test/java/org/apache/commons/jexl3/PragmaTest.java
+++ b/src/test/java/org/apache/commons/jexl3/PragmaTest.java
@@ -22,6 +22,9 @@ import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.junit.Assert;
 import org.junit.Test;
@@ -115,6 +118,67 @@ public class PragmaTest extends JexlTestCase {
         }
     }
 
+    public static class ModuleContext extends MapContext implements JexlContext.ModuleProcessor {
+        private final ConcurrentMap<String, Object> modules;
+        private final Map<String, JexlScript> sources;
+        private final AtomicInteger count = new AtomicInteger(0);
+
+        ModuleContext(Map<String, JexlScript> sources , ConcurrentMap<String, Object> modules) {
+            this.sources = sources;
+            this.modules = modules;
+        }
+
+        public Object script(String name) {
+            return sources.get(name);
+        }
+
+        public int getCountCompute() {
+            return count.get();
+        }
+        @Override
+        public Object processModule(JexlEngine engine, JexlInfo info, String name, final String body) {
+            if (body.isEmpty()) {
+                modules.remove(name);
+                return null;
+            }
+            return modules.computeIfAbsent(name, (n) -> {
+                Object module = engine.createExpression(info, body).evaluate(this);
+                if (module instanceof JexlScript) {
+                    module = ((JexlScript) module).execute(this);
+                }
+                count.incrementAndGet();
+                return module;
+            });
+        }
+    }
+
+    @Test public void testPragmaModule() {
+        Map<String, JexlScript> msrcs = new TreeMap<>();
+        msrcs.put("module0", JEXL.createScript("function f42(x) { 42 + x; } function f43(x) { 43 + x; }; { 'f42' : f42, 'f43' : f43 }"));
+        ConcurrentMap<String, Object> modules = new ConcurrentHashMap<>();
+        ModuleContext ctxt = new ModuleContext(msrcs, modules);
+        JexlScript script ;
+        Object result ;
+        script = JEXL.createScript("#pragma jexl.module.m0 \"script('module0')\"\n m0:f42(10);");
+        result = script.execute(ctxt);
+        Assert.assertEquals(52, result);
+        Assert.assertEquals(1, ctxt.getCountCompute());
+        result = script.execute(ctxt);
+        Assert.assertEquals(52, result);
+        Assert.assertEquals(1, ctxt.getCountCompute());
+        script = JEXL.createScript("#pragma jexl.module.m0 \"script('module0')\"\n m0:f43(10);");
+        result = script.execute(ctxt);
+        Assert.assertEquals(53, result);
+        Assert.assertEquals(1, ctxt.getCountCompute());
+        try {
+            script = JEXL.createScript("#pragma jexl.module.m0 ''\n#pragma jexl.module.m0 \"fubar('module0')\"\n m0:f43(10);");
+            result = script.execute(ctxt);
+            Assert.fail("fubar sshoud fail");
+        } catch(JexlException.Method xmethod) {
+            Assert.assertEquals("fubar", xmethod.getMethod());
+        }
+    }
+
 
     public static class StaticSleeper {
         // precludes instantiation