From 704a7e16160b795ad993470c796590a08930abaa Mon Sep 17 00:00:00 2001 From: Rahul Teke Date: Thu, 5 Jan 2017 15:31:18 +0530 Subject: [PATCH 1/2] added support for jsr233 script engine --- .../org/mvel2/jsr223/MvelCompiledScript.java | 32 ++++++ .../org/mvel2/jsr223/MvelScriptEngine.java | 89 +++++++++++++++ .../mvel2/jsr223/MvelScriptEngineFactory.java | 103 ++++++++++++++++++ .../services/javax.script.ScriptEngineFactory | 1 + .../mvel2/jsr223/MvelScriptEngineTest.java | 47 ++++++++ 5 files changed, 272 insertions(+) create mode 100644 src/main/java/org/mvel2/jsr223/MvelCompiledScript.java create mode 100644 src/main/java/org/mvel2/jsr223/MvelScriptEngine.java create mode 100644 src/main/java/org/mvel2/jsr223/MvelScriptEngineFactory.java create mode 100644 src/main/resources/META-INF/services/javax.script.ScriptEngineFactory create mode 100644 src/test/java/org/mvel2/jsr223/MvelScriptEngineTest.java diff --git a/src/main/java/org/mvel2/jsr223/MvelCompiledScript.java b/src/main/java/org/mvel2/jsr223/MvelCompiledScript.java new file mode 100644 index 000000000..4c4331e55 --- /dev/null +++ b/src/main/java/org/mvel2/jsr223/MvelCompiledScript.java @@ -0,0 +1,32 @@ +package org.mvel2.jsr223; + +import java.io.Serializable; + +import javax.script.CompiledScript; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptException; + +/** + * Created by rahult on 4/1/17. + */ +public class MvelCompiledScript extends CompiledScript { + + private final MvelScriptEngine scriptEngine; + private final Serializable compiledScript; + + public MvelCompiledScript(MvelScriptEngine engine, Serializable compiledScript) { + this.scriptEngine = engine; + this.compiledScript = compiledScript; + } + + @Override + public Object eval(ScriptContext context) throws ScriptException { + return scriptEngine.evaluate(compiledScript, context); + } + + @Override + public ScriptEngine getEngine() { + return scriptEngine; + } +} diff --git a/src/main/java/org/mvel2/jsr223/MvelScriptEngine.java b/src/main/java/org/mvel2/jsr223/MvelScriptEngine.java new file mode 100644 index 000000000..0b65723cc --- /dev/null +++ b/src/main/java/org/mvel2/jsr223/MvelScriptEngine.java @@ -0,0 +1,89 @@ +package org.mvel2.jsr223; + +import java.io.IOException; +import java.io.Reader; +import java.io.Serializable; + +import javax.script.*; + +import org.mvel2.MVEL; + +/** + * Created by rahult on 4/7/16. + */ +public class MvelScriptEngine extends AbstractScriptEngine implements ScriptEngine, Compilable { + + private volatile MvelScriptEngineFactory factory; + + @Override + public Object eval(String script, ScriptContext context) throws ScriptException { + Serializable expression = compiledScript(script); + return evaluate(expression, context); + } + + @Override + public Object eval(Reader reader, ScriptContext context) throws ScriptException { + return this.eval(readFully(reader), context); + } + + @Override + public Bindings createBindings() { + return new SimpleBindings(); + } + + @Override + public ScriptEngineFactory getFactory() { + if (this.factory == null) { + synchronized (this) { + if (this.factory == null) { + this.factory = new MvelScriptEngineFactory(); + } + } + } + + return this.factory; + } + + private static String readFully(Reader reader) throws ScriptException { + char[] arr = new char[8192]; + StringBuilder buf = new StringBuilder(); + + int numChars; + try { + while ((numChars = reader.read(arr, 0, arr.length)) > 0) { + buf.append(arr, 0, numChars); + } + } catch (IOException var5) { + throw new ScriptException(var5); + } + + return buf.toString(); + } + + @Override + public CompiledScript compile(String script) throws ScriptException { + return new MvelCompiledScript(this, compiledScript(script)); + } + + @Override + public CompiledScript compile(Reader reader) throws ScriptException { + return this.compile(readFully(reader)); + } + + public Serializable compiledScript(String script) throws ScriptException { + try { + Serializable expression = MVEL.compileExpression(script); + return expression; + } catch (Exception e) { + throw new ScriptException(e); + } + } + + public Object evaluate(Serializable expression, ScriptContext context) throws ScriptException { + try { + return MVEL.executeExpression(expression, context.getBindings(ScriptContext.ENGINE_SCOPE)); + } catch (Exception e) { + throw new ScriptException(e); + } + } +} diff --git a/src/main/java/org/mvel2/jsr223/MvelScriptEngineFactory.java b/src/main/java/org/mvel2/jsr223/MvelScriptEngineFactory.java new file mode 100644 index 000000000..e3e144364 --- /dev/null +++ b/src/main/java/org/mvel2/jsr223/MvelScriptEngineFactory.java @@ -0,0 +1,103 @@ +package org.mvel2.jsr223; + +import java.util.ArrayList; +import java.util.List; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineFactory; + +import org.mvel2.MVEL; + +/** + * Created by rahult on 4/7/16. + */ +public class MvelScriptEngineFactory implements ScriptEngineFactory { + private static final String ENGINE_NAME = MVEL.NAME; + private static final String ENGINE_VERSION = MVEL.VERSION; + private static final String LANGUAGE_NAME = "mvel"; + private static final String LANGUAGE_VERSION = MVEL.VERSION; + + private static final List NAMES = new ArrayList(); + private static final List EXTENSIONS = new ArrayList(); + private static final List MIME_TYPES = new ArrayList(); + + private static final MvelScriptEngine MVEL_SCRIPT_ENGINE = new MvelScriptEngine(); + + public MvelScriptEngineFactory() { + NAMES.add(LANGUAGE_NAME); + } + + @Override + public String getEngineName() { + return ENGINE_NAME; + } + + @Override + public String getEngineVersion() { + return ENGINE_VERSION; + } + + @Override + public List getExtensions() { + return EXTENSIONS; + } + + @Override + public List getMimeTypes() { + return MIME_TYPES; + } + + @Override + public List getNames() { + return NAMES; + } + + @Override + public String getLanguageName() { + return LANGUAGE_NAME; + } + + @Override + public String getLanguageVersion() { + return LANGUAGE_VERSION; + } + + @Override + public Object getParameter(String key) { + if (key.equals(ScriptEngine.NAME)) { + return getLanguageName(); + } else if (key.equals(ScriptEngine.ENGINE)) { + return getEngineName(); + } else if (key.equals(ScriptEngine.ENGINE_VERSION)) { + return getEngineVersion(); + } else if (key.equals(ScriptEngine.LANGUAGE)) { + return getLanguageName(); + } else if (key.equals(ScriptEngine.LANGUAGE_VERSION)) { + return getLanguageVersion(); + } else if (key.equals("THREADING")) { + return "THREAD-ISOLATED"; + } else { + return null; + } + } + + @Override + public String getMethodCallSyntax(String obj, String m, String... args) { + return null; + } + + @Override + public String getOutputStatement(String toDisplay) { + return null; + } + + @Override + public String getProgram(String... statements) { + return null; + } + + @Override + public ScriptEngine getScriptEngine() { + return MVEL_SCRIPT_ENGINE; + } +} diff --git a/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory b/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory new file mode 100644 index 000000000..4df975da6 --- /dev/null +++ b/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory @@ -0,0 +1 @@ +org.mvel2.jsr223.MvelScriptEngineFactory \ No newline at end of file diff --git a/src/test/java/org/mvel2/jsr223/MvelScriptEngineTest.java b/src/test/java/org/mvel2/jsr223/MvelScriptEngineTest.java new file mode 100644 index 000000000..d430076da --- /dev/null +++ b/src/test/java/org/mvel2/jsr223/MvelScriptEngineTest.java @@ -0,0 +1,47 @@ +package org.mvel2.jsr223; + +import org.junit.Test; +import org.mvel2.MVEL; + +import javax.script.*; + +import static org.junit.Assert.*; + +/** + * Created by rahult on 4/1/17. + */ +public class MvelScriptEngineTest { + + @Test + public void testScriptEngine() throws ScriptException { + ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); + ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("mvel"); + + SimpleBindings simpleBindings = new SimpleBindings(); + simpleBindings.put("a", 1); + simpleBindings.put("b", 2); + + int c = (Integer) scriptEngine.eval("a + b", simpleBindings); + assertEquals(c, 3); + + } + + @Test + public void testScriptEngineCompiledScript() throws ScriptException { + ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); + ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("mvel"); + + SimpleBindings simpleBindings = new SimpleBindings(); + simpleBindings.put("a", 1); + simpleBindings.put("b", 2); + + if (scriptEngine instanceof Compilable) { + Compilable compilableScriptEngine = (Compilable) scriptEngine; + CompiledScript compiledScript = compilableScriptEngine.compile("a+ b"); + int c = (Integer) compiledScript.eval(simpleBindings); + assertEquals(c, 3); + } + + } + +} \ No newline at end of file From 432d20669494fbf6e59018016725c7424fc6d4bd Mon Sep 17 00:00:00 2001 From: Rahul Teke Date: Mon, 9 Jan 2017 16:40:08 +0530 Subject: [PATCH 2/2] customized bindings use instead of simple bindings --- .../java/org/mvel2/jsr223/MvelBindings.java | 173 ++++++++++++++++++ .../org/mvel2/jsr223/MvelScriptEngine.java | 2 +- 2 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/mvel2/jsr223/MvelBindings.java diff --git a/src/main/java/org/mvel2/jsr223/MvelBindings.java b/src/main/java/org/mvel2/jsr223/MvelBindings.java new file mode 100644 index 000000000..88b3c6d2a --- /dev/null +++ b/src/main/java/org/mvel2/jsr223/MvelBindings.java @@ -0,0 +1,173 @@ +package org.mvel2.jsr223; + +import javax.script.Bindings; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Created by rahult on 9/1/17. + */ +public class MvelBindings implements Bindings { + + /** + * The Map field stores the attributes. + */ + private Map map; + + /** + * Constructor uses an existing Map to store the values. + * + * @param m The Map backing this SimpleBindings. + * @throws NullPointerException if m is null + */ + public MvelBindings(Map m) { + if (m == null) { + throw new NullPointerException(); + } + this.map = m; + } + + /** + * Default constructor uses a HashMap. + */ + public MvelBindings() { + this(new HashMap()); + } + + /** + * Sets the specified key/value in the underlying map field. + * + * @param name Name of value + * @param value Value to set. + * + * @return Previous value for the specified key. Returns null if key was previously unset. + * + * @throws NullPointerException if the name is null. + * @throws IllegalArgumentException if the name is empty. + */ + public Object put(String name, Object value) { + return map.put(name, value); + } + + /** + * putAll is implemented using Map.putAll. + * + * @param toMerge The Map of values to add. + * + * @throws NullPointerException if toMerge map is null or if some key in the map is null. + * @throws IllegalArgumentException if some key in the map is an empty String. + */ + public void putAll(Map toMerge) { + if (toMerge == null) { + throw new NullPointerException("toMerge map is null"); + } + for (Map.Entry entry : toMerge.entrySet()) { + String key = entry.getKey(); + put(key, entry.getValue()); + } + } + + /** {@inheritDoc} */ + public void clear() { + map.clear(); + } + + /** + * Returns true if this map contains a mapping for the specified key. More formally, + * returns true if and only if this map contains a mapping for a key k such + * that (key==null ? k==null : key.equals(k)). (There can be at most one such mapping.) + * + * @param key key whose presence in this map is to be tested. + * @return true if this map contains a mapping for the specified key. + * + * @throws NullPointerException if key is null + * @throws ClassCastException if key is not String + * @throws IllegalArgumentException if key is empty String + */ + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + /** {@inheritDoc} */ + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + /** {@inheritDoc} */ + public Set> entrySet() { + return map.entrySet(); + } + + /** + * Returns the value to which this map maps the specified key. Returns null if the map + * contains no mapping for this key. A return value of null does not necessarily + * indicate that the map contains no mapping for the key; it's also possible that the map + * explicitly maps the key to null. The containsKey operation may be used to + * distinguish these two cases. + * + *

+ * More formally, if this map contains a mapping from a key k to a value v + * such that (key==null ? k==null : + * key.equals(k)), then this method returns v; otherwise it returns null. + * (There can be at most one such mapping.) + * + * @param key key whose associated value is to be returned. + * @return the value to which this map maps the specified key, or null if the map + * contains no mapping for this key. + * + * @throws NullPointerException if key is null + * @throws ClassCastException if key is not String + * @throws IllegalArgumentException if key is empty String + */ + public Object get(Object key) { + return map.get(key); + } + + /** {@inheritDoc} */ + public boolean isEmpty() { + return map.isEmpty(); + } + + /** {@inheritDoc} */ + public Set keySet() { + return map.keySet(); + } + + /** + * Removes the mapping for this key from this map if it is present (optional operation). More + * formally, if this map contains a mapping from key k to value v such that + * (key==null ? k==null : key.equals(k)), that mapping is removed. (The map can + * contain at most one such mapping.) + * + *

+ * Returns the value to which the map previously associated the key, or null if the map + * contained no mapping for this key. (A null return can also indicate that the map + * previously associated null with the specified key if the implementation supports + * null values.) The map will not contain a mapping for the specified key once the call + * returns. + * + * @param key key whose mapping is to be removed from the map. + * @return previous value associated with specified key, or null if there was no + * mapping for key. + * + * @throws NullPointerException if key is null + * @throws ClassCastException if key is not String + * @throws IllegalArgumentException if key is empty String + */ + public Object remove(Object key) { + return map.remove(key); + } + + /** {@inheritDoc} */ + public int size() { + return map.size(); + } + + /** {@inheritDoc} */ + public Collection values() { + return map.values(); + } + +} diff --git a/src/main/java/org/mvel2/jsr223/MvelScriptEngine.java b/src/main/java/org/mvel2/jsr223/MvelScriptEngine.java index 0b65723cc..9c3d117dd 100644 --- a/src/main/java/org/mvel2/jsr223/MvelScriptEngine.java +++ b/src/main/java/org/mvel2/jsr223/MvelScriptEngine.java @@ -28,7 +28,7 @@ public Object eval(Reader reader, ScriptContext context) throws ScriptException @Override public Bindings createBindings() { - return new SimpleBindings(); + return new MvelBindings(); } @Override