26,6 → 26,9 |
|
// $Id$ |
|
// Related reference: |
// http://java.sun.com/developer/technicalArticles/J2SE/Desktop/scripting/ |
|
import java.io.IOException; |
import java.lang.reflect.Method; |
import java.net.URI; |
50,11 → 53,24 |
private final String boilerPlate; |
private int execution = 0; |
private final PluginProperties properties; |
|
private transient ScriptEngine currentEngine = null; |
private static final String END_KEY = "e"; |
private static final boolean debug; |
private final PluginEnvironment pE; |
|
private static class ExecResult { |
final ScriptEngine rhino; |
final Object evalResult; |
|
ExecResult(final ScriptEngine rhino_, final Object evalResult_) { |
rhino = rhino_; |
evalResult = evalResult_; |
} |
} |
|
static { |
nextUniqueVarNum = (int) (1193 * (1+Math.random())); |
debug = System.getProperty("debug", "0").equals("1"); |
} |
|
{ |
62,16 → 78,19 |
hooks.put(END_KEY, new LinkedList<Runnable>()); |
} |
|
protected SandboxImpl(final URI plugin, final Bindings bs, final PluginProperties pp) { |
this(plugin, bs, pp, null); |
protected SandboxImpl(final PluginEnvironment pe, final URI plugin, |
final Bindings bs, final PluginProperties pp) { |
this(pe, plugin, bs, pp, null); |
} |
|
protected SandboxImpl(final URI plugin, final Bindings bs, |
protected SandboxImpl(final PluginEnvironment pe, final URI plugin, |
final Bindings bs, |
final PluginProperties pp, final String boilerPlate) { |
bindings = bs; |
pluginUri = plugin; |
pluginUri = plugin; // if null => sandbox without file |
this.boilerPlate = boilerPlate; |
properties = pp; |
pE = pe; |
} |
|
public void addEndHook(Runnable r) { |
82,7 → 101,7 |
* Namespace for variables used by the enviroment |
*/ |
private static String namespace() { |
return "$net.outlyer.runtime"; |
return "$net.outlyer.runtime.internal"; |
} |
|
/** |
102,42 → 121,32 |
.append(uniqueVarName()).toString(); |
} |
|
/** |
* Gets an engine. |
* If the system property "debug" is set to "1" the returned engine |
* will dump extra information to the console |
* @return New engine |
*/ |
private static ScriptEngine getEngine() { |
return new ScriptEngineManager().getEngineByName("rhino"); |
final ScriptEngine se = new ScriptEngineManager().getEngineByName("rhino"); |
if (debug) { |
return new EngineSink(se); |
} |
return se; |
} |
|
private ScriptEngine execute(final String prependText, |
private ExecResult execute(final String prependText, |
final String appendText, |
final Map<String,Object> extraBindings) |
throws PluginExecutionException { |
final ScriptEngine rhino = getEngine(); |
currentEngine = rhino; |
|
// First, export in the global namespace the reference to this |
// sandbox. This variable isn't meant to be used directly, it's only |
// set to refer to it from the boilerplate code (AFAIK there's no |
// way to add a binding with a FQ name). |
// TODO: Can a fully qualified name be bound directly? |
final String globalSanboxVarName = uniqueVarName(); |
bindings.put(globalSanboxVarName, this); |
if (null != extraBindings) { |
for (final String key : extraBindings.keySet()) { |
bindings.put(key, extraBindings.get(key)); |
bindings.putAll(extraBindings); |
} |
} |
|
// Will contain some biolerplate code used to provide access |
// to the runtime wrapped-environment |
final StringBuilder runtime = new StringBuilder(); |
// $net.outlyer.runtime.sandbox Contains a reference to this object |
// Implementation note: PluginEnvironment.EXPORTED_SANDBOX_VARIABLE should have the same name |
// TODO: ? $net.outlyer.runtime.pluginEnvironment |
runtime.append("var $net = {\n") |
.append(" outlyer : {\n") |
.append(" runtime : {\n") |
.append(" sandbox : ").append(globalSanboxVarName).append("\n") |
.append(" }\n") |
.append(" }\n") |
.append("};\n"); |
Object result = null; |
|
try { |
rhino.setBindings(bindings, ScriptContext.ENGINE_SCOPE); |
148,14 → 157,26 |
rhino.eval(boilerPlate); |
} |
|
// Create the $net.outlyer.runtime pseudo-namespace... |
rhino.eval(runtime.toString()); |
// $net.outlyer.runtime.sandbox Contains a reference to this object |
// Implementation note: PluginEnvironment.EXPORTED_SANDBOX_VARIABLE |
// should have the same name |
final NamespaceContainer.$Net $net = new NamespaceContainer.$Net(pE, this); |
assert this == $net.outlyer.runtime.sandbox; |
rhino.put("$net", $net); |
// ....internal can be used to store random internal data, it's |
// a dynamic object so that fields can be created as needed |
rhino.eval("$net.outlyer.runtime.internal={};"); |
|
if (null != prependText) { |
rhino.eval(prependText); |
} |
|
rhino.eval(new PluginReader(pluginUri)); |
if (null != pluginUri) { |
result = rhino.eval(new PluginReader(pluginUri)); |
} |
else { |
result = null; |
} |
|
if (null != appendText) { |
rhino.eval(appendText); |
172,8 → 193,11 |
catch (final ScriptException e) { |
throw new PluginExecutionException("Script exception: " + e.getMessage(), e); |
} |
finally { |
currentEngine = null; |
} |
|
return rhino; |
return new ExecResult(rhino, result); |
} |
|
public void execute() throws PluginExecutionException { |
180,16 → 204,31 |
execute(null, null, null); |
} |
|
ScriptEngine executeAndGetEngine() throws PluginExecutionException { |
return execute(null, null, null).rhino; |
} |
|
public <T> T createDelayedImplementation(final Class<T> c, |
final String objectName) |
throws PluginExecutionException { |
final String varImpl = uniqueVarName(); |
|
if (null == c) { |
throw new IllegalArgumentException("Can't instantiate null"); |
} |
if (!c.isInterface()) { |
throw new IllegalArgumentException("Can only create delayed " + |
"implementations for interfaces"); |
} |
if (null == objectName) { |
throw new IllegalArgumentException("Implementor's name can't be null"); |
} |
final StringBuilder preCode = new StringBuilder(); |
|
if (debug) { |
preCode.append("// Forward declaration for delayed implementation:\n"); |
} |
preCode.append("var ").append(objectName).append(" = {\n"); |
for (final Method method : c.getMethods()) { |
if (method.getDeclaringClass() != c) { |
if (method.getDeclaringClass() == Object.class) { // Implement everything, including hierarchies |
continue; |
} |
preCode.append(method.getName()).append(": null,\n"); |
196,6 → 235,16 |
} |
preCode.append("};\n"); |
|
final ScriptEngine rhino = execute(preCode.toString(), null, null).rhino; |
|
try { |
final StringBuilder retriever = new StringBuilder(); |
retriever.append("new ") |
.append(c.getCanonicalName()).append("(").append(objectName).append(");"); |
if (debug) { |
retriever.append("// <== Actual delayed implementation creation"); |
} |
|
// Could be safely instantiated before actually being assigned IF |
// plugins were required to define their methods with |
// objectName.methodName = function() {} ... |
202,98 → 251,88 |
// but would file with |
// objectName = { methodName: function() {}, ... } |
// Creating the instance as postCode does the job for both |
final StringBuilder postCode = new StringBuilder(); |
postCode.append(varImpl).append(" = new ") |
.append(c.getCanonicalName()).append("(").append(objectName).append(");"); |
|
final ScriptEngine rhino = execute(preCode.toString(), postCode.toString(), null); |
|
assert (c.isInstance(rhino.get(varImpl))); |
return (T) rhino.get(varImpl); |
try { |
currentEngine = rhino; |
// eval() returns the value of the last evaluated line/command |
assert (c.isInstance(rhino.eval(retriever.toString()))); |
return (T) rhino.eval(retriever.toString()); |
} |
|
public <T> T createDelayedImplementation(final Class<T> c) |
throws PluginExecutionException { |
return createDelayedImplementation(c, false); |
finally { |
currentEngine = null; |
} |
|
public <T> T createDelayedImplementation(final Class<T> c, boolean allowPartial) |
throws PluginExecutionException { |
return createDelayedImplementation(c, null, allowPartial); |
} |
catch (final ScriptException e) { |
throw new PluginExecutionException("Failed to create the delayed " + |
"implementation instance: " + e.getMessage(), e); |
} |
} |
|
public <T> T createDelayedImplementation(final Class<T> interfaceClass, |
final T fallbackObject) |
/** |
* {@inheritDoc} |
*/ |
public <T> T createDelayedImplementation(final Class<T> c, |
final Map<Method, String> map) |
throws PluginExecutionException { |
if (null == fallbackObject) { |
throw new IllegalArgumentException("Can't use a null fallback object"); |
if (null == c) { |
throw new IllegalArgumentException("Can't instantiate null"); |
} |
return createDelayedImplementation(interfaceClass, fallbackObject, true); |
if (!c.isInterface()) { |
throw new IllegalArgumentException("Can only create delayed " + |
"implementations for interfaces"); |
} |
if (null == map || map.isEmpty()) { |
throw new IllegalArgumentException("No mappings; creating a delayed " + |
"implementation with no mappings " + |
"is equivalent to creating" + |
"the object from Java"); |
} |
|
private <T> T createDelayedImplementation(final Class<T> c, |
final T fallback, |
boolean allowPartial) |
throws PluginExecutionException { |
final StringBuilder hack = new StringBuilder(); |
final StringBuffer preCode = new StringBuffer(); |
|
final String var = uniqueFQVarName(); |
// Apparently only global namespace objects can be retrieved with get() |
// so to simplify retrieval a global variable is used |
final String varImpl = uniqueVarName(); |
|
hack.append(var).append("={\n"); |
preCode.append(var).append(" = {\n"); |
|
final StringBuilder script = new StringBuilder(); |
for (final Method method : map.keySet()) { |
preCode.append(method.getName()).append(": ") |
.append(map.get(method)) |
.append("\n,\n"); |
} |
|
final String varFallback = uniqueVarName(); |
preCode.append("};\n"); |
|
for (final Method m : c.getMethods()) { |
if (m.getDeclaringClass() != c) { |
continue; |
final ScriptEngine rhino = execute(preCode.toString(), null, null).rhino; |
|
try { |
final StringBuilder retriever = new StringBuilder(); |
retriever.append("new ") |
.append(c.getCanonicalName()).append("(").append(var).append(");"); |
if (debug) { |
retriever.append("// <== Actual delayed implementation creation"); |
} |
|
final String implFuncName = c.getSimpleName()+"_"+m.getName(); |
// Could be safely instantiated before actually being assigned IF |
// plugins were required to define their methods with |
// objectName.methodName = function() {} ... |
// but would file with |
// objectName = { methodName: function() {}, ... } |
// Creating the instance as postCode does the job for both |
|
if (allowPartial) { |
// This makes undefined functions to be mapped to undefined |
// variables (otherwise the interface instantation would fail) |
// Apparently defining a var with the same name as a function |
// produces no error, while checking for an undefined |
// variable/method does fail. |
// FIXME: Might be cleaner by handling "undefined" |
script.append("var ").append(implFuncName).append(";\n"); |
|
if (null != fallback) { |
script.append("if (!").append(implFuncName).append(")\n\t") |
.append(implFuncName).append("=") |
.append(varFallback).append(".").append(m.getName()) |
.append("\n"); |
try { |
currentEngine = rhino; |
// eval() returns the value of the last evaluated line/command |
assert (c.isInstance(rhino.eval(retriever.toString()))); |
return (T) rhino.eval(retriever.toString()); |
} |
// script.append("if (!").append(implFuncName).append(") ") |
// .append(implFuncName).append("=null;\n"); |
finally { |
currentEngine = null; |
} |
|
hack.append("\t").append(m.getName()).append(" : ").append(implFuncName).append(",\n"); |
} |
|
hack.append("};\nvar ") |
.append(varImpl).append("=new ") |
.append(c.getCanonicalName()).append("(").append(var).append(");"); |
|
script.append(hack.toString()); |
|
final HashMap<String, Object> bn = new HashMap(); |
bn.put(varFallback, fallback); |
|
if (null != fallback) { |
script.append("\n// " + bn.get(varFallback).getClass()); |
catch (final ScriptException e) { |
throw new PluginExecutionException("Failed to create the delayed " + |
"implementation instance: " + e.getMessage(), e); |
} |
//System.err.println(script.toString()); |
|
final ScriptEngine rhino = execute(null, script.toString(), bn); |
assert (c.isInstance(rhino.get(varImpl))); |
return (T) rhino.get(varImpl); |
} |
|
public BasePluginObject getPluginObject() throws PluginExecutionException { |
324,5 → 363,7 |
return pluginUri.normalize(); // FIXME: Return a copy |
} |
|
|
public ScriptEngine getCurrentEngine() { |
return currentEngine; |
} |
} |