// // ScriptRunner.java // Thud // // Copyright (c) 2001-2007 Anthony Parker & the THUD team. // All rights reserved. See LICENSE.TXT for more information. // package net.sourceforge.btthud.script; import java.util.List; import java.util.Iterator; import org.mozilla.javascript.Context; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.Function; import java.io.Reader; import java.io.IOException; /** * Scripted event-handling thread. * * TODO: Currently only uses the Rhino JavaScript engine, but should be * extended in the future to support pluggable scripting languages. */ public class ScriptRunner implements Runnable { private Context context = null; private Scriptable sharedScope = null; private boolean go = true; private List<Event> eventQueue = new java.util.ArrayList<Event> (); // Construct and start a new ScriptRunner thread. public ScriptRunner () { new Thread (this, "ScriptRunner").start(); } // Add an event to the queue. public void call (final String function) { call(function, Context.emptyArgs); } public void call (final String function, final Object[] args) { addEvent(new Event (function, args)); } private void addEvent (final Event evt) { synchronized (eventQueue) { eventQueue.add(evt); eventQueue.notify(); } } // Execute a script from an input stream. public void execute (final Reader input, final String name) { addEvent(new ExecuteEvent (input, name)); } public void execute (final String input, final String name) { addEvent(new ExecuteEvent (input, name)); } // Main loop that waits for events to process. public void run () { context = Context.enter(); try { // TODO: Let user set (and adjust) optimization level. // Initialize shared global scope. sharedScope = context.initStandardObjects(null, true); // TODO: We'd add and seal our Thud extensions here. //sharedScope.sealObject(); // Initialize unsealed global scope. // TODO: This would be a writable global scope, where // user-defined scripts would set global values. It // might not be needed. // TODO: We might want a per-connection scope, too. // Event dispatch loop. synchronized (eventQueue) { while (go) { // Handle pending events. dispatchEvents(); // Wait for more events. try { eventQueue.wait(); } catch (InterruptedException e) { // No big deal. } } } } finally { Context.exit(); } } // Stop thread gracefully. public void pleaseStop () { synchronized (eventQueue) { go = false; eventQueue.notify(); } } // Perform actual event dispatch. private void dispatchEvents () { final Iterator<Event> eqIter = eventQueue.iterator(); while (eqIter.hasNext()) { final Event evt = eqIter.next(); eqIter.remove(); try { if (evt.isExecuteEvent()) { handleEvent((ExecuteEvent)evt); } else { handleEvent(evt); } } catch (Exception e) { // Report exceptions and continue. System.err.println("Script: " + e); } } } private void handleEvent (final Event evt) { final String fName = evt.key; final Object[] args = (Object[])evt.data; // Find function. final Object fObject = sharedScope.get(fName, sharedScope); if (!(fObject instanceof Function)) { // No function. System.err.println("No function: " + fName + "()"); return; } // Extend scope for function call. final Scriptable scope = context.newObject(sharedScope); scope.setPrototype(sharedScope); scope.setParentScope(null); // Invoke function. final Object result = ((Function)fObject).call(context, scope, scope, args); System.out.println("Script: " + context.toString(result)); } private void handleEvent (final ExecuteEvent evt) { // Handle execute event. if (evt.isString()) { executeString((String)evt.data, evt.key); } else { executeScript((Reader)evt.data, evt.key); } } private void executeString (final String input, final String name) { // Execute string in global scope. // TODO: Make this togglable? final Object result = context.evaluateString(sharedScope, input, name, 1, null); System.out.println("Script: " + context.toString(result)); } private void executeScript (final Reader input, final String name) { // Execute script in global scope. try { context.evaluateReader(sharedScope, input, name, 1, null); } catch (IOException e) { System.err.println("Script: " + name + ": " + e); } } // Create a local scope. private Scriptable getLocalScope () { final Scriptable scope = context.newObject(sharedScope); scope.setPrototype(sharedScope); scope.setParentScope(null); return scope; } // Private Event class. static private class Event { protected final String key; protected final Object data; private Event (final String key, final Object data) { this.key = key; this.data = data; } protected boolean isExecuteEvent () { return false; } public String toString () { return "<" + key + ", " + data + ">"; } } static private class ExecuteEvent extends Event { private boolean isString; private ExecuteEvent (final Reader input, final String name) { super(name, input); isString = false; } private ExecuteEvent (final String input, final String name) { super(name, input); isString = true; } protected boolean isExecuteEvent () { return true; } protected boolean isString () { return isString; } } }