001: package bsh.engine;
002:
003: import java.io.*;
004: import java.util.Map;
005: import javax.script.*;
006: import bsh.*;
007: import static javax.script.ScriptContext.*;
008:
009: /*
010: Notes
011: This engine supports open-ended pluggable scriptcontexts
012: */
013: public class BshScriptEngine extends AbstractScriptEngine implements
014: Compilable, Invocable {
015: // The BeanShell global namespace for the interpreter is stored in the
016: // engine scope map under this key.
017: static final String engineNameSpaceKey = "org_beanshell_engine_namespace";
018:
019: private BshScriptEngineFactory factory;
020: private bsh.Interpreter interpreter;
021:
022: public BshScriptEngine() {
023: this (null);
024: }
025:
026: public BshScriptEngine(BshScriptEngineFactory factory) {
027: this .factory = factory;
028: getInterpreter(); // go ahead and prime the interpreter now
029: }
030:
031: protected Interpreter getInterpreter() {
032: if (interpreter == null) {
033: this .interpreter = new bsh.Interpreter();
034: interpreter.setNameSpace(null); // should always be set by context
035: }
036:
037: return interpreter;
038: }
039:
040: public Object eval(String script, ScriptContext scriptContext)
041: throws ScriptException {
042: return evalSource(script, scriptContext);
043: }
044:
045: public Object eval(Reader reader, ScriptContext scriptContext)
046: throws ScriptException {
047: return evalSource(reader, scriptContext);
048: }
049:
050: /*
051: This is the primary implementation method.
052: We respect the String/Reader difference here in BeanShell because
053: BeanShell will do a few extra things in the string case... e.g.
054: tack on a trailing ";" semicolon if necessary.
055: */
056: private Object evalSource(Object source, ScriptContext scriptContext)
057: throws ScriptException {
058: bsh.NameSpace contextNameSpace = getEngineNameSpace(scriptContext);
059: Interpreter bsh = getInterpreter();
060: bsh.setNameSpace(contextNameSpace);
061:
062: // This is a big hack, convert writer to PrintStream
063: bsh.setOut(new PrintStream(new WriterOutputStream(scriptContext
064: .getWriter())));
065: bsh.setErr(new PrintStream(new WriterOutputStream(scriptContext
066: .getErrorWriter())));
067:
068: try {
069: if (source instanceof Reader)
070: return bsh.eval((Reader) source);
071: else
072: return bsh.eval((String) source);
073: } catch (ParseException e) {
074: // explicit parsing error
075: throw new ScriptException(e.toString(), e
076: .getErrorSourceFile(), e.getErrorLineNumber());
077: } catch (TargetError e) {
078: // The script threw an application level exception
079: // set it as the cause ?
080: ScriptException se = new ScriptException(e.toString(), e
081: .getErrorSourceFile(), e.getErrorLineNumber());
082: se.initCause(e.getTarget());
083: throw se;
084: } catch (EvalError e) {
085: // The script couldn't be evaluated properly
086: throw new ScriptException(e.toString(), e
087: .getErrorSourceFile(), e.getErrorLineNumber());
088: } catch (InterpreterError e) {
089: // The interpreter had a fatal problem
090: throw new ScriptException(e.toString());
091: }
092: }
093:
094: /*
095: Check the context for an existing global namespace embedded
096: in the script context engine scope. If none exists, ininitialize the
097: context with one.
098: */
099: private static NameSpace getEngineNameSpace(
100: ScriptContext scriptContext) {
101: NameSpace ns = (NameSpace) scriptContext.getAttribute(
102: engineNameSpaceKey, ENGINE_SCOPE);
103:
104: if (ns == null) {
105: // Create a global namespace for the interpreter
106: Map engineView = new ScriptContextEngineView(scriptContext);
107: ns = new ExternalNameSpace(null/*parent*/,
108: "javax_script_context", engineView);
109:
110: scriptContext.setAttribute(engineNameSpaceKey, ns,
111: ENGINE_SCOPE);
112: }
113:
114: return ns;
115: }
116:
117: public Bindings createBindings() {
118: return new SimpleBindings();
119: }
120:
121: public ScriptEngineFactory getFactory() {
122: if (factory == null)
123: factory = new BshScriptEngineFactory();
124: return factory;
125: }
126:
127: /**
128: * Compiles the script (source represented as a <code>String</code>) for later
129: * execution.
130: *
131: * @param script The source of the script, represented as a
132: * <code>String</code>.
133: *
134: * @return An subclass of <code>CompiledScript</code> to be executed later
135: * using one of the <code>eval</code> methods of <code>CompiledScript</code>.
136: *
137: * @throws ScriptException if compilation fails.
138: * @throws NullPointerException if the argument is null.
139: */
140:
141: public CompiledScript compile(String script) throws ScriptException {
142: return compile(new StringReader(script));
143: }
144:
145: /**
146: * Compiles the script (source read from <code>Reader</code>) for later
147: * execution. Functionality is identical to <code>compile(String)</code> other
148: * than the way in which the source is passed.
149: *
150: * @param script The reader from which the script source is obtained.
151: *
152: * @return An implementation of <code>CompiledScript</code> to be executed
153: * later using one of its <code>eval</code> methods of
154: * <code>CompiledScript</code>.
155: *
156: * @throws ScriptException if compilation fails.
157: * @throws NullPointerException if argument is null.
158: */
159: public CompiledScript compile(Reader script) throws ScriptException {
160: // todo
161: throw new Error("unimplemented");
162: }
163:
164: /**
165: * Calls a procedure compiled during a previous script execution, which is
166: * retained in the state of the <code>ScriptEngine<code>.
167: *
168: * @param name The name of the procedure to be called.
169: * @param thiz If the procedure is a member of a class defined in the script
170: * and thiz is an instance of that class returned by a previous execution or
171: * invocation, the named method is called through that instance. If classes are
172: * not supported in the scripting language or if the procedure is not a member
173: * function of any class, the argument must be <code>null</code>.
174: * @param args Arguments to pass to the procedure. The rules for converting
175: * the arguments to scripting variables are implementation-specific.
176: *
177: * @return The value returned by the procedure. The rules for converting the
178: * scripting variable returned by the procedure to a Java Object are
179: * implementation-specific.
180: *
181: * @throws javax.script.ScriptException if an error occurrs during invocation
182: * of the method.
183: * @throws NoSuchMethodException if method with given name or matching argument
184: * types cannot be found.
185: * @throws NullPointerException if method name is null.
186: */
187: public Object invoke(Object thiz, String name, Object... args)
188: throws ScriptException, NoSuchMethodException {
189: if (!(thiz instanceof bsh.This))
190: throw new ScriptException("Illegal objec type: "
191: + thiz.getClass());
192:
193: bsh.This bshObject = (bsh.This) thiz;
194:
195: try {
196: return bshObject.invokeMethod(name, args);
197: } catch (ParseException e) {
198: // explicit parsing error
199: throw new ScriptException(e.toString(), e
200: .getErrorSourceFile(), e.getErrorLineNumber());
201: } catch (TargetError e) {
202: // The script threw an application level exception
203: // set it as the cause ?
204: ScriptException se = new ScriptException(e.toString(), e
205: .getErrorSourceFile(), e.getErrorLineNumber());
206: se.initCause(e.getTarget());
207: throw se;
208: } catch (EvalError e) {
209: // The script couldn't be evaluated properly
210: throw new ScriptException(e.toString(), e
211: .getErrorSourceFile(), e.getErrorLineNumber());
212: } catch (InterpreterError e) {
213: // The interpreter had a fatal problem
214: throw new ScriptException(e.toString());
215: }
216: }
217:
218: /**
219: * Same as invoke(Object, String, Object...) with <code>null</code> as the
220: * first argument. Used to call top-level procedures defined in scripts.
221: *
222: * @param args Arguments to pass to the procedure
223: *
224: * @return The value returned by the procedure
225: *
226: * @throws javax.script.ScriptException if an error occurrs during invocation
227: * of the method.
228: * @throws NoSuchMethodException if method with given name or matching
229: * argument types cannot be found.
230: * @throws NullPointerException if method name is null.
231: */
232: public Object invoke(String name, Object... args)
233: throws ScriptException, NoSuchMethodException {
234: return invoke(getGlobal(), name, args);
235: }
236:
237: /**
238: * Returns an implementation of an interface using procedures compiled in the
239: * interpreter. The methods of the interface may be implemented using the
240: * <code>invoke</code> method.
241: *
242: * @param clasz The <code>Class</code> object of the interface to return.
243: *
244: * @return An instance of requested interface - null if the requested interface
245: * is unavailable, i. e. if compiled methods in the
246: * <code>ScriptEngine</code> cannot be found matching the ones in the
247: * requested interface.
248: *
249: * @throws IllegalArgumentException if the specified <code>Class</code> object
250: * does not exist or is not an interface.
251: */
252: public <T> T getInterface(Class<T> clasz) {
253: try {
254: return (T) getGlobal().getInterface(clasz);
255: } catch (UtilEvalError utilEvalError) {
256: utilEvalError.printStackTrace();
257: return null;
258: }
259: }
260:
261: /**
262: * Returns an implementation of an interface using member functions of a
263: * scripting object compiled in the interpreter. The methods of the interface
264: * may be implemented using invoke(Object, String, Object...) method.
265: *
266: * @param thiz The scripting object whose member functions are used to
267: * implement the methods of the interface.
268: * @param clasz The <code>Class</code> object of the interface to return.
269: *
270: * @return An instance of requested interface - null if the requested
271: * interface is unavailable, i. e. if compiled methods in the
272: * <code>ScriptEngine</code> cannot be found matching the ones in the
273: * requested interface.
274: *
275: * @throws IllegalArgumentException if the specified <code>Class</code> object
276: * does not exist or is not an interface, or if the specified Object is null
277: * or does not represent a scripting object.
278: */
279: public <T> T getInterface(Object thiz, Class<T> clasz) {
280: if (!(thiz instanceof bsh.This))
281: throw new IllegalArgumentException("invalid object type: "
282: + thiz.getClass());
283:
284: try {
285: bsh.This bshThis = (bsh.This) thiz;
286: return (T) bshThis.getInterface(clasz);
287: } catch (UtilEvalError utilEvalError) {
288: utilEvalError.printStackTrace(System.err);
289: return null;
290: }
291: }
292:
293: private bsh.This getGlobal() {
294: // requires 2.0b5 to make getThis() public
295: return getEngineNameSpace(getContext()).getThis(
296: getInterpreter());
297: }
298:
299: /*
300: This is a total hack. We need to introduce a writer to the
301: Interpreter.
302: */
303: class WriterOutputStream extends OutputStream {
304: Writer writer;
305:
306: WriterOutputStream(Writer writer) {
307: this .writer = writer;
308: }
309:
310: public void write(int b) throws IOException {
311: writer.write(b);
312: }
313:
314: public void flush() throws IOException {
315: writer.flush();
316: }
317:
318: public void close() throws IOException {
319: writer.close();
320: }
321: }
322:
323: }
|