| /* --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(); |
| } |
| } |