Subversion Repositories pub

Compare Revisions

Ignore whitespace Rev 48 → Rev 49

/pluggablejs/trunk/src/net/outlyer/plugins/BasePluginObject.java
0,0 → 1,16
package net.outlyer.plugins;
 
import net.outlyer.plugins.sandboxing.SandboxAccessor;
 
/**
* Simplest object to be exported as "plugin"
* @see PluginObject
*/
public class BasePluginObject extends PluginProperties implements SandboxAccessor, Cloneable {
public String name;
public int version = 0;
 
@Override public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
/pluggablejs/trunk/src/net/outlyer/plugins/utils/Input.java
0,0 → 1,34
package net.outlyer.plugins.utils;
 
import java.io.IOException;
import java.io.InputStream;
 
/**
*
*/
public class Input {
 
private final InputStream in;
{
in = System.in;
}
public String readline() {
final StringBuilder sb = new StringBuilder();
 
int c;
try {
do {
c = in.read();
sb.append((char) c);
} while (0 != c && '\n' != c);
}
catch (final IOException e) {
return null;
}
return sb.toString();
}
}
/pluggablejs/trunk/src/net/outlyer/plugins/utils/Output.java
0,0 → 1,32
package net.outlyer.plugins.utils;
 
import java.io.PrintStream;
 
/**
* Provides a simplified wrapper for output
*/
public class Output {
 
private final PrintStream ps;
public Output(final PrintStream ps) {
this.ps = ps;
}
public void println(final String s) {
ps.println(s);
}
 
/**
* Note that varargs can't be used transparently from JS
* @param sfmt
* @param args
*/
public void printf(final String sfmt, final Object [] args) {
ps.printf(sfmt, args);
}
public void printf(final String s) {
ps.printf("%s", s);
}
}
/pluggablejs/trunk/src/net/outlyer/plugins/utils/Hooks.java
0,0 → 1,14
package net.outlyer.plugins.utils;
 
import net.outlyer.plugins.sandboxing.SandboxAccessorImpl;
 
/**
*
*/
public class Hooks extends SandboxAccessorImpl {
// Due to Rhino's magic passing a JS function() is enough
public void atexit(Runnable callback) {
getSandbox().addEndHook(callback);
}
}
/pluggablejs/trunk/src/net/outlyer/plugins/utils/package-info.java
0,0 → 1,10
 
/**
* This package provides some common functionality that might often be
* wanted to be exported as global objects.
* {@see net.outlyer.plugins.sandboxing.PluginEnvironment#exportGlobalObject}.
*/
 
package net.outlyer.plugins.utils;
 
 
/pluggablejs/trunk/src/net/outlyer/plugins/PluginLocator.java
0,0 → 1,319
package net.outlyer.plugins;
 
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import net.outlyer.plugins.sandboxing.PluginEnvironment;
import net.outlyer.plugins.sandboxing.Sandbox;
 
/**
*
*/
public class PluginLocator {
 
private final List<URI> paths;
private final Library library;
private static final FileFilter jsFileFilter;
private boolean scanned = false;
static {
jsFileFilter = new FileFilter() {
public boolean accept(final File pathname) {
return pathname.isFile() && pathname.canRead() &&
pathname.getName().endsWith(".js");
}
};
}
private static class Library {
private final HashMap<String, List<Sandbox>> byType;
private final HashMap<String, Sandbox> byFilename;
private final HashMap<String, Sandbox> byName;
{
byType = new HashMap<String, List<Sandbox>>();
byFilename = new HashMap<String, Sandbox>();
byName = new HashMap<String, Sandbox>();
}
void add(final String fileName, final BasePluginObject bpo, final Sandbox sbox) {
byName.put(bpo.name, sbox);
byFilename.put(fileName, sbox);
if (byType.get(bpo.type) == null) {
byType.put(bpo.type, new LinkedList<Sandbox>());
}
byType.get(bpo.type).add(sbox);
}
void drop() {
byType.clear();
byFilename.clear();
byName.clear();
}
}
{
paths = new LinkedList<URI>();
library = new Library();
}
public PluginLocator(final URI defaultPath) {
if (null == defaultPath) {
throw new IllegalArgumentException("Null path in list of paths");
}
 
if (defaultPath.getScheme().equals("file")) {
final File path = new File(defaultPath.normalize()).getAbsoluteFile();
if (path.exists() && path.isDirectory() && path.canRead()) {
paths.add(path.toURI().normalize());
}
}
else if (defaultPath.getScheme().equals("jar")) {
final URI normalized = defaultPath.normalize();
if (!normalized.getSchemeSpecificPart().endsWith("/")) {
throw new IllegalArgumentException("Jar file entries must" +
" represent directories");
}
// TODO: Check for existence (Class.getResource())
paths.add(normalized);
}
else {
throw new IllegalArgumentException("Only directories or jar directories are accepted");
}
}
public Sandbox getFirst(final String type, final String name, final String fileName) {
if (null == type && null == name && null == fileName) {
throw new IllegalArgumentException("At least one of type, name or fileName must be non-null");
}
if (!scanned) {
throw new IllegalStateException("Can't retrieve before scanning");
}
final Set<Sandbox> sf = new HashSet<Sandbox>(),
sn = new HashSet<Sandbox>(),
st = new HashSet<Sandbox>();
// All keys are optional but if one of them is not null and yields no
// results then getFirst yields no result
if (null != fileName) {
final Sandbox s = library.byFilename.get(fileName);
if (null == s) {
return null;
}
sf.add(s);
}
if (null != name) {
final Sandbox s = library.byName.get(name);
if (null == s) {
return null;
}
sn.add(s);
}
if (null != type) {
final Collection<Sandbox> byType = library.byType.get(type);
if (byType.isEmpty()) {
return null;
}
st.addAll(byType);
}
Set<Sandbox> inters;
 
if (null != fileName) {
inters = sf;
 
if (null != name) {
assert ! sn.isEmpty();
inters.retainAll(sn);
}
if (!inters.isEmpty() && null != type) {
assert ! st.isEmpty();
inters.retainAll(st);
}
}
else if (null != name) {
assert null == fileName && sf.isEmpty();
inters = sn;
if (null != type) {
assert ! st.isEmpty();
sn.retainAll(st);
}
}
else {
assert null != type && ! st.isEmpty();
assert null == name && sn.isEmpty();
assert null == fileName && sf.isEmpty();
 
inters = st;
}
if (inters.isEmpty()) {
return null;
}
 
return inters.iterator().next();
}
public Collection<Sandbox> getAll(final String type, final String name, final String fileName) {
if (null == type && null == name && null == fileName) {
throw new IllegalArgumentException("At least one of type, name or fileName must be non-null");
}
if (!scanned) {
throw new IllegalStateException("Can't retrieve before scanning");
}
final Set<Sandbox> set = new HashSet<Sandbox>();
if (null != fileName) {
final Sandbox s = library.byFilename.get(fileName);
if (null != s) {
set.add(s);
}
}
if (null != name) {
final Sandbox s = library.byName.get(name);
if (null != s) {
set.add(s);
}
}
if (null != type) {
final Collection<Sandbox> sbox = library.byType.get(type);
if (null != sbox) {
set.addAll(sbox);
}
}
return Collections.unmodifiableSet(set);
}
public void scan(final PluginEnvironment pe) {
if (scanned) {
return;
}
 
for (final URI uri : paths) {
if (uri.getScheme().equals("file")) {
final File dir = new File(uri);
checkDirConsistency(dir);
scan(pe, dir);
}
else {
assert uri.getScheme().equals("jar");
scan(pe, uri);
}
}
 
scanned = true;
}
 
/**
* Scan over a directory
* @param dir Directory to scan
*/
private void scan(final PluginEnvironment pe, final File dir) {
assert dir.isDirectory() && dir.canRead();
for (final File candidateFile : dir.listFiles(jsFileFilter)) {
final URI fileURI = candidateFile.toURI();
try {
final Sandbox sb = pe.createSandbox(fileURI);
final BasePluginObject bpo = sb.getPluginObject();
if (bpo.name == null) {
bpo.name = candidateFile.getName();
}
library.add(candidateFile.getName(), bpo, sb);
}
catch (final Exception e) {
continue;
}
}
}
 
/**
* Scan over a directory inside a jar file
* @param jarUri Directory to scan
* @return LIst of plugins in directory <code>jarUri</code>
*/
private void scan(final PluginEnvironment pe, final URI jarUri) {
assert jarUri.getScheme().equals("jar");
 
try {
final JarURLConnection conn = (JarURLConnection) jarUri.toURL().openConnection();
assert null != conn.getEntryName();
final Enumeration<JarEntry> entries = conn.getJarFile().entries();
final String[] jarURIElements =jarUri.getSchemeSpecificPart().split("!");
assert jarURIElements.length == 2;
final String jarFile = jarURIElements[0]; // = file:....jar
final String root = jarURIElements[1]; // = /path
while (entries.hasMoreElements()) {
final JarEntry je = entries.nextElement();
 
final String url = "/"+je.getName();
if (url.startsWith(root) && !je.isDirectory()) {
// Accepted URI
 
try {
final Sandbox sb = pe.createSandbox(jarUri);
 
final BasePluginObject bpo = sb.getPluginObject();
 
final String fileName = new File(jarUri.getPath()).getName();
if (bpo.name == null) {
bpo.name = fileName;
}
library.add(fileName, bpo, sb);
}
catch (final Exception e) {
continue;
}
}
}
}
catch (IOException e) {
throw new IllegalStateException("Error reading jar file");
}
}
 
/**
* Throws an unchecked exception if something that, on construction, was
* a valid directory, is not anymore.
* @param dir Directory to check
* @throws java.lang.IllegalStateException If the <em>directory</em> is
* no longer a directory or can't be accessed or has ceased to exist.
*/
private static void checkDirConsistency(final File dir)
throws IllegalStateException {
if (!dir.exists() || !dir.isDirectory() || !dir.canRead()) {
throw new IllegalStateException("Permissions of " + dir +
" modified while running");
}
}
public Collection<Sandbox> list() {
if (!scanned) {
throw new IllegalStateException("Can't list before scanning");
}
return Collections.unmodifiableCollection(library.byFilename.values());
}
}
/pluggablejs/trunk/src/net/outlyer/plugins/PluginObject.java
0,0 → 1,32
package net.outlyer.plugins;
 
import java.util.LinkedList;
import java.util.List;
 
/**
* Default plugin object, adds a "features" field intended to describe
* the plugin features, and execution() which provides the number of times
* the script has been executed.
*/
public class PluginObject extends BasePluginObject {
public List features;
 
{
features = new LinkedList();
}
public int execution() {
return getSandbox().getExecution();
}
 
@Override public Object clone() throws CloneNotSupportedException {
PluginObject po = (PluginObject) super.clone();
po.features = new LinkedList();
po.features.addAll(this.features);
return po;
}
}
/pluggablejs/trunk/src/net/outlyer/plugins/API.java
0,0 → 1,9
package net.outlyer.plugins;
 
/**
*
*/
public final class API {
public static final int REVISION = 1;
public static final int IMPLEMENTATION = 1;
}
/pluggablejs/trunk/src/net/outlyer/plugins/PluginProperties.java
0,0 → 1,11
package net.outlyer.plugins;
 
import net.outlyer.plugins.sandboxing.SandboxAccessorImpl;
 
/**
*
*/
public class PluginProperties extends SandboxAccessorImpl {
public int apiVersion = 1;
public String type;
}
/pluggablejs/trunk/src/net/outlyer/plugins/sandboxing/PluginEnvironment.java
0,0 → 1,200
package net.outlyer.plugins.sandboxing;
 
import net.outlyer.plugins.PluginException;
import java.io.IOException;
import java.io.LineNumberReader;
import java.lang.reflect.Field;
import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import net.outlyer.plugins.API;
import net.outlyer.plugins.BasePluginObject;
import net.outlyer.plugins.PluginObject;
import net.outlyer.plugins.PluginProperties;
 
/**
* Provides the environment for {@link net.outlyer.plugins.sandboxing.Sandbox}es.
*/
public class PluginEnvironment {
static class UnsupportedAPIException extends PluginException {
public UnsupportedAPIException(int providedApi) {
super("Plugin has unsupported API ("+providedApi+")");
}
}
static class UnsupportedTypeException extends PluginException {
public UnsupportedTypeException(String pluginType) {
super("Plugin has unsupported type ("+pluginType+")");
}
}
private final Map<String, Object> exportedObjects;
private BasePluginObject pluginObject;
private StringBuilder boilerPlate;
private List<String> acceptedTypes = null;
private int maxApi=API.REVISION;
int linesToCheckForSupport = 5;
static final String EXPORTED_SANDBOX_VARIABLE = "$net.outlyer.runtime.sandbox";
{
exportedObjects = new HashMap();
pluginObject = null;
boilerPlate = new StringBuilder();
}
private void enableSandboxAccess(final String name, final SandboxAccessor sa) {
try {
Field f = sa.getClass().getField("_getSandbox");
}
catch (final NoSuchFieldException e) {
throw new IllegalArgumentException("object " + name + " must comply " +
"with the contract of SandboxAccessor");
}
 
final String callback = name + "._getSandbox";
 
boilerPlate.append(callback).append("=function() {")
.append(" return ")
.append(EXPORTED_SANDBOX_VARIABLE).append(";\n};");
 
}
public void exportGlobalObject(String name, Object object) {
exportedObjects.put(name, object);
if (object instanceof SandboxAccessor) {
enableSandboxAccess(name, (SandboxAccessor)object);
}
}
public void setPluginObject(BasePluginObject pObject) {
pluginObject = pObject;
}
public PluginProperties checkForSupport(final URI uri) throws PluginException {
final ScriptEngine rhino = new ScriptEngineManager().getEngineByName("rhino");
 
try {
// Define a Plugin object with a field named type
rhino.eval("var plugin={ type: null, apiVersion: null };");
}
catch (ScriptException e) {
assert false;
throw new IllegalStateException("Unknown error encountered");
}
 
// First of all check the script for support
Object type = null;
Object apiVersion = null;
// Try to eval each line, since it executes in a sandbox this is
// *relatively* safe
String line;
try {
final LineNumberReader script = new LineNumberReader(new PluginReader(uri));
try {
while ((null == type || null == apiVersion) &&
(null != (line = script.readLine())) &&
script.getLineNumber() < linesToCheckForSupport) {
try {
rhino.eval(line);
if (null == type) {
rhino.eval("$_1_$ = plugin.type;");
type = rhino.get("$_1_$");
}
if (null == apiVersion) {
rhino.eval("$_1_$ = plugin.apiVersion;");
apiVersion = rhino.get("$_1_$");
}
}
catch (ScriptException e) {
// Exceptions are to be expected since none of the guaranteed
// plugin tools are provided in this context they'll try to
// access unexisting objects
}
}
}
finally {
script.close();
}
}
catch (final IOException e) {
throw new PluginExecutionException("Failed while reading plugin: " + e.getMessage(), e);
}
 
if (null == type) {
type = "";
}
if (null == apiVersion) {
apiVersion = 0;
}
final PluginProperties pp = new PluginProperties();
 
try {
pp.apiVersion = Double.valueOf(apiVersion.toString()).intValue();
pp.type = type.toString();
}
catch (final NumberFormatException e) {
throw new PluginException("Incorrect value for apiVersion provided, must be integer");
}
if (null != acceptedTypes) {
if (!acceptedTypes.contains(pp.type)) {
throw new UnsupportedTypeException(pp.type);
}
}
if (pp.apiVersion > maxApi) {
throw new UnsupportedAPIException(pp.apiVersion);
}
return pp;
}
 
public Sandbox createSandbox(final URI forUri) throws PluginException {
final ScriptEngine rhino = new ScriptEngineManager().getEngineByName("rhino");
final PluginProperties pp = checkForSupport(forUri);
 
if (null == pluginObject) {
pluginObject = new PluginObject();
}
 
try {
final PluginObject po = (PluginObject) pluginObject.clone();
 
final Bindings bindings = rhino.createBindings();
 
for (final String objectName : exportedObjects.keySet()) {
bindings.put(objectName, exportedObjects.get(objectName));
}
bindings.put("plugin", po);
enableSandboxAccess("plugin", po);
 
return new SandboxImpl(forUri, bindings, boilerPlate.toString());
}
catch (final CloneNotSupportedException e) {
assert false;
throw new IllegalStateException("Incorrect implementation");
}
}
 
public void setAcceptedTypes(final String ... pluginTypes) {
if (null != pluginTypes) {
for (final String type : pluginTypes) {
if (null == type) {
throw new IllegalArgumentException("The set of plugin types" +
" must be either null, or a set of non-null values");
}
}
}
acceptedTypes = Arrays.asList(pluginTypes);
}
}
/pluggablejs/trunk/src/net/outlyer/plugins/sandboxing/SandboxAccessorImpl.java
0,0 → 1,23
package net.outlyer.plugins.sandboxing;
 
import java.util.concurrent.Callable;
 
/**
* {@see SandboxAccessor}
*/
public abstract class SandboxAccessorImpl implements SandboxAccessor {
public Callable<Sandbox> _getSandbox;
protected final Sandbox getSandbox() {
if (null == _getSandbox) {
throw new IllegalStateException(getClass()+" hasn't been correctly initialised");
}
try {
return _getSandbox.call();
}
catch (final Exception e) {
throw new IllegalStateException("getSandbox produced an exception, which is not expected!");
}
}
}
/pluggablejs/trunk/src/net/outlyer/plugins/sandboxing/RuntimeHooks.java
0,0 → 1,11
package net.outlyer.plugins.sandboxing;
 
/**
* Provides hooks to the execution
*/
public interface RuntimeHooks {
/**
* Adds a callback to be executed after the script is finished
*/
void addEndHook(final Runnable r);
}
/pluggablejs/trunk/src/net/outlyer/plugins/sandboxing/SandboxProperties.java
0,0 → 1,9
package net.outlyer.plugins.sandboxing;
 
/**
* Provides access to some properties of a sandbox
* @see Sandbox
*/
public interface SandboxProperties {
public int getExecution();
}
/pluggablejs/trunk/src/net/outlyer/plugins/sandboxing/Sandbox.java
0,0 → 1,46
package net.outlyer.plugins.sandboxing;
 
import net.outlyer.plugins.BasePluginObject;
 
/**
* Public methods provided by the sandbox.
* Note that all methods in this interface imply a call to {@link #execute}.
*/
public interface Sandbox extends RuntimeHooks, SandboxProperties {
 
/**
* @see #createDelayedImplementation(Class, boolean)
*/
<T> T createDelayedImplementation(final Class<T> interfaceClass) throws PluginExecutionException;
 
/**
* Creates an implementation of an interface from JS code
* @param interfaceClass Class of the interface to implement
* @param allowPartialImplementation If true, the implementation would be used
* even if incomplete (note that calling an unimplemented method will produce
* an exception)
* @return The implementation
* @throws net.outlyer.plugins.sandboxing.PluginExecutionException
*/
<T> T createDelayedImplementation(final Class<T> interfaceClass, boolean allowPartialImplementation) throws PluginExecutionException;
 
/**
* @see #createDelayedImplementation(Class, boolean)
* @param fallbackObject If not null incomplete implementations are allowed and
* calls to undefined methods will be passed to this object
*/
<T> T createDelayedImplementation(final Class<T> interfaceClass, final T fallbackObject) throws PluginExecutionException;
<T> T createDelayedImplementation(final Class<T> interfaceClass, final String objectName) throws PluginExecutionException;
 
/**
* Run the script
*/
void execute() throws PluginExecutionException;
 
/**
* Obtains object contauning the plugin object
* @return
*/
BasePluginObject getPluginObject() throws PluginExecutionException;
}
/pluggablejs/trunk/src/net/outlyer/plugins/sandboxing/SandboxImpl.java
0,0 → 1,253
package net.outlyer.plugins.sandboxing;
 
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import net.outlyer.plugins.BasePluginObject;
 
/**
* Implementation of the Sandbox interface
*/
class SandboxImpl implements Sandbox {
private final Bindings bindings;
private final URI pluginUri;
private static int nextUniqueVarNum;
private Map<String, Collection<Runnable>> hooks;
private final String boilerPlate;
private int execution = 0;
private static final String END_KEY = "e";
 
static {
nextUniqueVarNum = (int) (1193 * (1+Math.random()));
}
{
hooks = new HashMap<String, Collection<Runnable>>();
hooks.put(END_KEY, new LinkedList<Runnable>());
}
protected SandboxImpl(final URI plugin, final Bindings bs) {
this(plugin, bs, null);
}
protected SandboxImpl(final URI plugin, final Bindings bs, final String boilerPlate) {
bindings = bs;
pluginUri = plugin;
this.boilerPlate = boilerPlate;
}
public void addEndHook(Runnable r) {
hooks.get(END_KEY).add(r);
}
 
/**
* Namespace for variables used by the enviroment
*/
private static String namespace() {
return "$net.outlyer.runtime";
}
 
/**
* Creates a unique variable name.
*/
private static String uniqueVarName() {
return new StringBuilder("$_").append(nextUniqueVarNum+=1193).append("_$").toString();
}
 
/**
* Creates a unique variable name including namespace
* @see #namespace
* @see #uniqueVarName
*/
private static String uniqueFQVarName() {
return new StringBuilder(namespace()).append(".")
.append(uniqueVarName()).toString();
}
private static ScriptEngine getEngine() {
return new ScriptEngineManager().getEngineByName("rhino");
}
private ScriptEngine execute(boolean appendText, String text, final Map<String,Object> extraBindings)
throws PluginExecutionException {
final ScriptEngine rhino = getEngine();
// 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));
}
}
// 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");
 
try {
rhino.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
 
execution++;
if (null != boilerPlate) {
rhino.eval(boilerPlate);
}
// Create the $net.outlyer.runtime pseudo-namespace...
rhino.eval(runtime.toString());
 
rhino.eval(new PluginReader(pluginUri));
if (appendText) {
rhino.eval(text);
}
// Execute any end hooks
for (final Runnable hook : hooks.get(END_KEY)) {
hook.run();
}
}
catch (final IOException e) {
throw new PluginExecutionException("I/O Error: " + e.getMessage(), e);
}
catch (final ScriptException e) {
throw new PluginExecutionException("Script exception: " + e.getMessage(), e);
}
return rhino;
}
public void execute() throws PluginExecutionException {
execute(false, null, null);
}
 
