package org.jbpm.pvm.internal.script; import java.io.*; import java.util.Map; import javax.script.*; import bsh.*; import static javax.script.ScriptContext.*; /* 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 ); // This is a big hack, convert writer to PrintStream bsh.setOut( new PrintStream( new WriterOutputStream( scriptContext.getWriter() ) ) ); bsh.setErr( new PrintStream( new WriterOutputStream( 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() ); } } /* 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, ENGINE_SCOPE ); if ( ns == null ) { // Create a global namespace for the interpreter Map engineView = new ScriptContextEngineView( scriptContext ); ns = new ExternalNameSpace( null/*parent*/, "javax_script_context", engineView ); scriptContext.setAttribute( engineNameSpaceKey, ns, 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 String) for later * execution. * * @param script The source of the script, represented as a * String. * * @return An subclass of CompiledScript to be executed later * using one of the eval methods of CompiledScript. * * @throws ScriptException if compilation fails. * @throws NullPointerException if the argument is null. */ public CompiledScript compile( String script ) throws ScriptException { return compile( new StringReader( script ) ); } /** * Compiles the script (source read from Reader) for later * execution. Functionality is identical to 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 CompiledScript to be executed * later using one of its eval methods of * CompiledScript. * * @throws ScriptException if compilation fails. * @throws NullPointerException if argument is null. */ public CompiledScript compile( Reader script ) throws ScriptException { // todo throw new Error("unimplemented"); } /** * Calls a procedure compiled during a previous script execution, which is * retained in the state of the ScriptEngine. * * @param name The name of the script method to be called. * @param thiz thiz is an instance of the script class returned by a previous execution or * invocation, the named method is called through that instance. * @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 method. 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() ); } } /** * Used to call top-level procedures defined in scripts. * * @param name Name of the procedure * @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 procedure with given name or matching * argument types cannot be found. * @throws NullPointerException if procedure 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 * invokeFunction method. * * @param clasz The 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 * ScriptEngine cannot be found matching the ones in the * requested interface. * * @throws IllegalArgumentException if the specified Class object * does not exist or is not an interface. */ public T getInterface( Class clasz ) { try { return (T) getGlobal().getInterface( clasz ); } catch ( UtilEvalError utilEvalError ) { utilEvalError.printStackTrace(); return null; } } /** * 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 invokeMethod(Object, String, Object...) method. * * @param thiz The scripting object whose member functions are used to * implement the methods of the interface. * @param clasz The 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 * ScriptEngine cannot be found matching the ones in the * requested interface. * * @throws IllegalArgumentException if the specified 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 getInterface( Object thiz, Class clasz ) { if ( !(thiz instanceof bsh.This) ) throw new IllegalArgumentException( "invalid object type: "+thiz.getClass() ); try { bsh.This bshThis = (bsh.This)thiz; return (T) bshThis.getInterface( clasz ); } catch ( UtilEvalError utilEvalError ) { utilEvalError.printStackTrace( System.err ); return null; } } 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(); } } }