package pathways.dao { import flash.utils.getQualifiedClassName; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; //Basically a doubly-linked-list command-pattern-ish structure :-) //Can only be used once! Destroys the list structure as it is executed, to aid in garbage collection. //None of the GC code may be necessary, it is after all 2008. //...but we can debug that some time in the future. public class ChainLoader { //The method to invoke protected var actionFunction : Function; protected var args : Array = []; //The parent / prelude action protected var previousChain : ChainLoader = null; //The child / next action protected var nextChain : ChainLoader = null; //Is this action to be taken immediately, or if false then wait for a successEvent protected var isImmediate : Boolean = true; //To aid in debugging protected static var idSeq : Number = 0; private var debug : Number; private var functionDescription : String; //Constructor used once by the api consumer. Also used by then() and thenOnSuccess() public function ChainLoader(object : Object, methodName : String) { this.actionFunction = bindAction(object, methodName); debug = idSeq++; dbg("created - object = " + object + ", methodName = " + methodName); } public function withArgs(... allArgs) : ChainLoader { this.args = allArgs; return this; } //Initiate the chain. thisVal is the object you wish to be "this" for the function invocations public function go() : void { dbg("go()"); findTopOfChainAndStart(); } //Builds an immediate "next" chain and returns it so the consumer can continue chaining. public function then(object : Object, methodName : String) : ChainLoader { dbg("then() - object = " + object + ", methodName = " + methodName); var newChain : ChainLoader = new ChainLoader(object, methodName); newChain.isImmediate = true; //Set up linked list newChain.previousChain = this; this.nextChain = newChain; //Hand back to the api consumer return newChain; } //Builds a delayed "next" chain and returns it so the consumer can continue chaining. public function thenOnSuccess(object : Object, methodName : String) : ChainLoader { dbg("thenOnSuccess() - object = " + object + ", methodName = " + methodName); //Get a basic next chain var newChain : ChainLoader = then(object, methodName); //Mark for delayed execution newChain.isImmediate = false; //Hand back to the api consumer return newChain; } //Internal chain processing protected function findTopOfChainAndStart() : void { dbg("findTopOfChainAndStart()"); //Call dad if (previousChain != null) previousChain.findTopOfChainAndStart(); else doWork(); } //Kick off our work, and schedule any children to do the same protected function doWork() : void { dbg("doWork()"); previousChain = null; //Might help the Flash GC clean up after one of these babies :) dbg("doWork() - firing actionFunction"); var mightBeAToken : Object = actionFunction(args); //Are we done? if (nextChain != null) { //Nope, have a next chain to kick off. Is it immediate? if (nextChain.isImmediate) { dbg("doWork() - triggering next action"); // all too easy nextChain.doWork(); } else { if (mightBeAToken == null) { throw "Cannot schedule nextOnSuccess with a function that doesn't return a pathways.dao.Token! I got a null"; } else { dbg("doWork() - scheduling next action on resultEvent - token is " + mightBeAToken); if ( ! mightBeAToken is Token) throw "Cannot schedule nextOnSuccess with a function that doesn't return a pathways.dao.Token! I got a " + flash.utils.getQualifiedClassName(mightBeAToken); //Schedule auto-deletion of list if we get a fail. Might not be necessary, but will test later (mightBeAToken as Token).addEventListener(FaultEvent.FAULT, bindFailFunction(nextChain)); //Schedule next action with an anonymous func + closure. (mightBeAToken as Token).addEventListener(ResultEvent.RESULT, bindNextFunction(nextChain)); } } } //Again, may just help prevent memory leaks. Doubly-linked-lists are GC's annoying friend nextChain = null; actionFunction = null; } //For closure! protected function bindFailFunction(nextChain : ChainLoader) : Function { return function() : void { nextChain.fail(); }; } //For closure! protected function bindNextFunction(nextChain : ChainLoader) : Function { return function() : void { nextChain.doWork(); }; } //Builds an anonymous func to do the correct invoke protected function bindAction(object : Object, methodName : String) : Function { //Sanity checks ftw! if (object == null) throw "Can't bind to a function on a null object!"; if (methodName == null) throw "Can't bind to a null methodName"; if (!object.hasOwnProperty(methodName)) throw "Attempt to bind to " + methodName + " on " + object + " failed, method not found."; if (!object[methodName] is Function) throw methodName + " on " + object + " is a " + flash.utils.getQualifiedClassName(object[methodName]) + " not a Function!"; functionDescription = "" + object + "." + methodName + "()"; return function(argumentArray : Array) : Object { dbg("invoking " + functionDescription + " now..."); return (object[methodName] as Function).apply(object, argumentArray); }; } //Called in case of error. protected function fail() : void { // if (failFunction) failFunction(); //Remove references in order to help GC // failFunction = null; actionFunction = null; previousChain = null; if (nextChain != null) nextChain.fail(); nextChain = null; } private function dbg(msg : String) : void { trace("Chainloader #" + debug + ": " + msg); } } }