public <T> T createDelayedImplementation(Class<T> c, String objectName)
throws PluginExecutionException {
final String varImpl = uniqueVarName();
final StringBuilder code = new StringBuilder();
code.append(varImpl).append(" = new ")
.append(c.getCanonicalName()).append("(").append(objectName).append(");");
//System.err.println(code.toString());
final ScriptEngine rhino = execute(true, code.toString(), null);
assert (c.isInstance(rhino.get(varImpl)));
return (T) rhino.get(varImpl);
}
 
public <T> T createDelayedImplementation(Class<T> c) throws PluginExecutionException {
return createDelayedImplementation(c, false);
}
public <T> T createDelayedImplementation(Class<T> c, boolean allowPartial) throws PluginExecutionException {
return createDelayedImplementation(c, null, allowPartial);
}
 
public <T> T createDelayedImplementation(Class<T> interfaceClass, final T fallbackObject) throws PluginExecutionException {
if (null == fallbackObject) {
throw new IllegalArgumentException("Can't use a null fallback object");
}
return createDelayedImplementation(interfaceClass, fallbackObject, true);
}
private <T> T createDelayedImplementation(Class<T> c, T fallback, boolean allowPartial) throws PluginExecutionException {
final StringBuilder hack = new StringBuilder();
 
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");
final StringBuilder script = new StringBuilder();
final String varFallback = uniqueVarName();
for (final Method m : c.getMethods()) {
if (m.getDeclaringClass() != c) {
continue;
}
final String implFuncName = c.getSimpleName()+"_"+m.getName();
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.
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");
}
// script.append("if (!").append(implFuncName).append(") ")
// .append(implFuncName).append("=null;\n");
}
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());
}
//System.err.println(script.toString());
 
