/* AnExecute a script in JavaScript.
Copyright (c) 2014 The Regents of the University of California.
All rights reserved.
Permission is hereby granted, without written agreement and without
license or royalty fees, to use, copy, modify, and distribute this
software and its documentation for any purpose, provided that the above
copyright notice and the following two paragraphs appear in all copies
of this software.
IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
ENHANCEMENTS, OR MODIFICATIONS.
PT_COPYRIGHT_VERSION_2
COPYRIGHTENDKEY
*/
package ptolemy.actor.lib.js;
import io.socket.IOAcknowledge;
import io.socket.IOCallback;
import io.socket.SocketIO;
import io.socket.SocketIOException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.oltu.oauth2.client.OAuthClient;
import org.apache.oltu.oauth2.client.URLConnectionClient;
import org.apache.oltu.oauth2.client.request.OAuthBearerClientRequest;
import org.apache.oltu.oauth2.client.request.OAuthClientRequest;
import org.apache.oltu.oauth2.client.response.OAuthJSONAccessTokenResponse;
import org.apache.oltu.oauth2.client.response.OAuthResourceResponse;
import org.apache.oltu.oauth2.common.OAuth;
import org.apache.oltu.oauth2.common.OAuthProviderType;
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.types.GrantType;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.FunctionObject;
import org.mozilla.javascript.ImporterTopLevel;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.NativeJavaObject;
import org.mozilla.javascript.NativeObject;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.WrappedException;
import org.ptolemy.ptango.lib.HttpRequest;
import org.ptolemy.ptango.lib.HttpResponse;
import ptolemy.actor.IOPort;
import ptolemy.actor.TypedAtomicActor;
import ptolemy.actor.TypedIOPort;
import ptolemy.actor.gui.BrowserLauncher;
import ptolemy.actor.parameters.ParameterPort;
import ptolemy.actor.parameters.PortParameter;
import ptolemy.actor.util.Time;
import ptolemy.data.ActorToken;
import ptolemy.data.ArrayToken;
import ptolemy.data.BooleanToken;
import ptolemy.data.DoubleToken;
import ptolemy.data.IntToken;
import ptolemy.data.LongToken;
import ptolemy.data.ObjectToken;
import ptolemy.data.RecordToken;
import ptolemy.data.StringToken;
import ptolemy.data.Token;
import ptolemy.data.expr.Parameter;
import ptolemy.data.type.BaseType;
import ptolemy.graph.Inequality;
import ptolemy.kernel.CompositeEntity;
import ptolemy.kernel.Entity;
import ptolemy.kernel.util.IllegalActionException;
import ptolemy.kernel.util.InternalErrorException;
import ptolemy.kernel.util.KernelException;
import ptolemy.kernel.util.NameDuplicationException;
import ptolemy.kernel.util.StringAttribute;
import ptolemy.kernel.util.Workspace;
import ptolemy.util.MessageHandler;
///////////////////////////////////////////////////////////////////
//// JavaScript
/**
Execute a script in JavaScript that can read inputs and parameters,
perform calculations, and write outputs. The script may be provided
as the textual value of the script parameter, or as an input
on the scriptIn port.
If it is an input, then it can be different on each firing.
If it is a parameter, then the script is compiled in the initialize()
method, and any changes made to it are ignored until the next initiatlization
of the model.
To use this actor, add input and output ports and parameters, and specify
a script.
Your script should define one or more of the following functions:
initialize. This function is invoked each time this actor
is initialized, or if an initialize function is provided on the
scriptIn input port, then it will be invoked when this
actor fires and consumes that input. This function can read
parameter values using valueOf() (see below), normally there
will be no inputs, so it should not read inputs.
fire. This function is invoked each time this actor fires.
It can read parameters, read inputs using get(), and write outputs
using send(). Note that all inputs from all ports are consumed during
a firing, even if the script does not contain a call to get() for
a port. Multiple calls to get() during a
firing will return the same value.
wrapup. This function is invoked at the end of execution of
of the model. It can read parameters, but normally should not
read inputs nor write outputs.
Usually, you will need to explicitly specify the types
of the output ports. Alternatively, you can enable backward
type inference on the enclosing model, and the types of output
ports will be inferred by how the data are used.
You may also need to set the type of input ports. Usually, forward
type inference will work, and the type of the input port will be based
on the source of data. However,
if the input comes from an output port whose output is undefined,
such as JSONToToken or HttpRequestHandler, then you may want to
enable backward type inference, and specify here the type of input
that your script requires.
The context in which your functions run provide the following methods:
alert(string): pop up a dialog with the specified message.
clearTimeout(int): clear a timeout with the specified handle.
error(string): throw an IllegalActionException with the specified message.
get(port, n): get an input from a port on channel n (return null if there is no input).
print(string): print the specified string to the console (standard out).
readURL(string): read the specified URL and return its contents as a string (HTTP GET).
send(value, port, n): send a value to an output port on channel n
setTimeout(function, int): set the function to execute after specified time and return handle.
valueOf(parameter): retrieve the value of a parameter.
The last argument of get() and send() is optional.
If you leave it off, the channel number will be assumed to be zero
(designating the first channel connected to the port).
The following example script calculates the factorial of the input.
function fire() {
var value = get(input);
if (value < 0) {
error("Input must be greater than or equal to 0.");
} else {
var total = 1;
while (value > 1) {
total *= value;
value--;
}
send(total, output);
}
}
Your script may also store values from one firing to the next, or from
initialization to firing. For example,
var init;
function initialize() {
init = 0;
}
function fire() {
init = init + 1;
send(init, output);
}
will send a count of firings to the output named "output".
Notice that you may name ports or parameters of this actor in such
a way as to shadow objects in the global scope. For example,
JavaScript provides a JSON object that you can use to operate
on JSON strings, for example with the function JSON.parse().
However, if you name a port or parameter "JSON", then any reference
to JSON in your fire() method, for example, will refer to the port
or parameter, and not to the global object. To explicitly refer
to the global object, use the syntax "this.JSON".
In addition, the symbol "actor" is defined to be the instance of
this actor. In JavaScript, you can invoke methods on it.
For example, the JavaScript
actor.toplevel().getName();
will return the name of the top-level composite actor containing
this actor.
Not all Ptolemy II data types translate naturally to
JavaScript types. This actor converts Ptolemy types int,
double, string, and boolean to and from equivalent JavaScript
types. In addition, arrays are converted to native
JavaScript arrays. Ptolemy II records are converted into
JavaScript objects, and JavaScript objects with enumerable
properties into records.
For other Ptolemy II types, the script will see the
corresponding Token object. For example, if you send
a number of type long to an input of a JavaScript actor,
the script (inside a fire() function):
var value = get(input);
print(value.getClass().toString());
will print on standard out the string
"class ptolemy.data.LongToken"
JavaScript does not have a long data type (as of this writing), so instead the
get() call returns a JavaScript Object wrapping the Ptolemy II
LongToken object. You can then invoke methods on that token,
such as getClass(), as done above.
Scripts can instantiate Java classes and invoke methods on them.
For example, the following script will build a simple Ptolemy II model:
importPackage(Packages.ptolemy.actor);
importClass(Packages.ptolemy.actor.lib.Ramp);
importClass(Packages.ptolemy.actor.lib.FileWriter);
importClass(Packages.ptolemy.domains.sdf.kernel.SDFDirector);
var toplevel = new TypedCompositeActor();
var ramp = new Ramp(toplevel, "ramp");
var writer = new FileWriter(toplevel, "writer");
toplevel.connect(ramp.output, writer.input);
var director = new SDFDirector(toplevel, "SDFDirector");
director.iterations.setExpression("10");
You can even send this out on an output port.
For example,
send(toplevel, output, 0);
where "output" is the name of the output port.
Subclasses of this actor may put it in "restricted" mode, which
limits the functionality as follows:
The "actor" variable (referring to this instance of the actor) does not get created.
The readURL method only supports the HTTP protocol (in particular,
it does not support the "file" protocol, and hence cannot access local files).
@author Edward A. Lee
@version $Id: JavaScript.java 70402 2014-10-23 00:52:20Z cxh $
@since Ptolemy II 10.0
@Pt.ProposedRating Yellow (eal)
@Pt.AcceptedRating Red (bilung)
*/
public class JavaScript extends TypedAtomicActor {
/** Construct an actor with the given container and name.
* In addition to invoking the base class constructors, construct
* the init and step parameter and the step
* port. Initialize init
* to IntToken with value 0, and step to IntToken with value 1.
* @param container The container.
* @param name The name of this actor.
* @exception IllegalActionException If the actor cannot be contained
* by the proposed container.
* @exception NameDuplicationException If the container already has an
* actor with this name.
*/
public JavaScript(CompositeEntity container, String name)
throws NameDuplicationException, IllegalActionException {
super(container, name);
script = new StringAttribute(this, "script");
// Set the visibility to expert, as casual users should
// not see the script. This is particularly true if one
// installs an actor that is an instance of this with a
// particular script in the library.
// FIXME: Not until we have the editors.
// script.setVisibility(Settable.EXPERT);
// Create the script input.
// NOTE: This cannot be a PortParameter because it can't be a string-mode
// parameter. The parameter syntax uses $var substitution, which is likely
// to conflict with JavaScript, particularly if jQuery is used.
scriptIn = new TypedIOPort(this, "scriptIn", true, false);
scriptIn.setTypeEquals(BaseType.STRING);
StringAttribute cardinal = new StringAttribute(scriptIn, "_cardinal");
cardinal.setExpression("SOUTH");
// initialize the script to provide an empty template:
script.setExpression("// Put your JavaScript program here.\n"
+ "// Add ports and parameters.\n"
+ "// Define JavaScript functions initialize(), fire(), and/or wrapup().\n"
+ "// Use valueOf(parameterName) to refer to parameters.\n"
+ "// In the fire() function, use get(parameterName, channel) to read inputs.\n"
+ "// Send to output ports using send(value, portName, channel).\n");
}
///////////////////////////////////////////////////////////////////
//// ports and parameters ////
/** The script to execute when this actor fires. */
public StringAttribute script;
/** Alternative way to provide a script to execute when this actor fires.
* This input port has type string.
* If this is connected and provided an input, then the
* script provided as input is executed instead of the one given
* by the script parameter.
*/
public TypedIOPort scriptIn;
///////////////////////////////////////////////////////////////////
//// public methods ////
/** Clone the actor into the specified workspace.
* @param workspace The workspace for the new object.
* @return A new actor.
* @exception CloneNotSupportedException If a derived class contains
* an attribute that cannot be cloned.
*/
@Override
public Object clone(Workspace workspace) throws CloneNotSupportedException {
JavaScript newObject = (JavaScript) super.clone(workspace);
newObject._inputTokens = new HashMap>();
newObject._outputTokens = null;
newObject._pendingTimeoutFunctions = null;
newObject._pendingTimeoutIDs = null;
return newObject;
}
/** Execute the script, handling inputs and producing outputs as described.
*
*
* First, send any outputs that have been queued to be sent by calling send()
* from outside any firing of this JavaScript actor.
*
* Next, read all inputs, making a record of which ones have new tokens.
*
* If there is a new token on the scriptIn input port, then replace the current
* script with the one specified on that port. Any previously defined methods
* such as fire() will be replaced or deleted (if the new script does not have one).
* If the new script has an initialize() method, then invoke that method.
*
* Next, if the current script has a fire() method, invoke that method.
*
* Next, if any input has a new token and there is a method bound to that port
* via the setInputHandler() method, then invoke that method.
* Such methods will be invoked in the order in which handleInput() was invoked.
*
* Next, if setTimeout() has been called, and the current time of the director
* matches the time specified in setTimeout(), then invoke the function specified
* in setTimeout().
*
*
* @exception IllegalActionException If calling send() or super.fire()
* throws it.
*/
@Override
public void fire() throws IllegalActionException {
super.fire();
// If there is an input at scriptIn, evaluate that script.
try {
if (scriptIn.getWidth() > 0 && scriptIn.hasToken(0)) {
// A script is provided as input.
String scriptValue = ((StringToken) scriptIn.get(0))
.stringValue();
// FIXME: Need to delete any side effects from previously read scripts,
// such as function definitions and bindings. Timeouts?
// See the initialize() method.
// Compile the script.
_compiledScript = _context.compileString(scriptValue,
getName(), 1, null);
// Execute the script (Note that the script defines functions and variables;
// the functions are not invoked here. Just defined.)
_compiledScript.exec(Context.getCurrentContext(), _scope);
// Execute the initialize() function, if it exists.
Object initializeFunction = _scope.get("initialize", _scope);
if (initializeFunction instanceof Function) {
((Function) initializeFunction).call(
Context.getCurrentContext(), _scope, _global, null);
}
}
} catch (WrappedException ex) {
Throwable original = ex.getWrappedException();
throw new IllegalActionException(this, ex,
"Exception during executing script at line "
+ ex.lineNumber() + ".\n" + original.getMessage());
}
// Send out buffered outputs, if there are any.
// This has to be synchronized in case a callback calls send() at the
// same time.
synchronized (this) {
if (_outputTokens != null) {
for (IOPort port : _outputTokens.keySet()) {
HashMap> tokens = _outputTokens
.get(port);
for (Map.Entry> entry : tokens
.entrySet()) {
List queue = entry.getValue();
if (queue != null) {
for (Token token : queue) {
port.send(entry.getKey(), token);
}
}
}
}
_outputTokens.clear();
}
}
// Update any port parameters that have been added.
for (PortParameter portParameter : attributeList(PortParameter.class)) {
// FIXME: Record which ones have new tokens.
portParameter.update();
}
// Read all the available inputs.
_inputTokens.clear();
for (IOPort input : this.inputPortList()) {
// Skip the scriptIn input.
if (input == scriptIn) {
continue;
}
// Skip ParameterPorts, as those are handled by the update() call above.
if (input instanceof ParameterPort) {
continue;
}
HashMap tokens = new HashMap();
for (int i = 0; i < input.getWidth(); i++) {
if (input.hasToken(i)) {
// FIXME: Record that there is a token.
tokens.put(i, input.get(i));
}
}
_inputTokens.put(input, tokens);
}
try {
// Mark that we are in the fire() method, enabling outputs to be
// sent immediately.
// Synchronize to ensure that this function invocation is atomic
// w.r.t. to any callbacks.
synchronized (JavaScript.this) {
_inFire = true;
try {
// If there is a fire() function, invoke it.
Object fireFunction = _scope.get("fire", _scope);
if (fireFunction instanceof Function) {
((Function) fireFunction).call(
Context.getCurrentContext(), _scope, _global,
_EMPTY_ARGS);
}
// FIXME: If setInputHandler() has been called, invoke the appropriate methods.
// Handle timeout requests that match the current time.
if (_pendingTimeoutIDs != null) {
// If current time matches pending timeout requests, invoke them.
Time currentTime = getDirector().getModelTime();
List ids = _pendingTimeoutIDs.get(currentTime);
if (ids != null) {
for (Integer id : ids) {
Function function = _pendingTimeoutFunctions
.get(id);
if (function != null) {
function.call(Context.getCurrentContext(),
_scope, _global, _EMPTY_ARGS);
_pendingTimeoutFunctions.remove(id);
}
}
_pendingTimeoutIDs.remove(currentTime);
}
}
} finally {
_inFire = false;
}
}
} catch (WrappedException ex) {
Throwable original = ex.getWrappedException();
throw new IllegalActionException(this, ex,
"Exception during executing script at line "
+ ex.lineNumber() + ".\n" + original.getMessage());
}
}
/** Register the ports and parameters with the JavaScript engine.
* @exception IllegalActionException If calling send() or super.fire()
* throws it.
*/
@Override
public void initialize() throws IllegalActionException {
super.initialize();
_executing = true;
_pendingTimeoutFunctions = null;
_pendingTimeoutIDs = null;
// Expose the ports and parameters by name as JavaScript variables.
for (TypedIOPort port : portList()) {
// Do not convert the scriptIn port to a JavaScript variable.
if (port == scriptIn) {
continue;
}
Object jsObject;
if (_restricted) {
jsObject = Context.javaToJS(new PortProxy(port), _scope);
} else {
jsObject = Context.javaToJS(port, _scope);
}
_scope.put(port.getName(), _scope, jsObject);
}
for (Parameter parameter : attributeList(Parameter.class)) {
Object jsObject;
if (_restricted) {
jsObject = Context.javaToJS(new ParameterProxy(parameter),
_scope);
} else {
jsObject = Context.javaToJS(parameter, _scope);
}
_scope.put(parameter.getName(), _scope, jsObject);
}
// Compile the script to execute at run time.
String scriptValue = script.getValueAsString();
_compiledScript = _context.compileString(scriptValue, getName(), 1,
null);
// Execute the script (Note that the script defines functions and variables;
// the functions are not invoked here. Just defined.)
_compiledScript.exec(Context.getCurrentContext(), _scope);
// Execute the initialize() function, if it exists.
// Synchronize to ensure that this is atomic w.r.t. any callbacks.
// Note that the callbacks might be invoked after a model has terminated
// execution.
synchronized (JavaScript.this) {
// Clear any queued output tokens.
if (_outputTokens != null) {
_outputTokens.clear();
}
Object initializeFunction = _scope.get("initialize", _scope);
if (initializeFunction instanceof Function) {
((Function) initializeFunction).call(
Context.getCurrentContext(), _scope, _global,
_EMPTY_ARGS);
}
}
}
/** Return true if the specified string is not a JavaScript keyword and is a valid JavaScript identifier.
* @param identifier The proposed name.
* @return True if it is a valid identifier name.
*/
public static boolean isValidIdentifierName(String identifier) {
// Pathetically, neither JavaScript nor Rhino provide this method.
int length = identifier.length();
if (length == 0) {
return false;
}
if (!Character.isJavaIdentifierStart(identifier.charAt(0))) {
return false;
}
for (int i = 1; i != length; ++i) {
if (!Character.isJavaIdentifierPart(identifier.charAt(i))) {
return false;
}
}
return !_KEYWORDS.contains(identifier);
}
/** Set the state to equal the value of the init parameter.
* The state is incremented by the value of the step
* parameter on each iteration (in the postfire() method).
* @exception IllegalActionException If the parent class throws it.
*/
@Override
public void preinitialize() throws IllegalActionException {
super.preinitialize();
// Create a context for the current thread.
_context = Context.enter();
// If you want to include standard Rhino methods, like print, do this:
// _context.initStandardObjects();
// Create a scope for this actor.
_scope = new ImporterTopLevel(Context.getCurrentContext());
// Create also also a global scope so that if the actor shadows any
// global objects, they can still be referenced using this.objectName.
_global = new ImporterTopLevel(Context.getCurrentContext());
// Define built-in methods in the context.
PtolemyJavaScript scriptable = new PtolemyJavaScript();
scriptable.setParentScope(_scope);
// Get a reference to the instance method to be made available in JavaScript as a global function.
try {
// Create an array of method names and a matching array of arrays of argument types
// for functions that will be provided in the JavaScript environment.
// Keep these alphabetical.
String[] methodNames = { "alert", "clearTimeout", "error", "get",
"httpRequest", "localHostAddress", "openBrowser", "print",
"readProtectedURL", "readURL", "requestAccess",
"requestAuth", "send", "setTimeout", "socketX", "valueOf" };
Class[][] args = {
{ String.class }, // alert
{ Integer.class }, // clearTimeout
{ String.class }, // error
{ NativeJavaObject.class, Double.class }, // get
{ String.class, String.class, NativeObject.class,
String.class, Integer.class }, // httpRequest
{}, // localHostAddress
{ String.class }, // openBrowser
{ String.class }, // print
{ String.class, String.class }, // readProtectedURL
{ String.class }, // readURL
{ String.class, String.class, String.class, String.class,
String.class }, // requestAccess
{ String.class, String.class, String.class, Boolean.class }, // requestAuth
{ Object.class, NativeJavaObject.class, Double.class }, // send
{ Function.class, Integer.class }, // setTimeout
{ String.class, NativeObject.class, NativeJavaObject.class }, // socketX
{ NativeJavaObject.class }, // valueOf
};
int count = 0;
for (String methodName : methodNames) {
Method scriptableInstanceMethod = PtolemyJavaScript.class
.getMethod(methodName, args[count]);
FunctionObject scriptableFunction = new PtolemyFunctionObject(
methodName, scriptableInstanceMethod, scriptable);
// Make it accessible within the scriptExecutionScope.
_scope.put(methodName, _scope, scriptableFunction);
count++;
}
// This actor is exposed as an object named "actor", unless the actor is restricted.
if (!_restricted) {
Object jsObject = Context.javaToJS(this, _scope);
_scope.put("actor", _scope, jsObject);
}
} catch (Throwable throwable) {
throw new IllegalActionException(this, throwable,
"Failed to create built-in JavaScript methods.");
}
}
/** Execute the wrapup function, if it is defined, and exit the context for this thread.
* @exception IllegalActionException If the parent class throws it.
*/
@Override
public void wrapup() throws IllegalActionException {
try {
// If there are open sockets, disconnect.
if (_openSockets != null && _openSockets.size() > 0) {
for (SocketIO socket : _openSockets) {
socket.disconnect();
}
_openSockets.clear();
}
// If there is a wrapup() function, invoke it.
Object wrapupFunction = _scope.get("wrapup", _scope);
// Synchronize so that this invocation is atomic w.r.t. any callbacks.
synchronized (JavaScript.this) {
if (wrapupFunction instanceof Function) {
((Function) wrapupFunction).call(
Context.getCurrentContext(), _scope, _global,
_EMPTY_ARGS);
}
}
// This is static because the context depends on the current thread.
// So this exits the context associated with the current thread.
Context.exit();
} finally {
_executing = false;
}
super.wrapup();
}
///////////////////////////////////////////////////////////////////
//// protected methods ////
/** Return null, becuase the default type constraints, where output
* types are greater than or equal to all input types, make no sense
* for this actor. Output types need to be set explicitly or inferred
* from backward type inference, and input types need to be set explicitly
* or inferred from forward type inference.
* @return Null.
*/
@Override
protected Set _defaultTypeConstraints() {
return null;
}
///////////////////////////////////////////////////////////////////
//// protected fields ////
/** Compiled JavaScript. */
protected Script _compiledScript;
/** Rhino context. */
protected Context _context;
/** Empty argument list for JavaScript function invocation. */
protected final static Object[] _EMPTY_ARGS = new Object[] {};
/** True while the model is executing (between initialize() and
* wrapup(), inclusive.
*/
protected boolean _executing;
/** Global scope, unpolluted by anything in this actor. */
protected Scriptable _global;
/** Keywords. */
protected static final String[] _JAVASCRIPT_KEYWORDS = new String[] {
"abstract", "as", "boolean", "break", "byte", "case", "catch",
"char", "class", "continue", "const", "debugger", "default",
"delete", "do", "double", "else", "enum", "export", "extends",
"false", "final", "finally", "float", "for", "function", "goto",
"if", "implements", "import", "in", "instanceof", "int",
"interface", "is", "long", "namespace", "native", "new", "null",
"package", "private", "protected", "public", "return", "short",
"static", "super", "switch", "synchronized", "this", "throw",
"throws", "transient", "true", "try", "typeof", "use", "var",
"void", "volatile", "while", "with" };
/** Keywords as a Set. */
protected static final Set _KEYWORDS = new HashSet(
Arrays.asList(_JAVASCRIPT_KEYWORDS));
/** If set to true in the constructor of a base class, then put this actor in "restricted"
* mode. This limits the functionality as described in the class comment.
*/
protected boolean _restricted = false;
/** Scope in which to evaluate scripts. */
protected Scriptable _scope;
///////////////////////////////////////////////////////////////////
//// Private Variables ////
/** True while the actor is firing, false otherwise. */
private boolean _inFire;
/** Buffer for all input tokens before calling the fire function
* in the script. Calls to get in the script will then retrieve
* the value from this buffer instead of actually calling get on
* the port.
*/
private HashMap> _inputTokens = new HashMap>();
/** List of open sockets. */
List _openSockets;
/** Buffer for output tokens that are produced in a call to send
* while the actor is not firing. This makes sure that actors can
* spontaneously produce outputs.
*/
private HashMap>> _outputTokens;
/** Map from timeout ID to pending timeout functions. */
private Map _pendingTimeoutFunctions;
/** Map from timeout time to pending timeout IDs. */
private Map