PageRenderTime 51ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/src/bsh/BshScriptEngine.java

http://beanshell2.googlecode.com/
Java | 380 lines | 190 code | 71 blank | 119 comment | 13 complexity | 117d1511a559a3d57a92f21d7446e809 MD5 | raw file
  1. package bsh;
  2. import javax.script.AbstractScriptEngine;
  3. import javax.script.Bindings;
  4. import javax.script.Compilable;
  5. import javax.script.CompiledScript;
  6. import javax.script.Invocable;
  7. import javax.script.ScriptContext;
  8. import javax.script.ScriptEngine;
  9. import javax.script.ScriptEngineFactory;
  10. import javax.script.ScriptException;
  11. import javax.script.SimpleBindings;
  12. import java.io.IOException;
  13. import java.io.OutputStream;
  14. import java.io.PrintStream;
  15. import java.io.Reader;
  16. import java.io.Writer;
  17. import java.util.ArrayList;
  18. import java.util.Collections;
  19. import java.util.HashMap;
  20. import java.util.List;
  21. import java.util.Map;
  22. /*
  23. Adopted from http://ikayzo.org/svn/beanshell/BeanShell/engine/src/bsh/engine/BshScriptEngine.java
  24. Notes
  25. This engine supports open-ended pluggable scriptcontexts
  26. */
  27. public class BshScriptEngine extends AbstractScriptEngine implements Compilable, Invocable {
  28. // The BeanShell global namespace for the interpreter is stored in the
  29. // engine scope map under this key.
  30. static final String engineNameSpaceKey = "org_beanshell_engine_namespace";
  31. private BshScriptEngineFactory factory;
  32. private bsh.Interpreter interpreter;
  33. public BshScriptEngine() {
  34. this(null);
  35. }
  36. public BshScriptEngine(BshScriptEngineFactory factory) {
  37. this.factory = factory;
  38. getInterpreter(); // go ahead and prime the interpreter now
  39. }
  40. protected Interpreter getInterpreter() {
  41. if (interpreter == null) {
  42. this.interpreter = new bsh.Interpreter();
  43. interpreter.setNameSpace(null); // should always be set by context
  44. }
  45. return interpreter;
  46. }
  47. public Object eval(String script, ScriptContext scriptContext) throws ScriptException {
  48. return evalSource(script, scriptContext);
  49. }
  50. public Object eval(Reader reader, ScriptContext scriptContext) throws ScriptException {
  51. return evalSource(reader, scriptContext);
  52. }
  53. /*
  54. This is the primary implementation method.
  55. We respect the String/Reader difference here in BeanShell because
  56. BeanShell will do a few extra things in the string case... e.g.
  57. tack on a trailing ";" semicolon if necessary.
  58. */
  59. private Object evalSource(Object source, ScriptContext scriptContext) throws ScriptException {
  60. bsh.NameSpace contextNameSpace = getEngineNameSpace(scriptContext);
  61. Interpreter bsh = getInterpreter();
  62. bsh.setNameSpace(contextNameSpace);
  63. bsh.setOut(toPrintStream(scriptContext.getWriter()));
  64. bsh.setErr(toPrintStream(scriptContext.getErrorWriter()));
  65. try {
  66. if (source instanceof Reader) {
  67. return bsh.eval((Reader) source);
  68. } else {
  69. return bsh.eval((String) source);
  70. }
  71. } catch (ParseException e) {
  72. // explicit parsing error
  73. throw new ScriptException(e.toString(), e.getErrorSourceFile(), e.getErrorLineNumber());
  74. } catch (TargetError e) {
  75. // The script threw an application level exception
  76. // set it as the cause ?
  77. ScriptException se = new ScriptException(e.toString(), e.getErrorSourceFile(), e.getErrorLineNumber());
  78. se.initCause(e.getTarget());
  79. throw se;
  80. } catch (EvalError e) {
  81. // The script couldn't be evaluated properly
  82. throw new ScriptException(e.toString(), e.getErrorSourceFile(), e.getErrorLineNumber());
  83. } catch (InterpreterError e) {
  84. // The interpreter had a fatal problem
  85. throw new ScriptException(e.toString());
  86. }
  87. }
  88. private PrintStream toPrintStream(final Writer writer) {
  89. // This is a big hack, convert writer to PrintStream
  90. return new PrintStream(new WriterOutputStream(writer));
  91. }
  92. /*
  93. Check the context for an existing global namespace embedded
  94. in the script context engine scope. If none exists, ininitialize the
  95. context with one.
  96. */
  97. private static NameSpace getEngineNameSpace(ScriptContext scriptContext) {
  98. NameSpace ns = (NameSpace) scriptContext.getAttribute(engineNameSpaceKey, ScriptContext.ENGINE_SCOPE);
  99. if (ns == null) {
  100. // Create a global namespace for the interpreter
  101. Map<String, Object> engineView = new ScriptContextEngineView(scriptContext);
  102. ns = new ExternalNameSpace(null/*parent*/, "javax_script_context", engineView);
  103. scriptContext.setAttribute(engineNameSpaceKey, ns, ScriptContext.ENGINE_SCOPE);
  104. }
  105. return ns;
  106. }
  107. public Bindings createBindings() {
  108. return new SimpleBindings();
  109. }
  110. public ScriptEngineFactory getFactory() {
  111. if (factory == null) {
  112. factory = new BshScriptEngineFactory();
  113. }
  114. return factory;
  115. }
  116. /**
  117. * Compiles the script (source represented as a {@code String}) for later
  118. * execution.
  119. *
  120. * @param script The source of the script, represented as a {@code String}.
  121. * @return An subclass of {@code CompiledScript} to be executed later
  122. * using one of the {@code eval} methods of {@code CompiledScript}.
  123. * @throws ScriptException if compilation fails.
  124. * @throws NullPointerException if the argument is null.
  125. */
  126. public CompiledScript compile(String script) throws ScriptException {
  127. try {
  128. final PreparsedScript preparsed = new PreparsedScript(script);
  129. return new CompiledScript() {
  130. @Override
  131. public Object eval(ScriptContext context) throws ScriptException {
  132. final HashMap<String, Object> map = new HashMap<String, Object>();
  133. final List<Integer> scopes = new ArrayList<Integer>(context.getScopes());
  134. Collections.sort(scopes); // lowest scope at first pos
  135. Collections.reverse(scopes); // highest scope at first pos
  136. for (final Integer scope : scopes) {
  137. map.putAll(context.getBindings(scope));
  138. }
  139. preparsed.setOut(toPrintStream(context.getWriter()));
  140. preparsed.setErr(toPrintStream(context.getErrorWriter()));
  141. try {
  142. return preparsed.invoke(map);
  143. } catch (final EvalError e) {
  144. throw constructScriptException(e);
  145. }
  146. }
  147. @Override
  148. public ScriptEngine getEngine() {
  149. return BshScriptEngine.this;
  150. }
  151. };
  152. } catch (final EvalError e) {
  153. throw constructScriptException(e);
  154. }
  155. }
  156. private ScriptException constructScriptException(final EvalError e) {
  157. return new ScriptException(e.getMessage(), e.getErrorSourceFile(), e.getErrorLineNumber());
  158. }
  159. private static String convertToString(Reader reader) throws IOException {
  160. final StringBuffer buffer = new StringBuffer(64);
  161. char[] cb = new char[64];
  162. int len;
  163. while ((len = reader.read(cb)) != -1) {
  164. buffer.append(cb, 0, len);
  165. }
  166. return buffer.toString();
  167. }
  168. /**
  169. * Compiles the script (source read from {@code Reader}) for later
  170. * execution. Functionality is identical to {@code compile(String)} other
  171. * than the way in which the source is passed.
  172. *
  173. * @param script The reader from which the script source is obtained.
  174. * @return An implementation of {@code CompiledScript} to be executed
  175. * later using one of its {@code eval} methods of
  176. * {@code CompiledScript}.
  177. * @throws ScriptException if compilation fails.
  178. * @throws NullPointerException if argument is null.
  179. */
  180. public CompiledScript compile(Reader script) throws ScriptException {
  181. try {
  182. return compile(convertToString(script));
  183. } catch (IOException e) {
  184. throw new ScriptException(e);
  185. }
  186. }
  187. /**
  188. * Calls a procedure compiled during a previous script execution, which is
  189. * retained in the state of the {@code ScriptEngine{@code .
  190. *
  191. * @param name The name of the procedure to be called.
  192. * @param thiz If the procedure is a member of a class defined in the script
  193. * and thiz is an instance of that class returned by a previous execution or
  194. * invocation, the named method is called through that instance. If classes are
  195. * not supported in the scripting language or if the procedure is not a member
  196. * function of any class, the argument must be {@code null}.
  197. * @param args Arguments to pass to the procedure. The rules for converting
  198. * the arguments to scripting variables are implementation-specific.
  199. * @return The value returned by the procedure. The rules for converting the
  200. * scripting variable returned by the procedure to a Java Object are
  201. * implementation-specific.
  202. * @throws javax.script.ScriptException if an error occurrs during invocation
  203. * of the method.
  204. * @throws NoSuchMethodException if method with given name or matching argument
  205. * types cannot be found.
  206. * @throws NullPointerException if method name is null.
  207. */
  208. public Object invokeMethod(Object thiz, String name, Object... args) throws ScriptException, NoSuchMethodException {
  209. if (!(thiz instanceof bsh.This)) {
  210. throw new ScriptException("Illegal objec type: " + thiz.getClass());
  211. }
  212. bsh.This bshObject = (bsh.This) thiz;
  213. try {
  214. return bshObject.invokeMethod(name, args);
  215. } catch (ParseException e) {
  216. // explicit parsing error
  217. throw new ScriptException(e.toString(), e.getErrorSourceFile(), e.getErrorLineNumber());
  218. } catch (TargetError e) {
  219. // The script threw an application level exception
  220. // set it as the cause ?
  221. ScriptException se = new ScriptException(e.toString(), e.getErrorSourceFile(), e.getErrorLineNumber());
  222. se.initCause(e.getTarget());
  223. throw se;
  224. } catch (EvalError e) {
  225. // The script couldn't be evaluated properly
  226. throw new ScriptException(e.toString(), e.getErrorSourceFile(), e.getErrorLineNumber());
  227. } catch (InterpreterError e) {
  228. // The interpreter had a fatal problem
  229. throw new ScriptException(e.toString());
  230. }
  231. }
  232. /**
  233. * Same as invoke(Object, String, Object...) with {@code null} as the
  234. * first argument. Used to call top-level procedures defined in scripts.
  235. *
  236. * @param args Arguments to pass to the procedure
  237. * @return The value returned by the procedure
  238. * @throws javax.script.ScriptException if an error occurrs during invocation
  239. * of the method.
  240. * @throws NoSuchMethodException if method with given name or matching
  241. * argument types cannot be found.
  242. * @throws NullPointerException if method name is null.
  243. */
  244. public Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException {
  245. return invokeMethod(getGlobal(), name, args);
  246. }
  247. /**
  248. * Returns an implementation of an interface using procedures compiled in the
  249. * interpreter. The methods of the interface may be implemented using the
  250. * {@code invoke} method.
  251. *
  252. * @param clasz The {@code Class} object of the interface to return.
  253. * @return An instance of requested interface - null if the requested interface
  254. * is unavailable, i. e. if compiled methods in the
  255. * {@code ScriptEngine} cannot be found matching the ones in the
  256. * requested interface.
  257. * @throws IllegalArgumentException if the specified {@code Class} object
  258. * does not exist or is not an interface.
  259. */
  260. public <T> T getInterface(Class<T> clasz) {
  261. return clasz.cast(getGlobal().getInterface(clasz));
  262. }
  263. /**
  264. * Returns an implementation of an interface using member functions of a
  265. * scripting object compiled in the interpreter. The methods of the interface
  266. * may be implemented using invoke(Object, String, Object...) method.
  267. *
  268. * @param thiz The scripting object whose member functions are used to
  269. * implement the methods of the interface.
  270. * @param clasz The {@code Class} object of the interface to return.
  271. * @return An instance of requested interface - null if the requested
  272. * interface is unavailable, i. e. if compiled methods in the
  273. * {@code ScriptEngine} cannot be found matching the ones in the
  274. * requested interface.
  275. * @throws IllegalArgumentException if the specified {@code Class} 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: " + thiz.getClass());
  282. }
  283. bsh.This bshThis = (bsh.This) thiz;
  284. return clasz.cast(bshThis.getInterface(clasz));
  285. }
  286. private bsh.This getGlobal() {
  287. // requires 2.0b5 to make getThis() public
  288. return getEngineNameSpace(getContext()).getThis(getInterpreter());
  289. }
  290. /*
  291. This is a total hack. We need to introduce a writer to the
  292. Interpreter.
  293. */
  294. class WriterOutputStream extends OutputStream {
  295. Writer writer;
  296. WriterOutputStream(Writer writer) {
  297. this.writer = writer;
  298. }
  299. public void write(int b) throws IOException {
  300. writer.write(b);
  301. }
  302. public void flush() throws IOException {
  303. writer.flush();
  304. }
  305. public void close() throws IOException {
  306. writer.close();
  307. }
  308. }
  309. }