final ScriptEngine rhino = execute(true, script.toString(), bn);
 
assert (c.isInstance(rhino.get(varImpl)));
return (T) rhino.get(varImpl);
}
 
public BasePluginObject getPluginObject() throws PluginExecutionException {
execute();
assert bindings.get("plugin") instanceof BasePluginObject;
return (BasePluginObject) bindings.get("plugin");
}
public int getExecution() {
return execution;
}
}
/pluggablejs/trunk/src/net/outlyer/plugins/sandboxing/PluginExecutionException.java
0,0 → 1,25
package net.outlyer.plugins.sandboxing;
 
import net.outlyer.plugins.PluginException;
 
/**
*
*/
public class PluginExecutionException extends PluginException {
 
public PluginExecutionException(Throwable cause) {
super(cause);
}
 
public PluginExecutionException(String message, Throwable cause) {
super(message, cause);
}
 
public PluginExecutionException(String message) {
super(message);
}
 
public PluginExecutionException() {
}
 
}
/pluggablejs/trunk/src/net/outlyer/plugins/sandboxing/PluginReader.java
0,0 → 1,51
package net.outlyer.plugins.sandboxing;
 
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
 
/**
* java.io.Reader for plugins, tries to abstract the plugin location (file or jar)
*/
class PluginReader extends Reader {
 
private final Reader readerImpl;
 
PluginReader(final URI uri) throws IOException {
super();
assert null != uri;
try {
if (uri.getScheme().equals("file")) {
readerImpl = new FileReader(new File(uri));
}
else {
final String path = uri.getSchemeSpecificPart().split("!")[1];
final InputStream is = getClass().getResourceAsStream(path);
if (null == is) {
throw new IOException("Failed to get resource for " + uri);
}
readerImpl = new InputStreamReader(is);
}
}
catch (final FileNotFoundException e) {
throw new IOException("Can\'t read input " + uri);
}
}
 
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
return readerImpl.read(cbuf, off, len);
}
 
@Override
public void close() throws IOException {
readerImpl.close();
}
}
/pluggablejs/trunk/src/net/outlyer/plugins/sandboxing/SandboxAccessor.java
0,0 → 1,17
package net.outlyer.plugins.sandboxing;
 
/**
* Optional interface for exported objects (see
* {@link net.outlyer.plugins.sandboxing.PluginEnvironment#exportGlobalObject}),
* it enables exported objects to retrieve the sandbox object in which they're
* being executed.
*
* Implementors must contain a field exactly:
*
* public Callable<Sandbox> _getSandbox = null;
*
* The convenience default implementation, {@link SandboxAccessorImpl} can
* be used as a base class if that's possible, to hide such requirement.
*/
public interface SandboxAccessor {
}
/pluggablejs/trunk/src/net/outlyer/plugins/PluginException.java
0,0 → 1,23
package net.outlyer.plugins;
 
/**
*
*/
public class PluginException extends Exception {
 
public PluginException(Throwable cause) {
super(cause);
}
 
public PluginException(String message, Throwable cause) {
super(message, cause);
}
 
public PluginException(String message) {
super(message);
}
 
public PluginException() {
}
 
}