blob: 62c967932934960663285779effa128ed14299a1 [file] [log] [blame]
/* --COPYRIGHT--,EPL
* Copyright (c) 2008 Texas Instruments and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Texas Instruments - initial implementation
*
* --/COPYRIGHT--*/
package xdc.services.getset;
import java.util.Collection;
import java.util.HashSet;
import org.mozilla.javascript.Callable;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.Scriptable;
import xdc.services.intern.xsr.Value;
/**
* A scheduled function in a Group.
*
* Each Fxn must be idempotent. That is, if the Fxn has just been run then
* running it again immediately should have no additional effect; i.e.,
* Fxn f is idempotent iff running "f(); f()" is always equivalent to running
* just "f()".
*
* Examples of such functions are those that just copy a value from one
* location to another without changing it, or only changing its data
* representation.
*/
public class Fxn implements Callable
{
private Callable body;
private Group group;
private boolean stale = false;
private Object status = null;
private Collection<Field> outputs = null;
private String debugName = null;
/* continuation state for the next invocation */
private Scriptable scope = null;
private Scriptable thisObj = null;
private Object[] args = null;
/**
* Create a new Fxn that will execute the given body, as scheduled
* by the given Group.
*/
public Fxn(Group group, Callable body)
{
this.group = group;
this.body = body;
}
/**
* Get the exit status from the last time this Fxn was executed.
* @return null if the Fxn executed successfully, else an Object.
*/
public Object getStatus()
{
return status;
}
/**
* Mark the given field as a formal input to the Fxn.
* The Fxn will be re-executed whenever the value of the
* field changes.
*/
public Fxn addInput(Value.Observable obj, String name)
{
/* add this fxn as a setter */
Setters.init(obj, name).add(this);
return this;
}
/**
* Mark a given array element as a formal input to the Fxn.
*/
public Fxn addInput(Value.Observable obj, int index)
{
/* add this fxn as a setter */
Setters.init(obj, index).add(this);
return this;
}
/**
* Mark the given field of the object as a formal output
* of the Fxn. A field can't be both an input and output of
* an Fxn -- if so, it will be ignored as an input. This enables
* correct scheduling of Fxns with outputs that are updated
* by read-modify-write, for example if stored as a single
* bit in an integer.
*/
public Fxn addOutput(Value.Observable obj, String name)
{
if (outputs == null) {
outputs = new HashSet<Field>();
}
outputs.add(new Field(obj, name));
return this;
}
/**
* Mark a given array element as a formal output of the Fxn.
*/
public Fxn addOutput(Value.Observable obj, int index)
{
if (outputs == null) {
outputs = new HashSet<Field>();
}
outputs.add(new Field(obj, index));
return this;
}
/**
* Get whether this Fxn needs to be executed.
*/
public boolean getStale()
{
return stale;
}
/**
* Set whether the Fxn needs to be executed, typically because one
* of the inputs has changed value. This method should only be
* called by the Fxn's scheduling Group.
*/
public void setStale(boolean stale)
{
this.stale = stale;
}
/**
* Execute the Fxn body.
*/
public void run()
{
try {
if (GetSet.getDebug()) {
System.out.println("*** calling " + getDebugName());
}
/* get the current context */
Context cx = Context.getCurrentContext();
if (scope == null || thisObj == null || args == null) {
/* set default call parameters */
thisObj = scope = ScriptRuntime.getTopCallScope(cx);
args = new Object[0];
}
/* execute the function body */
body.call(cx, scope, thisObj, args);
/* signal that execution completed successfully */
status = null;
}
catch (RuntimeException e) {
/* report the exception that caused the failure */
if (GetSet.getDebug()) {
e.printStackTrace();
}
/* TODO: should we cancel the defered Fxns in this group? If
* not, it looks like we can double fault during retraction;
* if multiple deferred functions would throw an error we don't
* want to have these functions run with their deferred args.
*/
//group.removeStale();
status = e;
/* rethrow the error - keeps the stack info intact */
throw e;
}
finally {
/* clear the call parameters */
thisObj = scope = null;
args = null;
}
}
/**
* Schedule the Fxn for execution at the next opportunity.
* Will be called with the given scope, thisObj, and args.
* The call Context will be whatever is the current Context
* at the time of the call.
*/
private void schedule(Scriptable scope, Scriptable thisObj, Object[] args)
{
this.scope = scope;
this.thisObj = thisObj;
this.args = args;
group.schedule(this);
}
/**
* A setter that is called whenever one of the inputs of the
* Fxn changes in value. Ignores the change if the input is also
* an output.
*/
public Object call(Context cx, Scriptable scope, Scriptable thisObj,
Object[] args)
{
if (outputs != null) {
/*
* Ignore changes on any of the outputs of the Fxn. When
* an output must be read in order to be updated, it may be
* logged as an input to the Fxn, as well as an output. But
* we don't want to receive notifications in those cases.
*/
Value.Observable obj = (Value.Observable)thisObj;
Object prop = args[0];
Field field = null;
if (prop instanceof String) {
field = new Field(obj, (String)prop);
}
else if (prop instanceof Number) {
field = new Field(obj, ((Number)prop).intValue());
}
if (outputs.contains(field)) {
if (GetSet.getDebug()) {
System.out.println("*** skipping " + getDebugName() +
" because " + prop + " is an output");
}
return null;
}
}
if (GetSet.getDebug()) {
System.out.println("*** scheduling " + getDebugName() +
" because of " + args[0]);
}
/* schedule the Fxn to be run */
schedule(scope, thisObj, args);
return null;
}
/**
* Get a shorthand name for the Fxn, to be used in debug output.
*/
private String getDebugName()
{
if (debugName == null) {
/* choose a default in case we can't find anything better */
debugName = body.toString();
/* try getting the JavaScript function name */
try {
if (body instanceof Scriptable) {
Scriptable scriptable = (Scriptable)body;
Object obj = scriptable.get("name", scriptable);
if (obj instanceof String) {
String name = (String) obj;
if (name.length() > 0) {
debugName = name;
}
}
}
}
catch (Exception e) {
}
}
return debugName;
}
/*
* ======== toString ========
*/
public String toString()
{
return getDebugName();
}
}