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:19 UTC

[commons-jexl] branch JEXL-392 created (now cc999410)

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

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


      at cc999410 JEXL-392: initial drop;

This branch includes the following new commits:

     new cc999410 JEXL-392: initial drop;

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



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

Posted by he...@apache.org.
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