package bsh; import javax.script.AbstractScriptEngine; import javax.script.Bindings; import javax.script.Compilable; import javax.script.CompiledScript; import javax.script.Invocable; import javax.script.ScriptContext; import javax.script.ScriptEngine; import javax.script.ScriptEngineFactory; import javax.script.ScriptException; import javax.script.SimpleBindings; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.io.Reader; import java.io.Writer; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /* Adopted from http://ikayzo.org/svn/beanshell/BeanShell/engine/src/bsh/engine/BshScriptEngine.java Notes This engine supports open-ended pluggable scriptcontexts */ public class BshScriptEngine extends AbstractScriptEngine implements Compilable, Invocable { // The BeanShell global namespace for the interpreter is stored in the // engine scope map under this key. static final String engineNameSpaceKey = "org_beanshell_engine_namespace"; private BshScriptEngineFactory factory; private bsh.Interpreter interpreter; public BshScriptEngine() { this(null); } public BshScriptEngine(BshScriptEngineFactory factory) { this.factory = factory; getInterpreter(); // go ahead and prime the interpreter now } protected Interpreter getInterpreter() { if (interpreter == null) { this.interpreter = new bsh.Interpreter(); interpreter.setNameSpace(null); // should always be set by context } return interpreter; } public Object eval(String script, ScriptContext scriptContext) throws ScriptException { return evalSource(script, scriptContext); } public Object eval(Reader reader, ScriptContext scriptContext) throws ScriptException { return evalSource(reader, scriptContext); } /* This is the primary implementation method. We respect the String/Reader difference here in BeanShell because BeanShell will do a few extra things in the string case... e.g. tack on a trailing ";" semicolon if necessary. */ private Object evalSource(Object source, ScriptContext scriptContext) throws ScriptException { bsh.NameSpace contextNameSpace = getEngineNameSpace(scriptContext); Interpreter bsh = getInterpreter(); bsh.setNameSpace(contextNameSpace); bsh.setOut(toPrintStream(scriptContext.getWriter())); bsh.setErr(toPrintStream(scriptContext.getErrorWriter())); try { if (source instanceof Reader) { return bsh.eval((Reader) source); } else { return bsh.eval((String) source); } } catch (ParseException e) { // explicit parsing error throw new ScriptException(e.toString(), e.getErrorSourceFile(), e.getErrorLineNumber()); } catch (TargetError e) { // The script threw an application level exception // set it as the cause ? ScriptException se = new ScriptException(e.toString(), e.getErrorSourceFile(), e.getErrorLineNumber()); se.initCause(e.getTarget()); throw se; } catch (EvalError e) { // The script couldn't be evaluated properly throw new ScriptException(e.toString(), e.getErrorSourceFile(), e.getErrorLineNumber()); } catch (InterpreterError e) { // The interpreter had a fatal problem throw new ScriptException(e.toString()); } } private PrintStream toPrintStream(final Writer writer) { // This is a big hack, convert writer to PrintStream return new PrintStream(new WriterOutputStream(writer)); } /* Check the context for an existing global namespace embedded in the script context engine scope. If none exists, ininitialize the context with one. */ private static NameSpace getEngineNameSpace(ScriptContext scriptContext) { NameSpace ns = (NameSpace) scriptContext.getAttribute(engineNameSpaceKey, ScriptContext.ENGINE_SCOPE); if (ns == null) { // Create a global namespace for the interpreter Map<String, Object> engineView = new ScriptContextEngineView(scriptContext); ns = new ExternalNameSpace(null/*parent*/, "javax_script_context", engineView); scriptContext.setAttribute(engineNameSpaceKey, ns, ScriptContext.ENGINE_SCOPE); } return ns; } public Bindings createBindings() { return new SimpleBindings(); } public ScriptEngineFactory getFactory() { if (factory == null) { factory = new BshScriptEngineFactory(); } return factory; } /** * Compiles the script (source represented as a {@code String}) for later * execution. * * @param script The source of the script, represented as a {@code String}. * @return An subclass of {@code CompiledScript} to be executed later * using one of the {@code eval} methods of {@code CompiledScript}. * @throws ScriptException if compilation fails. * @throws NullPointerException if the argument is null. */ public CompiledScript compile(String script) throws ScriptException { try { final PreparsedScript preparsed = new PreparsedScript(script); return new CompiledScript() { @Override public Object eval(ScriptContext context) throws ScriptException { final HashMap<String, Object> map = new HashMap<String, Object>(); final List<Integer> scopes = new ArrayList<Integer>(context.getScopes()); Collections.sort(scopes); // lowest scope at first pos Collections.reverse(scopes); // highest scope at first pos for (final Integer scope : scopes) { map.putAll(context.getBindings(scope)); } preparsed.setOut(toPrintStream(context.getWriter())); preparsed.setErr(toPrintStream(context.getErrorWriter())); try { return preparsed.invoke(map); } catch (final EvalError e) { throw constructScriptException(e); } } @Override public ScriptEngine getEngine() { return BshScriptEngine.this; } }; } catch (final EvalError e) { throw constructScriptException(e); } } private ScriptException constructScriptException(final EvalError e) { return new ScriptException(e.getMessage(), e.getErrorSourceFile(), e.getErrorLineNumber()); } private static String convertToString(Reader reader) throws IOException { final StringBuffer buffer = new StringBuffer(64); char[] cb = new char[64]; int len; while ((len = reader.read(cb)) != -1) { buffer.append(cb, 0, len); } return buffer.toString(); } /** * Compiles the script (source read from {@code Reader}) for later * execution. Functionality is identical to {@code compile(String)} other * than the way in which the source is passed. * * @param script The reader from which the script source is obtained. * @return An implementation of {@code CompiledScript} to be executed * later using one of its {@code eval} methods of * {@code CompiledScript}. * @throws ScriptException if compilation fails. * @throws NullPointerException if argument is null. */ public CompiledScript compile(Reader script) throws ScriptException { try { return compile(convertToString(script)); } catch (IOException e) { throw new ScriptException(e); } } /** * Calls a procedure compiled during a previous script execution, which is * retained in the state of the {@code ScriptEngine{@code . * * @param name The name of the procedure to be called. * @param thiz If the procedure is a member of a class defined in the script * and thiz is an instance of that class returned by a previous execution or * invocation, the named method is called through that instance. If classes are * not supported in the scripting language or if the procedure is not a member * function of any class, the argument must be {@code null}. * @param args Arguments to pass to the procedure. The rules for converting * the arguments to scripting variables are implementation-specific. * @return The value returned by the procedure. The rules for converting the * scripting variable returned by the procedure to a Java Object are * implementation-specific. * @throws javax.script.ScriptException if an error occurrs during invocation * of the method. * @throws NoSuchMethodException if method with given name or matching argument * types cannot be found. * @throws NullPointerException if method name is null. */ public Object invokeMethod(Object thiz, String name, Object... args) throws ScriptException, NoSuchMethodException { if (!(thiz instanceof bsh.This)) { throw new ScriptException("Illegal objec type: " + thiz.getClass()); } bsh.This bshObject = (bsh.This) thiz; try { return bshObject.invokeMethod(name, args); } catch (ParseException e) { // explicit parsing error throw new ScriptException(e.toString(), e.getErrorSourceFile(), e.getErrorLineNumber()); } catch (TargetError e) { // The script threw an application level exception // set it as the cause ? ScriptException se = new ScriptException(e.toString(), e.getErrorSourceFile(), e.getErrorLineNumber()); se.initCause(e.getTarget()); throw se; } catch (EvalError e) { // The script couldn't be evaluated properly throw new ScriptException(e.toString(), e.getErrorSourceFile(), e.getErrorLineNumber()); } catch (InterpreterError e) { // The interpreter had a fatal problem throw new ScriptException(e.toString()); } } /** * Same as invoke(Object, String, Object...) with {@code null} as the * first argument. Used to call top-level procedures defined in scripts. * * @param args Arguments to pass to the procedure * @return The value returned by the procedure * @throws javax.script.ScriptException if an error occurrs during invocation * of the method. * @throws NoSuchMethodException if method with given name or matching * argument types cannot be found. * @throws NullPointerException if method name is null. */ public Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException { return invokeMethod(getGlobal(), name, args); } /** * Returns an implementation of an interface using procedures compiled in the * interpreter. The methods of the interface may be implemented using the * {@code invoke} method. * * @param clasz The {@code Class} object of the interface to return. * @return An instance of requested interface - null if the requested interface * is unavailable, i. e. if compiled methods in the * {@code ScriptEngine} cannot be found matching the ones in the * requested interface. * @throws IllegalArgumentException if the specified {@code Class} object * does not exist or is not an interface. */ public <T> T getInterface(Class<T> clasz) { return clasz.cast(getGlobal().getInterface(clasz)); } /** * Returns an implementation of an interface using member functions of a * scripting object compiled in the interpreter. The methods of the interface * may be implemented using invoke(Object, String, Object...) method. * * @param thiz The scripting object whose member functions are used to * implement the methods of the interface. * @param clasz The {@code Class} object of the interface to return. * @return An instance of requested interface - null if the requested * interface is unavailable, i. e. if compiled methods in the * {@code ScriptEngine} cannot be found matching the ones in the * requested interface. * @throws IllegalArgumentException if the specified {@code Class} object * does not exist or is not an interface, or if the specified Object is null * or does not represent a scripting object. */ public <T> T getInterface(Object thiz, Class<T> clasz) { if (!(thiz instanceof bsh.This)) { throw new IllegalArgumentException("invalid object type: " + thiz.getClass()); } bsh.This bshThis = (bsh.This) thiz; return clasz.cast(bshThis.getInterface(clasz)); } private bsh.This getGlobal() { // requires 2.0b5 to make getThis() public return getEngineNameSpace(getContext()).getThis(getInterpreter()); } /* This is a total hack. We need to introduce a writer to the Interpreter. */ class WriterOutputStream extends OutputStream { Writer writer; WriterOutputStream(Writer writer) { this.writer = writer; } public void write(int b) throws IOException { writer.write(b); } public void flush() throws IOException { writer.flush(); } public void close() throws IOException { writer.close(); } } }