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