Fixed bug in combineDiff() when diffToAdd==null.

Change-Id: I4df060829a3d6c4bf25ad05d5a9b51ea52c47b0a

--- WeaveCore/src/weave/core/SessionManager.as
+++ WeaveCore/src/weave/core/SessionManager.as
@@ -1,54 +1,54 @@
-/*
-    Weave (Web-based Analysis and Visualization Environment)
-    Copyright (C) 2008-2011 University of Massachusetts Lowell
-
-    This file is a part of Weave.
-
-    Weave is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License, Version 3,
-    as published by the Free Software Foundation.
-
-    Weave is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with Weave.  If not, see <http://www.gnu.org/licenses/>.
+/*
+    Weave (Web-based Analysis and Visualization Environment)
+    Copyright (C) 2008-2011 University of Massachusetts Lowell
+
+    This file is a part of Weave.
+
+    Weave is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License, Version 3,
+    as published by the Free Software Foundation.
+
+    Weave is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Weave.  If not, see <http://www.gnu.org/licenses/>.
 */

 package weave.core
-{
-	import flash.display.DisplayObject;
-	import flash.display.DisplayObjectContainer;
-	import flash.events.Event;
-	import flash.events.EventPhase;
-	import flash.system.Capabilities;
-	import flash.utils.Dictionary;
-	import flash.utils.describeType;
-	import flash.utils.getQualifiedClassName;
-	import flash.utils.getTimer;
-
-	import mx.binding.utils.BindingUtils;
-	import mx.binding.utils.ChangeWatcher;
-	import mx.core.UIComponent;
-	import mx.core.mx_internal;
-	import mx.rpc.AsyncResponder;
-	import mx.rpc.AsyncToken;
-	import mx.utils.ObjectUtil;
-
-	import weave.api.WeaveAPI;
-	import weave.api.core.ICallbackCollection;
-	import weave.api.core.IDisposableObject;
-	import weave.api.core.ILinkableCompositeObject;
-	import weave.api.core.ILinkableDynamicObject;
-	import weave.api.core.ILinkableHashMap;
-	import weave.api.core.ILinkableObject;
-	import weave.api.core.ILinkableVariable;
-	import weave.api.core.ISessionManager;
-	import weave.api.reportError;
-	import weave.compiler.StandardLib;
-	import weave.utils.Dictionary2D;
+{
+	import flash.display.DisplayObject;
+	import flash.display.DisplayObjectContainer;
+	import flash.events.Event;
+	import flash.events.EventPhase;
+	import flash.system.Capabilities;
+	import flash.utils.Dictionary;
+	import flash.utils.describeType;
+	import flash.utils.getQualifiedClassName;
+	import flash.utils.getTimer;
+
+	import mx.binding.utils.BindingUtils;
+	import mx.binding.utils.ChangeWatcher;
+	import mx.core.UIComponent;
+	import mx.core.mx_internal;
+	import mx.rpc.AsyncResponder;
+	import mx.rpc.AsyncToken;
+	import mx.utils.ObjectUtil;
+
+	import weave.api.WeaveAPI;
+	import weave.api.core.ICallbackCollection;
+	import weave.api.core.IDisposableObject;
+	import weave.api.core.ILinkableCompositeObject;
+	import weave.api.core.ILinkableDynamicObject;
+	import weave.api.core.ILinkableHashMap;
+	import weave.api.core.ILinkableObject;
+	import weave.api.core.ILinkableVariable;
+	import weave.api.core.ISessionManager;
+	import weave.api.reportError;
+	import weave.compiler.StandardLib;
+	import weave.utils.Dictionary2D;

 	/**
 	 * This is a collection of core functions in the Weave session framework.
@@ -131,10 +131,10 @@
 			{
 				// remember this child-parent relationship
 				childToParentDictionaryMap[linkableChild][linkableParent] = true;
-				parentToChildDictionaryMap[linkableParent][linkableChild] = true;
+				parentToChildDictionaryMap[linkableParent][linkableChild] = true;

 				// make child changes trigger parent callbacks
-				var parentCC:ICallbackCollection = getCallbackCollection(linkableParent as ILinkableObject);
+				var parentCC:ICallbackCollection = getCallbackCollection(linkableParent as ILinkableObject);
 				// set alwaysCallLast=true for triggering parent callbacks, so parent will be triggered after all the other child callbacks
 				getCallbackCollection(linkableChild).addImmediateCallback(linkableParent, parentCC.triggerCallbacks, false, true); // parent-child relationship
 			}
@@ -183,7 +183,7 @@
 				ownerToChildDictionaryMap[disposableParent][disposableChild] = true;
 				// initialize the parent dictionary for this child
 				childToParentDictionaryMap[disposableChild] = new Dictionary(true); // weak links to be GC-friendly
-			}
+			}
 			return disposableChild;
 		}

@@ -195,17 +195,17 @@
 		 */
 		public function unregisterLinkableChild(parent:ILinkableObject, child:ILinkableObject):void
 		{
-			if (childToParentDictionaryMap[child])
-				delete childToParentDictionaryMap[child][parent];
-			if (parentToChildDictionaryMap[parent])
-				delete parentToChildDictionaryMap[parent][child];
+			if (childToParentDictionaryMap[child])
+				delete childToParentDictionaryMap[child][parent];
+			if (parentToChildDictionaryMap[parent])
+				delete parentToChildDictionaryMap[parent][child];
 			getCallbackCollection(child).removeCallback(getCallbackCollection(parent).triggerCallbacks);
 		}

-		/**
-		 * This function will add or remove child objects from the session state of a parent.  Use this function
-		 * with care because the child will no longer be "sessioned."  The child objects will continue to trigger the
-		 * callbacks of the parent object, but they will no longer be considered a part of the parent's session state.
+		/**
+		 * This function will add or remove child objects from the session state of a parent.  Use this function
+		 * with care because the child will no longer be "sessioned."  The child objects will continue to trigger the
+		 * callbacks of the parent object, but they will no longer be considered a part of the parent's session state.
 		 * If you are not careful, this will break certain functionalities that depend on the session state of the parent.
 		 * @param parent A parent that the specified child objects were previously registered with.
 		 * @param child The child object to remove from the session state of the parent.
@@ -216,14 +216,14 @@
 			{
 				reportError("SessionManager.removeLinkableChildrenFromSessionState(): Parameters to this function cannot be null.");
 				return;
-			}
+			}
 			if (childToParentDictionaryMap[child] !== undefined && childToParentDictionaryMap[child][parent])
 				childToParentDictionaryMap[child][parent] = false;
 			if (parentToChildDictionaryMap[parent] !== undefined && parentToChildDictionaryMap[parent][child])
 				parentToChildDictionaryMap[parent][child] = false;
 		}

-		/**
+		/**
 		 * @private
 		 * This function will return all the child objects that have been registered with a parent.
 		 * @param parent A parent object to get the registered children of.
@@ -249,80 +249,80 @@
 			return childToOwnerMap[child] as ILinkableObject;
 		}

-		/**
-		 * @param root The linkable object to be placed at the root node of the tree.
-		 * @return A tree of nodes with the properties "label", "object", and "children"
-		 */
-		public function getSessionStateTree(object:ILinkableObject, objectName:String, objectTypeFilter:*=null):Object
-		{
-			var result:Object = {label: objectName, object: object, children: []};
-
-			var names:Array = [];
-			var childObject:ILinkableObject;
-			var tempObject:Object;
-			var ignoreList:Dictionary = new Dictionary(true);
-			if (object is ILinkableHashMap)
-			{
-				names = (object as ILinkableHashMap).getNames();
-
-				var sessionObjects:Array = (object as ILinkableHashMap).getObjects();
-
-				for (var i:int = 0; i < names.length; i++)
-				{
-					childObject = sessionObjects[i];
-					if (childToParentDictionaryMap[childObject] && childToParentDictionaryMap[childObject][object])
-					{
-						// don't include duplicate siblings
-						if (ignoreList[childObject] != undefined)
-							continue;
-						ignoreList[childObject] = true;
-
-						// get subtree
-						tempObject = getSessionStateTree(childObject,names[i],objectTypeFilter);
-						if (tempObject != null)
-							result.children.push(tempObject);
-					}
-				}
-			}
-			else
-			{
-				if (object is ILinkableDynamicObject)
-				{
-					// do not show static object in tree
-					names = (object as ILinkableDynamicObject).globalName ? [] : ["internalObject"];
-				}
-				else if (object)
-				{
-					names = (WeaveAPI.SessionManager as SessionManager).getLinkablePropertyNames(object);
-				}
-				for (var j:int = 0; j < names.length; j++)
-				{
-					childObject = object[names[j]];
-					if (!childObject)
-						continue;
-					if (/* object is ILinkableDynamicObject ||  */childToParentDictionaryMap[childObject] && childToParentDictionaryMap[childObject][object])
-					{
-						// don't include duplicate siblings
-						if (ignoreList[childObject] != undefined)
-							continue;
-						ignoreList[childObject] = true;
-
-						// get subtree
-						tempObject = getSessionStateTree(childObject,names[j],objectTypeFilter);
-						if (tempObject != null)
-							result.children.push(tempObject);
-					}
-				}
-			}
-
-			if (result.children.length == 0)
-				result.children = null;
-			if (objectTypeFilter == null)
-				return result;
-			if (result.children == null && !(object is objectTypeFilter))
-				return null;
-			return result;
-		}
+		/**
+		 * @param root The linkable object to be placed at the root node of the tree.
+		 * @return A tree of nodes with the properties "label", "object", and "children"
+		 */
+		public function getSessionStateTree(object:ILinkableObject, objectName:String, objectTypeFilter:*=null):Object
+		{
+			var result:Object = {label: objectName, object: object, children: []};
+
+			var names:Array = [];
+			var childObject:ILinkableObject;
+			var tempObject:Object;
+			var ignoreList:Dictionary = new Dictionary(true);
+			if (object is ILinkableHashMap)
+			{
+				names = (object as ILinkableHashMap).getNames();
+
+				var sessionObjects:Array = (object as ILinkableHashMap).getObjects();
+
+				for (var i:int = 0; i < names.length; i++)
+				{
+					childObject = sessionObjects[i];
+					if (childToParentDictionaryMap[childObject] && childToParentDictionaryMap[childObject][object])
+					{
+						// don't include duplicate siblings
+						if (ignoreList[childObject] != undefined)
+							continue;
+						ignoreList[childObject] = true;
+
+						// get subtree
+						tempObject = getSessionStateTree(childObject,names[i],objectTypeFilter);
+						if (tempObject != null)
+							result.children.push(tempObject);
+					}
+				}
+			}
+			else
+			{
+				if (object is ILinkableDynamicObject)
+				{
+					// do not show static object in tree
+					names = (object as ILinkableDynamicObject).globalName ? [] : ["internalObject"];
+				}
+				else if (object)
+				{
+					names = (WeaveAPI.SessionManager as SessionManager).getLinkablePropertyNames(object);
+				}
+				for (var j:int = 0; j < names.length; j++)
+				{
+					childObject = object[names[j]];
+					if (!childObject)
+						continue;
+					if (/* object is ILinkableDynamicObject ||  */childToParentDictionaryMap[childObject] && childToParentDictionaryMap[childObject][object])
+					{
+						// don't include duplicate siblings
+						if (ignoreList[childObject] != undefined)
+							continue;
+						ignoreList[childObject] = true;
+
+						// get subtree
+						tempObject = getSessionStateTree(childObject,names[j],objectTypeFilter);
+						if (tempObject != null)
+							result.children.push(tempObject);
+					}
+				}
+			}
+
+			if (result.children.length == 0)
+				result.children = null;
+			if (objectTypeFilter == null)
+				return result;
+			if (result.children == null && !(object is objectTypeFilter))
+				return null;
+			return result;
+		}

 		/**
 		 * This function will copy the session state from one sessioned object to another.
@@ -351,25 +351,25 @@

 			// special cases:
 			if (linkableObject is ILinkableVariable)
-			{
-				var lv:ILinkableVariable = linkableObject as ILinkableVariable;
-				if (removeMissingDynamicObjects == false && newState && getQualifiedClassName(newState) == 'Object')
-				{
-					// apply diff
-					var oldState:Object = lv.getSessionState();
-					for (var key:String in newState)
-					{
-						var value:Object = newState[key];
-						//if (typeof(value) == 'object')
-						//	todo: recursive call
-						//else
-						oldState[key] = value;
-					}
-					lv.setSessionState(oldState);
-				}
-				else
-				{
-					lv.setSessionState(newState);
+			{
+				var lv:ILinkableVariable = linkableObject as ILinkableVariable;
+				if (removeMissingDynamicObjects == false && newState && getQualifiedClassName(newState) == 'Object')
+				{
+					// apply diff
+					var oldState:Object = lv.getSessionState();
+					for (var key:String in newState)
+					{
+						var value:Object = newState[key];
+						//if (typeof(value) == 'object')
+						//	todo: recursive call
+						//else
+						oldState[key] = value;
+					}
+					lv.setSessionState(oldState);
+				}
+				else
+				{
+					lv.setSessionState(newState);
 				}
 				return;
 			}
@@ -386,8 +386,8 @@
 			var objectCC:ICallbackCollection = getCallbackCollection(linkableObject);
 			objectCC.delayCallbacks();

-			// set session state
-			var name:String;
+			// set session state
+			var name:String;
 			for each (name in getLinkablePropertyNames(linkableObject))
 			{
 				if (!newState.hasOwnProperty(name))
@@ -411,18 +411,18 @@
 					continue;

 				setSessionState(property, newState[name], removeMissingDynamicObjects);
-			}
-
-			// pass deprecated session state to deprecated setters
-			for each (name in getDeprecatedSetterNames(linkableObject))
-				if (newState.hasOwnProperty(name))
-					linkableObject[name] = newState[name];
+			}
+
+			// pass deprecated session state to deprecated setters
+			for each (name in getDeprecatedSetterNames(linkableObject))
+				if (newState.hasOwnProperty(name))
+					linkableObject[name] = newState[name];

 			// resume callbacks after setting session state
 			objectCC.resumeCallbacks();
 		}

-		private const _getSessionStateIgnoreList:Dictionary = new Dictionary(true); // keeps track of which objects are currently being traversed
+		private const _getSessionStateIgnoreList:Dictionary = new Dictionary(true); // keeps track of which objects are currently being traversed

 		/**
 		 * @param linkableObject An object containing sessioned properties (sessioned objects may be nested).
@@ -439,16 +439,16 @@
 			var result:Object = null;

 			// special cases (explicit session state)
-			if (linkableObject is ILinkableVariable)
-			{
-				result = (linkableObject as ILinkableVariable).getSessionState();
-			}
+			if (linkableObject is ILinkableVariable)
+			{
+				result = (linkableObject as ILinkableVariable).getSessionState();
+			}
 			else if (linkableObject is ILinkableCompositeObject)
-			{
+			{
 				result = (linkableObject as ILinkableCompositeObject).getSessionState();
 			}
-			else
-			{
+			else
+			{
 				// implicit session state
 				// first pass: get property names
 				var propertyNames:Array = getLinkablePropertyNames(linkableObject);
@@ -458,7 +458,7 @@
 				var i:int;
 				//trace("getting session state for "+getQualifiedClassName(sessionedObject),"propertyNames="+propertyNames);
 				for (i = 0; i < propertyNames.length; i++)
-				{
+				{
 					var name:String = propertyNames[i];
 					try
 					{
@@ -466,7 +466,7 @@
 						property = linkableObject[name] as ILinkableObject;
 					}
 					catch (e:Error)
-					{
+					{
 						reportError('Unable to get property "'+name+'" of class "'+getQualifiedClassName(linkableObject)+'"');
 					}
 					// first pass: set result[name] to the ILinkableObject
@@ -481,34 +481,34 @@
 						resultProperties.push(property);
 					}
 					else
-					{
-						/*
+					{
+						/*
 						if (property != null)
-							trace("ignoring duplicate object:",name,property);
+							trace("ignoring duplicate object:",name,property);
 						*/
 					}
-				}
+				}
 				// special case if there are no child objects -- return null
-				if (resultNames.length > 0)
-				{
+				if (resultNames.length > 0)
+				{
 					// second pass: get values from property names
 					result = new Object();
 					for (i = 0; i < resultNames.length; i++)
-					{
-						var value:Object = getSessionState(resultProperties[i]);
-						property = resultProperties[i] as ILinkableObject;
-						// do not include objects that have a null implicit session state (no child objects)
-						if (value == null && !(property is ILinkableVariable) && !(property is ILinkableCompositeObject))
+					{
+						var value:Object = getSessionState(resultProperties[i]);
+						property = resultProperties[i] as ILinkableObject;
+						// do not include objects that have a null implicit session state (no child objects)
+						if (value == null && !(property is ILinkableVariable) && !(property is ILinkableCompositeObject))
 							continue;
 						result[resultNames[i]] = value;
 						//trace("getState",getQualifiedClassName(sessionedObject),resultNames[i],result[resultNames[i]]);
-					}
-				}
-			}
-
-			_getSessionStateIgnoreList[linkableObject] = undefined;
-
-			return result;
+					}
+				}
+			}
+
+			_getSessionStateIgnoreList[linkableObject] = undefined;
+
+			return result;
 		}

 		/**
@@ -518,30 +518,30 @@
 		/**
 		 * This maps a qualified class name to an Array of names of deprecated setter functions contained in that class.
 		 */
-		private const classNameToDeprecatedSetterNamesMap:Object = new Object();
-
-		/**
-		 * @private
-		 */
-		private function getDeprecatedSetterNames(linkableObject:ILinkableObject):Array
-		{
+		private const classNameToDeprecatedSetterNamesMap:Object = new Object();
+
+		/**
+		 * @private
+		 */
+		private function getDeprecatedSetterNames(linkableObject:ILinkableObject):Array
+		{
 			if (linkableObject == null)
 			{
 				reportError("SessionManager.getDeprecatedSetterNames(): linkableObject cannot be null.");
 				return [];
-			}
-
-			var className:String = getQualifiedClassName(linkableObject);
-			var names:Array = classNameToDeprecatedSetterNamesMap[className] as Array;
-			if (names == null)
-			{
-				names = [];
-				for each (var tag:XML in describeType(linkableObject).accessor.(@access != "readonly"))
-					if (tag.metadata.(@name == "Deprecated").length() > 0)
-						names.push(tag.attribute("name"));
-				names.sort();
-				classNameToDeprecatedSetterNamesMap[className] = names;
-			}
+			}
+
+			var className:String = getQualifiedClassName(linkableObject);
+			var names:Array = classNameToDeprecatedSetterNamesMap[className] as Array;
+			if (names == null)
+			{
+				names = [];
+				for each (var tag:XML in describeType(linkableObject).accessor.(@access != "readonly"))
+					if (tag.metadata.(@name == "Deprecated").length() > 0)
+						names.push(tag.attribute("name"));
+				names.sort();
+				classNameToDeprecatedSetterNamesMap[className] = names;
+			}
 			return names;
 		}

@@ -550,7 +550,7 @@
 		 * @param linkableObject An object containing sessioned properties.
 		 * @return An Array containing the names of the sessioned properties of that object class.
 		 */
-		public function getLinkablePropertyNames(linkableObject:ILinkableObject):Array
+		public function getLinkablePropertyNames(linkableObject:ILinkableObject):Array
 		{
 			if (linkableObject == null)
 			{
@@ -563,7 +563,7 @@
 			if (propertyNames == null)
 			{
 				propertyNames = [];
-				// iterate over the public properties, saving the names of the ones that implement ILinkableObject
+				// iterate over the public properties, saving the names of the ones that implement ILinkableObject
 				var xml:XML = describeType(linkableObject);
 				//trace(xml.toXMLString());
 				for each (var tags:XMLList in [xml.constant, xml.variable, xml.accessor.(@access != "writeonly")])
@@ -603,129 +603,129 @@
 		 * This maps a parent ILinkableObject to a Dictionary, which maps each of its registered child ILinkableObjects to a value of true if the child should appear in the session state automatically or false if not.
 		 * Example: parentToChildDictionaryMap[parent][child] == true
 		 */
-		private const parentToChildDictionaryMap:Dictionary = new Dictionary(true); // use weak links to be GC-friendly
-
-		/**
-		 * This function will return all the descendant objects that implement ILinkableObject.
-		 * If the filter parameter is specified, the results will contain only those objects that extend or implement the filter class.
-		 * @param root A root object to get the descendants of.
-		 * @param filter An optional Class definition which will be used to filter the results.
-		 * @return An Array containing a list of descendant objects.
-		 */
-		public function getLinkableDescendants(root:ILinkableObject, filter:Class = null):Array
-		{
-			if (root == null)
-			{
-				reportError("SessionManager.getLinkableDescendants(): root cannot be null.");
-				return [];
-			}
-
-			var result:Array = [];
-			internalGetDescendants(result, root, filter, new Dictionary(true), int.MAX_VALUE);
-			// don't include root object
-			if (result.length > 0 && result[0] == root)
-				result.shift();
-			return result;
-		}
-		private function internalGetDescendants(output:Array, root:ILinkableObject, filter:Class, ignoreList:Dictionary, depth:int):void
-		{
-			if (root == null || ignoreList[root] !== undefined)
-				return;
-			ignoreList[root] = true;
-			if (filter == null || root is filter)
-				output.push(root);
-			if (--depth <= 0)
-				return;
-
-			for (var object:Object in parentToChildDictionaryMap[root])
-			{
-				internalGetDescendants(output, object as ILinkableObject, filter, ignoreList, depth);
-			}
-		}
-
-		private const _d2dOwnerTask:Dictionary2D = new Dictionary2D(true, true);
-		private const _d2dTaskOwner:Dictionary2D = new Dictionary2D(true, true);
-		private const _dBusyTraversal:Dictionary = new Dictionary(true); // ILinkableObject -> Boolean
-		private const _aBusyTraversal:Array = [];
-
-		/**
-		 * This will assign an asynchronous task to a linkable object so that <code>linkableObjectIsBusy(busyObject)</code>
-		 * will return true until all assigned tasks are unassigned using <code>unassignBusyTask(taskToken)</code>.
-		 * @param taskToken A token representing an asynchronous task.  If this is an AsyncToken, a responder will be added that will automatically call unassignBusyTask(taskToken) on success or failure.
-		 * @param busyObject The object that is busy waiting for the task to complete.
-		 */
-		public function assignBusyTask(taskToken:Object, busyObject:ILinkableObject):void
-		{
-			if (taskToken is AsyncToken)
-				(taskToken as AsyncToken).addResponder(new AsyncResponder(unassignAsyncToken, unassignAsyncToken, taskToken));
-
-			_d2dOwnerTask.set(busyObject, taskToken, true);
-			_d2dTaskOwner.set(taskToken, busyObject, true);
-		}
-
-		private function unassignAsyncToken(event:Event, token:Object):void
-		{
-			unassignBusyTask(token);
-		}
-
-		/**
-		 * This will unassign an asynchronous task from all linkable objects it has been previously assigned to.
-		 * @param taskToken A token representing an asynchronous task.
-		 */
-		public function unassignBusyTask(taskToken:Object):void
-		{
-			var dOwner:Dictionary = _d2dTaskOwner.dictionary[taskToken];
-			delete _d2dTaskOwner.dictionary[taskToken];
-			for (var owner:Object in dOwner)
-				delete _d2dOwnerTask.dictionary[owner][taskToken];
-		}
-
-		/**
-		 * This checks if any asynchronous tasks have been assigned to a linkable object or any of its registered descendants.
-		 * @param linkableObject The object to check.
-		 * @return A value of true if any asynchronous tasks have been assigned to the object.
-		 */
-		public function linkableObjectIsBusy(linkableObject:ILinkableObject):Boolean
-		{
-			var busy:Boolean = false;
-
-			_aBusyTraversal.push(linkableObject);
-			_dBusyTraversal[linkableObject] = true;
-
-			outerLoop: for (var i:int = 0; i < _aBusyTraversal.length; i++)
-			{
-				linkableObject = _aBusyTraversal[i] as ILinkableObject;
-
-				// if the object is assigned a task, it's busy
-				for (var task:Object in _d2dOwnerTask.dictionary[linkableObject])
-				{
-					busy = true;
-					break outerLoop;
-				}
-
-				// see if children are busy
-				var dChild:Dictionary = parentToChildDictionaryMap[linkableObject];
-				for (var child:Object in dChild)
-				{
-					// queue all the children that haven't been queued yet
-					if (!_dBusyTraversal[child])
-					{
-						_aBusyTraversal.push(child);
-						_dBusyTraversal[child] = true;
-					}
-				}
-			}
-
-			// reset traversal dictionary for next time
-			for each (linkableObject in _aBusyTraversal)
-				_dBusyTraversal[linkableObject] = false;
-
-			// reset traversal queue for next time
-			_aBusyTraversal.length = 0;
-
-			return busy;
-		}
-
+		private const parentToChildDictionaryMap:Dictionary = new Dictionary(true); // use weak links to be GC-friendly
+
+		/**
+		 * This function will return all the descendant objects that implement ILinkableObject.
+		 * If the filter parameter is specified, the results will contain only those objects that extend or implement the filter class.
+		 * @param root A root object to get the descendants of.
+		 * @param filter An optional Class definition which will be used to filter the results.
+		 * @return An Array containing a list of descendant objects.
+		 */
+		public function getLinkableDescendants(root:ILinkableObject, filter:Class = null):Array
+		{
+			if (root == null)
+			{
+				reportError("SessionManager.getLinkableDescendants(): root cannot be null.");
+				return [];
+			}
+
+			var result:Array = [];
+			internalGetDescendants(result, root, filter, new Dictionary(true), int.MAX_VALUE);
+			// don't include root object
+			if (result.length > 0 && result[0] == root)
+				result.shift();
+			return result;
+		}
+		private function internalGetDescendants(output:Array, root:ILinkableObject, filter:Class, ignoreList:Dictionary, depth:int):void
+		{
+			if (root == null || ignoreList[root] !== undefined)
+				return;
+			ignoreList[root] = true;
+			if (filter == null || root is filter)
+				output.push(root);
+			if (--depth <= 0)
+				return;
+
+			for (var object:Object in parentToChildDictionaryMap[root])
+			{
+				internalGetDescendants(output, object as ILinkableObject, filter, ignoreList, depth);
+			}
+		}
+
+		private const _d2dOwnerTask:Dictionary2D = new Dictionary2D(true, true);
+		private const _d2dTaskOwner:Dictionary2D = new Dictionary2D(true, true);
+		private const _dBusyTraversal:Dictionary = new Dictionary(true); // ILinkableObject -> Boolean
+		private const _aBusyTraversal:Array = [];
+
+		/**
+		 * This will assign an asynchronous task to a linkable object so that <code>linkableObjectIsBusy(busyObject)</code>
+		 * will return true until all assigned tasks are unassigned using <code>unassignBusyTask(taskToken)</code>.
+		 * @param taskToken A token representing an asynchronous task.  If this is an AsyncToken, a responder will be added that will automatically call unassignBusyTask(taskToken) on success or failure.
+		 * @param busyObject The object that is busy waiting for the task to complete.
+		 */
+		public function assignBusyTask(taskToken:Object, busyObject:ILinkableObject):void
+		{
+			if (taskToken is AsyncToken)
+				(taskToken as AsyncToken).addResponder(new AsyncResponder(unassignAsyncToken, unassignAsyncToken, taskToken));
+
+			_d2dOwnerTask.set(busyObject, taskToken, true);
+			_d2dTaskOwner.set(taskToken, busyObject, true);
+		}
+
+		private function unassignAsyncToken(event:Event, token:Object):void
+		{
+			unassignBusyTask(token);
+		}
+
+		/**
+		 * This will unassign an asynchronous task from all linkable objects it has been previously assigned to.
+		 * @param taskToken A token representing an asynchronous task.
+		 */
+		public function unassignBusyTask(taskToken:Object):void
+		{
+			var dOwner:Dictionary = _d2dTaskOwner.dictionary[taskToken];
+			delete _d2dTaskOwner.dictionary[taskToken];
+			for (var owner:Object in dOwner)
+				delete _d2dOwnerTask.dictionary[owner][taskToken];
+		}
+
+		/**
+		 * This checks if any asynchronous tasks have been assigned to a linkable object or any of its registered descendants.
+		 * @param linkableObject The object to check.
+		 * @return A value of true if any asynchronous tasks have been assigned to the object.
+		 */
+		public function linkableObjectIsBusy(linkableObject:ILinkableObject):Boolean
+		{
+			var busy:Boolean = false;
+
+			_aBusyTraversal.push(linkableObject);
+			_dBusyTraversal[linkableObject] = true;
+
+			outerLoop: for (var i:int = 0; i < _aBusyTraversal.length; i++)
+			{
+				linkableObject = _aBusyTraversal[i] as ILinkableObject;
+
+				// if the object is assigned a task, it's busy
+				for (var task:Object in _d2dOwnerTask.dictionary[linkableObject])
+				{
+					busy = true;
+					break outerLoop;
+				}
+
+				// see if children are busy
+				var dChild:Dictionary = parentToChildDictionaryMap[linkableObject];
+				for (var child:Object in dChild)
+				{
+					// queue all the children that haven't been queued yet
+					if (!_dBusyTraversal[child])
+					{
+						_aBusyTraversal.push(child);
+						_dBusyTraversal[child] = true;
+					}
+				}
+			}
+
+			// reset traversal dictionary for next time
+			for each (linkableObject in _aBusyTraversal)
+				_dBusyTraversal[linkableObject] = false;
+
+			// reset traversal queue for next time
+			_aBusyTraversal.length = 0;
+
+			return busy;
+		}
+

 		/**
 		 * This maps an ILinkableObject to a ICallbackCollection associated with it.
@@ -750,67 +750,67 @@
 			var objectCC:ICallbackCollection = linkableObjectToCallbackCollectionMap[linkableObject] as ICallbackCollection;
 			if (objectCC == null)
 			{
-				objectCC = new CallbackCollection();
-				if (CallbackCollection.debug)
+				objectCC = new CallbackCollection();
+				if (CallbackCollection.debug)
 					(objectCC as CallbackCollection)._linkableObject = linkableObject;
-				linkableObjectToCallbackCollectionMap[linkableObject] = objectCC;
-
-				// Make sure UIComponents get registered with linkable owners because MXML developers
-				// may forget to do so, since it's not simple or intuitive in MXML.
-				if (linkableObject is UIComponent)
-				{
-					var component:UIComponent = linkableObject as UIComponent;
-					if (!_registerUIComponent(component))
-						component.addEventListener(Event.ADDED, _registerUIComponentLater);
-				}
+				linkableObjectToCallbackCollectionMap[linkableObject] = objectCC;
+
+				// Make sure UIComponents get registered with linkable owners because MXML developers
+				// may forget to do so, since it's not simple or intuitive in MXML.
+				if (linkableObject is UIComponent)
+				{
+					var component:UIComponent = linkableObject as UIComponent;
+					if (!_registerUIComponent(component))
+						component.addEventListener(Event.ADDED, _registerUIComponentLater);
+				}
 			}
 			return objectCC;
-		}
-
-		/**
-		 * This function is an event listener that in turn calls _registerUIComponent.
-		 * @param event The event dispatched by the UIComponent to be passed to _registerUIComponent.
-		 */
-		private function _registerUIComponentLater(event:Event):void
-		{
-			if (event.target == event.currentTarget)
-			{
-				var component:UIComponent = event.currentTarget as UIComponent;
-				if (_registerUIComponent(component))
-					component.removeEventListener(event.type, _registerUIComponentLater, event.eventPhase == EventPhase.CAPTURING_PHASE);
-			}
-		}
-
-		/**
-		 * This function will register a UIComponent/ILinkableObject as a disposable child of an ancestral
-		 * DisplayObjectContainer/ILinkableObject if it has no linkable owner yet.  This makes sure that the
-		 * component is disposed of when its ancestor is disposed of.
-		 * @param linkableComponent A UIComponent that implements ILinkableObject.
-		 * @return true if the component has a linkable owner, either before or after this function is called.
-		 */
-		private function _registerUIComponent(linkableComponent:UIComponent):Boolean
-		{
-			if (objectWasDisposed(linkableComponent))
-			{
-				reportError('UIComponent running _registerUIComponent after being disposed');
-				return true; // so the event listener will be removed
-			}
-			var owner:ILinkableObject = childToOwnerMap[linkableComponent] as ILinkableObject;
-			if (owner == null)
-			{
-				var parent:DisplayObjectContainer = linkableComponent.parent;
-				while (parent)
-				{
-					if (parent is ILinkableObject)
-					{
-						registerDisposableChild(parent, linkableComponent);
-						return true; // component has a linkable owner now
-					}
-					parent = parent.parent;
-				}
-				return false; // component does not have a linkable owner yet
-			}
-			return true; // component already has a linkable owner
+		}
+
+		/**
+		 * This function is an event listener that in turn calls _registerUIComponent.
+		 * @param event The event dispatched by the UIComponent to be passed to _registerUIComponent.
+		 */
+		private function _registerUIComponentLater(event:Event):void
+		{
+			if (event.target == event.currentTarget)
+			{
+				var component:UIComponent = event.currentTarget as UIComponent;
+				if (_registerUIComponent(component))
+					component.removeEventListener(event.type, _registerUIComponentLater, event.eventPhase == EventPhase.CAPTURING_PHASE);
+			}
+		}
+
+		/**
+		 * This function will register a UIComponent/ILinkableObject as a disposable child of an ancestral
+		 * DisplayObjectContainer/ILinkableObject if it has no linkable owner yet.  This makes sure that the
+		 * component is disposed of when its ancestor is disposed of.
+		 * @param linkableComponent A UIComponent that implements ILinkableObject.
+		 * @return true if the component has a linkable owner, either before or after this function is called.
+		 */
+		private function _registerUIComponent(linkableComponent:UIComponent):Boolean
+		{
+			if (objectWasDisposed(linkableComponent))
+			{
+				reportError('UIComponent running _registerUIComponent after being disposed');
+				return true; // so the event listener will be removed
+			}
+			var owner:ILinkableObject = childToOwnerMap[linkableComponent] as ILinkableObject;
+			if (owner == null)
+			{
+				var parent:DisplayObjectContainer = linkableComponent.parent;
+				while (parent)
+				{
+					if (parent is ILinkableObject)
+					{
+						registerDisposableChild(parent, linkableComponent);
+						return true; // component has a linkable owner now
+					}
+					parent = parent.parent;
+				}
+				return false; // component does not have a linkable owner yet
+			}
+			return true; // component already has a linkable owner
 		}

 		/**
@@ -842,9 +842,9 @@
 		 */
 		public function disposeObjects(object:Object, ...moreObjects):void
 		{
-			if (object != null && !_disposedObjectsMap[object])
-			{
-				_disposedObjectsMap[object] = true;
+			if (object != null && !_disposedObjectsMap[object])
+			{
+				_disposedObjectsMap[object] = true;

 				try
 				{
@@ -877,11 +877,11 @@
 					if (childToParentDictionaryMap[linkableObject] !== undefined)
 					{
 						// remove the parent-to-child mappings
-						for (var parent:Object in childToParentDictionaryMap[linkableObject])
-							if (parentToChildDictionaryMap[parent] !== undefined)
-								delete parentToChildDictionaryMap[parent][linkableObject];
+						for (var parent:Object in childToParentDictionaryMap[linkableObject])
+							if (parentToChildDictionaryMap[parent] !== undefined)
+								delete parentToChildDictionaryMap[parent][linkableObject];
 						// remove child-to-parent mapping
-						delete childToParentDictionaryMap[linkableObject];
+						delete childToParentDictionaryMap[linkableObject];
 					}

 					// unregister from owner
@@ -909,17 +909,17 @@
 					{
 						// clear the pointers to the child dictionaries for this object
 						delete ownerToChildDictionaryMap[linkableObject];
-						delete parentToChildDictionaryMap[linkableObject];
+						delete parentToChildDictionaryMap[linkableObject];
 						// dispose of the children this object owned
 						for (var child:Object in children)
 							disposeObjects(child);
 					}

 					// FOR DEBUGGING PURPOSES
-					if (Capabilities.isDebugger)
-					{
+					if (Capabilities.isDebugger)
+					{
 						var error:Error = new Error("This is the stack trace from when the object was previously disposed.");
-						objectCC.addImmediateCallback(null, function():void { debugDisposedObject(linkableObject, error); } );
+						objectCC.addImmediateCallback(null, function():void { debugDisposedObject(linkableObject, error); } );
 					}
 				}

@@ -968,7 +968,7 @@
 			var parents:Array = []; for (obj in childToParentDictionaryMap[disposedObject]) { parents.push[obj]; }
 			var children:Array = []; for (obj in parentToChildDictionaryMap[disposedObject]) { children.push[obj]; }
 			var sessionState:Object = getSessionState(disposedObject);
-
+
 			// ADD A BREAKPOINT HERE TO DIAGNOSE THE PROBLEM
 			var msg:String = "WARNING: An object triggered callbacks after previously being disposed. " + getQualifiedClassName(disposedObject);
 			if (disposedObject is ILinkableVariable)
@@ -1066,11 +1066,11 @@

-		/**
-		 * This maps destination and source ILinkableObjects to a function like:
-		 *     function():void { setSessionState(destination, getSessionState(source), true); }
-		 */
-		private const linkFunctionCache:Dictionary2D = new Dictionary2D(true, true);
+		/**
+		 * This maps destination and source ILinkableObjects to a function like:
+		 *     function():void { setSessionState(destination, getSessionState(source), true); }
+		 */
+		private const linkFunctionCache:Dictionary2D = new Dictionary2D(true, true);
 		/**
 		 * This will link the session state of two ILinkableObjects.
 		 * The session state of 'primary' will be copied over to 'secondary' after linking them.
@@ -1088,15 +1088,15 @@
 			{
 				reportError("Warning! Attempt to link session state of an object with itself");
 				return;
-			}
-			if (linkFunctionCache.get(primary, secondary) is Function)
-				return; // already linked
-
-			var setPrimary:Function = function():void { setSessionState(primary, getSessionState(secondary), true); };
-			var setSecondary:Function = function():void { setSessionState(secondary, getSessionState(primary), true); };
-
-			linkFunctionCache.set(primary, secondary, setPrimary);
-			linkFunctionCache.set(secondary, primary, setSecondary);
+			}
+			if (linkFunctionCache.get(primary, secondary) is Function)
+				return; // already linked
+
+			var setPrimary:Function = function():void { setSessionState(primary, getSessionState(secondary), true); };
+			var setSecondary:Function = function():void { setSessionState(secondary, getSessionState(primary), true); };
+
+			linkFunctionCache.set(primary, secondary, setPrimary);
+			linkFunctionCache.set(secondary, primary, setSecondary);

 			// when secondary changes, copy from secondary to primary
 			getCallbackCollection(secondary).addImmediateCallback(primary, setPrimary);
@@ -1114,12 +1114,12 @@
 			{
 				reportError("SessionManager.unlinkObjects(): Parameters to this function cannot be null.");
 				return;
-			}
-
-			var setFirst:Function = linkFunctionCache.remove(first, second) as Function;
+			}
+
+			var setFirst:Function = linkFunctionCache.remove(first, second) as Function;
 			var setSecond:Function = linkFunctionCache.remove(second, first) as Function;

-			getCallbackCollection(second).removeCallback(setFirst);
+			getCallbackCollection(second).removeCallback(setFirst);
 			getCallbackCollection(first).removeCallback(setSecond);
 		}

@@ -1129,24 +1129,24 @@

 		/******************************************************
 		 * linking sessioned objects with bindable properties
-		 ******************************************************/
-
-		private const VALUE_NOT_ACCEPTED:String = lang('Value not accepted.'); // errorString used by linkBindableProperty
-
-		/*private function debugLink(linkVal:Object, bindVal:Object, useLinkableBefore:Boolean, useLinkableAfter:Boolean, callingLater:Boolean):void
-		{
-			var link:String = (useLinkableBefore && useLinkableAfter ? 'LINK' : 'link') + '(' + ObjectUtil.toString(linkVal) + ')';
-			var bind:String = (!useLinkableBefore && !useLinkableAfter ? 'BIND' : 'bind') + '(' + ObjectUtil.toString(bindVal) + ')';
-			var str:String = link + ', ' + bind;
-			if (useLinkableBefore && !useLinkableAfter)
-				str = link + ' = ' + bind;
-			if (!useLinkableBefore && useLinkableAfter)
-				str = bind + ' = ' + link;
-			if (callingLater)
-				str += ' (callingLater)';
-
-			trace(str);
-		}*/
+		 ******************************************************/
+
+		private const VALUE_NOT_ACCEPTED:String = lang('Value not accepted.'); // errorString used by linkBindableProperty
+
+		/*private function debugLink(linkVal:Object, bindVal:Object, useLinkableBefore:Boolean, useLinkableAfter:Boolean, callingLater:Boolean):void
+		{
+			var link:String = (useLinkableBefore && useLinkableAfter ? 'LINK' : 'link') + '(' + ObjectUtil.toString(linkVal) + ')';
+			var bind:String = (!useLinkableBefore && !useLinkableAfter ? 'BIND' : 'bind') + '(' + ObjectUtil.toString(bindVal) + ')';
+			var str:String = link + ', ' + bind;
+			if (useLinkableBefore && !useLinkableAfter)
+				str = link + ' = ' + bind;
+			if (!useLinkableBefore && useLinkableAfter)
+				str = bind + ' = ' + link;
+			if (callingLater)
+				str += ' (callingLater)';
+
+			trace(str);
+		}*/

 		/**
 		 * This function will link the session state of an ILinkableVariable to a bindable property of an object.
@@ -1154,8 +1154,8 @@
 		 * @param linkableVariable An ILinkableVariable to link to a bindable property.
 		 * @param bindableParent An object with a bindable property.
 		 * @param bindablePropertyName The variable name of the bindable property.
-		 * @param delay The delay to use before setting the linkable variable to reflect a change in the bindable property while the bindableParent has focus.
-		 * @param onlyWhenFocused If this is set to true and the bindableParent is a UIComponent, the bindable value will only be copied to the linkableVariable when the component has focus.
+		 * @param delay The delay to use before setting the linkable variable to reflect a change in the bindable property while the bindableParent has focus.
+		 * @param onlyWhenFocused If this is set to true and the bindableParent is a UIComponent, the bindable value will only be copied to the linkableVariable when the component has focus.
 		 */
 		public function linkBindableProperty(linkableVariable:ILinkableVariable, bindableParent:Object, bindablePropertyName:String, delay:uint = 0, onlyWhenFocused:Boolean = false):void
 		{
@@ -1169,164 +1169,164 @@
 			{
 				reportError('linkBindableProperty(): Unable to access property "'+bindablePropertyName+'" in class '+getQualifiedClassName(bindableParent));
 				return;
-			}
-
-			// unlink in case previously linked
+			}
+
+			// unlink in case previously linked
 			unlinkBindableProperty(linkableVariable, bindableParent, bindablePropertyName);
-
-			// initialize dictionaries
+
+			// initialize dictionaries
 			if (_watcherMap[linkableVariable] === undefined)
 				_watcherMap[linkableVariable] = new Dictionary(true);
 			if (_watcherMap[linkableVariable][bindableParent] === undefined)
-				_watcherMap[linkableVariable][bindableParent] = new Object();
-
+				_watcherMap[linkableVariable][bindableParent] = new Object();
+
 			var callbackCollection:ICallbackCollection = getCallbackCollection(linkableVariable);
-			var watcher:ChangeWatcher = null;
-			var useLinkableValue:Boolean = true;
-			var callLaterTime:int = 0;
-			var uiComponent:UIComponent = bindableParent as UIComponent;
-			var recursiveCall:Boolean = false;
+			var watcher:ChangeWatcher = null;
+			var useLinkableValue:Boolean = true;
+			var callLaterTime:int = 0;
+			var uiComponent:UIComponent = bindableParent as UIComponent;
+			var recursiveCall:Boolean = false;
 			// a function that takes zero parameters and sets the bindable value.
-			var synchronize:Function = function(firstParam:* = undefined, callingLater:Boolean = false):void
-			{
-				// unlink if linkableVariable was disposed of
-				if (objectWasDisposed(linkableVariable))
-				{
-					unlinkBindableProperty(linkableVariable, bindableParent, bindablePropertyName);
-					return;
-				}
-
-				/*debugLink(
-					linkableVariable.getSessionState(),
-					firstParam===undefined ? bindableParent[bindablePropertyName] : firstParam,
-					useLinkableValue,
-					firstParam===undefined,
-					callingLater
-				);*/
-
-				// If bindableParent has focus:
-				// When linkableVariable changes, update bindable value only when focus is lost (callLaterTime = int.MAX_VALUE).
-				// When bindable value changes, update linkableVariable after a delay.
-
-				if (!callingLater)
-				{
-					// remember which value changed last -- the linkable one or the bindable one
-					useLinkableValue = firstParam === undefined; // true when called from linkable variable grouped callback
-					// if we're not calling later and there is already a timestamp, just wait for the callLater to trigger
-					if (callLaterTime)
-					{
-						// if there is a callLater waiting to trigger, update the target time
-						callLaterTime = useLinkableValue ? int.MAX_VALUE : getTimer() + delay;
-
-						//trace('\tdelaying the timer some more');
-
-						return;
-					}
-				}
-
-				// if the bindable value is not a boolean and the bindable parent has focus, delay synchronization
-				var bindableValue:Object = bindableParent[bindablePropertyName];
-				if (!(bindableValue is Boolean))
-				{
-					if (uiComponent)
-					{
-						var obj:DisplayObject = uiComponent.getFocus();
-						if (obj && uiComponent.contains(obj)) // has focus
-						{
-							if (linkableVariable is LinkableVariable)
-							{
-								if ((linkableVariable as LinkableVariable).verifyValue(bindableValue))
-								{
-									// clear previous error string
-									if (uiComponent.errorString == VALUE_NOT_ACCEPTED)
-										uiComponent.errorString = '';
-								}
-								else
-								{
-									// show error string if not already shown
-									if (!uiComponent.errorString)
-										uiComponent.errorString = VALUE_NOT_ACCEPTED;
-								}
-							}
-
-							var currentTime:int = getTimer();
-
-							// if we're not calling later, set the target time (int.MAX_VALUE means delay until focus is lost)
-							if (!callingLater)
-								callLaterTime = useLinkableValue ? int.MAX_VALUE : currentTime + delay;
-
-							// if we haven't reached the target time yet or callbacks are delayed, call later
-							if (currentTime < callLaterTime)
-							{
-								uiComponent.callLater(synchronize, [firstParam, true]);
-								return;
-							}
-						}
-						else if (onlyWhenFocused && !callingLater)
-						{
-							// component does not have focus, so ignore the bindableValue.
-							return;
-						}
-
-						// otherwise, synchronize now
-						// clear saved time stamp when we are about to synchronize
-						callLaterTime = 0;
-					}
-				}
-
-				// if the linkable variable's callbacks are delayed, delay synchronization
-				if (getCallbackCollection(linkableVariable).callbacksAreDelayed)
-				{
-					WeaveAPI.StageUtils.callLater(linkableVariable, synchronize, [firstParam, true], WeaveAPI.TASK_PRIORITY_IMMEDIATE);
-					return;
-				}
-
-				// synchronize
-				if (useLinkableValue)
-				{
-					var linkableValue:Object = linkableVariable.getSessionState();
-					if ((bindableValue is Number) != (linkableValue is Number))
-					{
-						try {
-							if (linkableValue is Number)
-							{
-								if (isNaN(linkableValue as Number))
-									linkableValue = '';
-								else
-									linkableValue = '' + linkableValue;
-							}
-							else
-							{
-								linkableVariable.setSessionState(Number(linkableValue));
-								linkableValue = linkableVariable.getSessionState();
+			var synchronize:Function = function(firstParam:* = undefined, callingLater:Boolean = false):void
+			{
+				// unlink if linkableVariable was disposed of
+				if (objectWasDisposed(linkableVariable))
+				{
+					unlinkBindableProperty(linkableVariable, bindableParent, bindablePropertyName);
+					return;
+				}
+
+				/*debugLink(
+					linkableVariable.getSessionState(),
+					firstParam===undefined ? bindableParent[bindablePropertyName] : firstParam,
+					useLinkableValue,
+					firstParam===undefined,
+					callingLater
+				);*/
+
+				// If bindableParent has focus:
+				// When linkableVariable changes, update bindable value only when focus is lost (callLaterTime = int.MAX_VALUE).
+				// When bindable value changes, update linkableVariable after a delay.
+
+				if (!callingLater)
+				{
+					// remember which value changed last -- the linkable one or the bindable one
+					useLinkableValue = firstParam === undefined; // true when called from linkable variable grouped callback
+					// if we're not calling later and there is already a timestamp, just wait for the callLater to trigger
+					if (callLaterTime)
+					{
+						// if there is a callLater waiting to trigger, update the target time
+						callLaterTime = useLinkableValue ? int.MAX_VALUE : getTimer() + delay;
+
+						//trace('\tdelaying the timer some more');
+
+						return;
+					}
+				}
+
+				// if the bindable value is not a boolean and the bindable parent has focus, delay synchronization
+				var bindableValue:Object = bindableParent[bindablePropertyName];
+				if (!(bindableValue is Boolean))
+				{
+					if (uiComponent)
+					{
+						var obj:DisplayObject = uiComponent.getFocus();
+						if (obj && uiComponent.contains(obj)) // has focus
+						{
+							if (linkableVariable is LinkableVariable)
+							{
+								if ((linkableVariable as LinkableVariable).verifyValue(bindableValue))
+								{
+									// clear previous error string
+									if (uiComponent.errorString == VALUE_NOT_ACCEPTED)
+										uiComponent.errorString = '';
+								}
+								else
+								{
+									// show error string if not already shown
+									if (!uiComponent.errorString)
+										uiComponent.errorString = VALUE_NOT_ACCEPTED;
+								}
 							}
-						} catch (e:Error) { }
-					}
-					if (bindableValue != linkableValue)
-						bindableParent[bindablePropertyName] = linkableValue;
-
-					// clear previous error string
-					if (uiComponent && linkableVariable is LinkableVariable && uiComponent.errorString == VALUE_NOT_ACCEPTED)
-						uiComponent.errorString = '';
-				}
-				else
-				{
-					var prevCount:uint = callbackCollection.triggerCounter;
-					linkableVariable.setSessionState(bindableValue);
-					// Always synchronize after setting the linkableVariable because there may
-					// be constraints on the session state that will prevent the callbacks
-					// from triggering if the bindable value does not match those constraints.
-					// This makes UIComponents update to the real value after they lose focus.
-					if (callbackCollection.triggerCounter == prevCount && !recursiveCall)
-					{
-						// avoid infinite recursion in the case where the new value is not accepted by a verifier function
-						recursiveCall = true;
-						synchronize();
-						recursiveCall = false;
-					}
+
+							var currentTime:int = getTimer();
+
+							// if we're not calling later, set the target time (int.MAX_VALUE means delay until focus is lost)
+							if (!callingLater)
+								callLaterTime = useLinkableValue ? int.MAX_VALUE : currentTime + delay;
+
+							// if we haven't reached the target time yet or callbacks are delayed, call later
+							if (currentTime < callLaterTime)
+							{
+								uiComponent.callLater(synchronize, [firstParam, true]);
+								return;
+							}
+						}
+						else if (onlyWhenFocused && !callingLater)
+						{
+							// component does not have focus, so ignore the bindableValue.
+							return;
+						}
+
+						// otherwise, synchronize now
+						// clear saved time stamp when we are about to synchronize
+						callLaterTime = 0;
+					}
+				}
+
+				// if the linkable variable's callbacks are delayed, delay synchronization
+				if (getCallbackCollection(linkableVariable).callbacksAreDelayed)
+				{
+					WeaveAPI.StageUtils.callLater(linkableVariable, synchronize, [firstParam, true], WeaveAPI.TASK_PRIORITY_IMMEDIATE);
+					return;
+				}
+
+				// synchronize
+				if (useLinkableValue)
+				{
+					var linkableValue:Object = linkableVariable.getSessionState();
+					if ((bindableValue is Number) != (linkableValue is Number))
+					{
+						try {
+							if (linkableValue is Number)
+							{
+								if (isNaN(linkableValue as Number))
+									linkableValue = '';
+								else
+									linkableValue = '' + linkableValue;
+							}
+							else
+							{
+								linkableVariable.setSessionState(Number(linkableValue));
+								linkableValue = linkableVariable.getSessionState();
+							}
+						} catch (e:Error) { }
+					}
+					if (bindableValue != linkableValue)
+						bindableParent[bindablePropertyName] = linkableValue;
+
+					// clear previous error string
+					if (uiComponent && linkableVariable is LinkableVariable && uiComponent.errorString == VALUE_NOT_ACCEPTED)
+						uiComponent.errorString = '';
+				}
+				else
+				{
+					var prevCount:uint = callbackCollection.triggerCounter;
+					linkableVariable.setSessionState(bindableValue);
+					// Always synchronize after setting the linkableVariable because there may
+					// be constraints on the session state that will prevent the callbacks
+					// from triggering if the bindable value does not match those constraints.
+					// This makes UIComponents update to the real value after they lose focus.
+					if (callbackCollection.triggerCounter == prevCount && !recursiveCall)
+					{
+						// avoid infinite recursion in the case where the new value is not accepted by a verifier function
+						recursiveCall = true;
+						synchronize();
+						recursiveCall = false;
+					}
 				}
 			};
-			// Copy session state over to bindable property now, before calling BindingUtils.bindSetter(),
+			// Copy session state over to bindable property now, before calling BindingUtils.bindSetter(),
 			// because that will copy from the bindable property to the sessioned property.
 			synchronize();
 			watcher = BindingUtils.bindSetter(synchronize, bindableParent, bindablePropertyName);
@@ -1372,309 +1372,339 @@
 		/**
 		 * This maps a ChangeWatcher object to a function that was added as a callback to the corresponding ILinkableVariable.
 		 */
-		private const _watcherToSynchronizeFunctionMap:Dictionary = new Dictionary(); // use weak links to be GC-friendly
-
-
-
-
-
-		/*******************
-		 * Computing diffs
-		 *******************/
-
-
-		internal static const DIFF_DELETE:String = 'delete';
-
-		/**
-		 * This function computes the diff of two session states.
-		 * @param oldState The source session state.
-		 * @param newState The destination session state.
-		 * @return A patch that generates the destination session state when applied to the source session state, or undefined if the two states are equivalent.
-		 */
-		public function computeDiff(oldState:Object, newState:Object):*
-		{
-			var type:String = typeof(oldState); // the type of null is 'object'
-			var diffValue:*;
-
-			// special case if types differ
-			if (typeof(newState) != type)
-				return newState;
-
-			if (type == 'xml')
-			{
-				throw new Error("XML is not supported as a primitive session state type.");
-			}
-			else if (type == 'number')
-			{
-				if (isNaN(oldState as Number) && isNaN(newState as Number))
-					return undefined; // no diff
-
-				if (oldState != newState)
-					return newState;
-
-				return undefined; // no diff
-			}
-			else if (oldState === null || newState === null || type != 'object') // other primitive value
-			{
-				if (oldState !== newState) // no type-casting
-					return newState;
-
-				return undefined; // no diff
-			}
-			else if (oldState is Array && newState is Array)
-			{
-				// create an array of new DynamicState objects for all new names followed by missing old names
-				var i:int;
-				var typedState:Object;
-				var changeDetected:Boolean = false;
-
-				// create oldLookup
-				var oldLookup:Object = {};
-				var objectName:String;
-				var className:String;
-				var sessionState:Object;
-				for (i = 0; i < oldState.length; i++)
-				{
-					typedState = oldState[i];
-
-					// if we see a string, assume both are String Arrays.
-					if (typedState is String || typedState is Array)
-					{
-						if (StandardLib.arrayCompare(oldState as Array, newState as Array) == 0)
-							return undefined; // no diff
-						return newState;
-					}
-
-					//note: there is no error checking here for typedState
-					objectName = typedState[DynamicState.OBJECT_NAME];
-					// use '' instead of null to avoid "null"
-					oldLookup[objectName || ''] = typedState;
-				}
-				if (oldState.length != newState.length)
-					changeDetected = true;
-
-				// create new Array with new DynamicState objects
-				var result:Array = [];
-				for (i = 0; i < newState.length; i++)
-				{
-					typedState = newState[i];
-
-					// if we see a string, assume both are String Arrays.
-					if (typedState is String || typedState is Array)
-					{
-						if (StandardLib.arrayCompare(oldState as Array, newState as Array) == 0)
-							return undefined; // no diff
-						return newState; // TODO: same object pointer.. potential problem?
-					}
-
-					//note: there is no error checking here for typedState
-					objectName = typedState[DynamicState.OBJECT_NAME];
-					className = typedState[DynamicState.CLASS_NAME];
-					sessionState = typedState[DynamicState.SESSION_STATE];
-					var oldTypedState:Object = oldLookup[objectName || ''];
-					delete oldLookup[objectName || '']; // remove it from the lookup because it's already been handled
-
-					// If the object specified in newState does not exist in oldState, we don't need to do anything further.
-					// If the class is the same as before, then we can save a diff instead of the entire session state.
-					// If the class changed, we can't save only a diff -- we need to keep the entire session state.
-					// Replace the sessionState in the new DynamicState object with the diff.
-					if (oldTypedState != null && oldTypedState[DynamicState.CLASS_NAME] == className)
-					{
-						className = null; // no change
-						diffValue = computeDiff(oldTypedState[DynamicState.SESSION_STATE], sessionState);
-						if (diffValue === undefined)
-						{
-							// Since the class name is the same and the session state is the same,
-							// we only need to specify that this name is still present.
-							result.push(objectName);
-
-							if (!changeDetected && oldState[i][DynamicState.OBJECT_NAME] != objectName)
-								changeDetected = true;
-
-							continue;
-						}
-						sessionState = diffValue;
-					}
-
-					// save in new array and remove from lookup
-					result.push(new DynamicState(objectName, className, sessionState));
-					changeDetected = true;
-				}
-
-				// Anything remaining in the lookup does not appear in newState.
-				// Add DynamicState entries with an invalid className ("delete") to convey that each of these objects should be removed.
-				for (objectName in oldLookup)
-				{
-					result.push(new DynamicState(objectName, DIFF_DELETE));
-					changeDetected = true;
-				}
-
-				if (changeDetected)
-					return result;
-
-				return undefined; // no diff
-			}
-			else // nested object
-			{
-				var diff:* = undefined; // start with no diff
-
-				// find old properties that changed value
-				for (var oldName:String in oldState)
-				{
-					diffValue = computeDiff(oldState[oldName], newState[oldName]);
-					if (diffValue !== undefined)
-					{
-						if (!diff)
-							diff = {};
-						diff[oldName] = diffValue;
-					}
-				}
-
-				// find new properties
-				for (var newName:String in newState)
-				{
-					if (oldState[newName] === undefined)
-					{
-						if (!diff)
-							diff = {};
-						diff[newName] = newState[newName]; // TODO: same object pointer.. potential problem?
-					}
-				}
-
-				return diff;
-			}
-		}
-
-		/**
-		 * This modifies an existing diff to include an additional diff.
-		 * @param baseDiff The base diff which will be modified to include an additional diff.
-		 * @param diffToAdd The diff to add to the base diff.  This diff will not be modified.
-		 * @return The modified baseDiff, or a new diff object if baseDiff is a primitive value.
-		 */
-		public function combineDiff(baseDiff:Object, diffToAdd:Object):Object
-		{
-			// special case for no change
-			if (diffToAdd === null)
-				return baseDiff;
-
-			var baseType:String = typeof(baseDiff); // the type of null is 'object'
-			var diffType:String = typeof(diffToAdd);
-
-			// special cases
-			if (baseDiff == null || baseType != diffType || baseType != 'object')
-			{
-				if (diffType == 'object') // not a primitive, so make a copy
-					baseDiff = ObjectUtil.copy(diffToAdd);
-				else
-					baseDiff = diffToAdd;
-			}
-			else if (baseDiff is Array && diffToAdd is Array)
-			{
-				var typedState:Object;
-				var i:int;
-
-				// If a non-String, non-Array is found, treat both as Arrays of DynamicState objects
-				var isTyped:Boolean = false;
-				checkArrays: for each (var array:Array in arguments) // [baseDiff, diffToAdd]
-				{
-					for each (typedState in array)
-					{
-						if (!(typedState is String || typedState is Array))
-						{
-							isTyped = true;
-							break checkArrays;
-						}
-					}
-				}
-				if (isTyped)
-				{
-					var objectName:String;
-
-					// create lookup: objectName -> old diff entry
-					// temporarily turn baseDiff into an Array of object names
-					var baseLookup:Object = {};
-					for (i = 0; i < baseDiff.length; i++)
-					{
-						typedState = baseDiff[i];
-						// note: no error checking for typedState
-						if (typedState is String || typedState == null)
-							objectName = typedState as String;
-						else
-							objectName = typedState[DynamicState.OBJECT_NAME] as String;
-						baseLookup[objectName] = typedState;
-						// temporarily turn baseDiff into an Array of object names
-						baseDiff[i] = objectName;
-					}
-					// apply each typedState diff appearing in diffToAdd
-					for (i = 0; i < diffToAdd.length; i++)
-					{
-						typedState = diffToAdd[i];
-						// note: no error checking for typedState
-						if (typedState is String || typedState == null)
-							objectName = typedState as String;
-						else
-							objectName = typedState[DynamicState.OBJECT_NAME] as String;
-
-						// adjust names list so this name appears at the end
-						if (baseLookup.hasOwnProperty(objectName))
-						{
-							for (var j:int = (baseDiff as Array).indexOf(objectName); j < baseDiff.length - 1; j++)
-								baseDiff[j] = baseDiff[j + 1];
-							baseDiff[baseDiff.length - 1] = objectName;
-						}
-						else
-						{
-							baseDiff.push(objectName);
-						}
-
-						// apply diff
-						var oldTypedState:Object = baseLookup[objectName];
-						if (oldTypedState is String || oldTypedState == null)
-						{
-							if (typedState is String || typedState == null)
-								baseLookup[objectName] = typedState; // avoid unnecessary function call overhead
-							else
-								baseLookup[objectName] = ObjectUtil.copy(typedState);
-						}
-						else if (!(typedState is String || typedState == null)) // update dynamic state
-						{
-							var className:String = typedState[DynamicState.CLASS_NAME];
-							// if new className is different and not null, start with a fresh typedState diff
-							if (className && className != oldTypedState[DynamicState.CLASS_NAME])
-							{
-								baseLookup[objectName] = ObjectUtil.copy(typedState);
-							}
-							else // className hasn't changed, so combine the diffs
-							{
-								oldTypedState[DynamicState.SESSION_STATE] = combineDiff(oldTypedState[DynamicState.SESSION_STATE], typedState[DynamicState.SESSION_STATE]);
-							}
-						}
-					}
-					// change baseDiff back from names to typed states
-					for (i = 0; i < baseDiff.length; i++)
-						baseDiff[i] = baseLookup[baseDiff[i]];
-				}
-				else // not typed session state
-				{
-					// overwrite old Array with new Array's values
-					baseDiff.length = diffToAdd.length;
-					for (i = diffToAdd.length - 1; i >= 0; i--)
-					{
-						var value:Object = diffToAdd[i];
-						if (value is String)
-							baseDiff[i] = value; // avoid function call overhead
-						else
-							baseDiff[i] = combineDiff(baseDiff[i], value);
-					}
-				}
-			}
-			else // nested object
-			{
-				for (var newName:String in diffToAdd)
-					baseDiff[newName] = combineDiff(baseDiff[newName], diffToAdd[newName]);
-			}
-
-			return baseDiff;
+		private const _watcherToSynchronizeFunctionMap:Dictionary = new Dictionary(); // use weak links to be GC-friendly
+
+
+
+
+
+		/*******************
+		 * Computing diffs
+		 *******************/
+
+
+		internal static const DIFF_DELETE:String = 'delete';
+
+		/**
+		 * This function computes the diff of two session states.
+		 * @param oldState The source session state.
+		 * @param newState The destination session state.
+		 * @return A patch that generates the destination session state when applied to the source session state, or undefined if the two states are equivalent.
+		 */
+		public function computeDiff(oldState:Object, newState:Object):*
+		{
+			var type:String = typeof(oldState); // the type of null is 'object'
+			var diffValue:*;
+
+			// special case if types differ
+			if (typeof(newState) != type)
+				return newState;
+
+			if (type == 'xml')
+			{
+				throw new Error("XML is not supported as a primitive session state type.");
+			}
+			else if (type == 'number')
+			{
+				if (isNaN(oldState as Number) && isNaN(newState as Number))
+					return undefined; // no diff
+
+				if (oldState != newState)
+					return newState;
+
+				return undefined; // no diff
+			}
+			else if (oldState === null || newState === null || type != 'object') // other primitive value
+			{
+				if (oldState !== newState) // no type-casting
+					return newState;
+
+				return undefined; // no diff
+			}
+			else if (oldState is Array && newState is Array)
+			{
+				// create an array of new DynamicState objects for all new names followed by missing old names
+				var i:int;
+				var typedState:Object;
+				var changeDetected:Boolean = false;
+
+				// create oldLookup
+				var oldLookup:Object = {};
+				var objectName:String;
+				var className:String;
+				var sessionState:Object;
+				for (i = 0; i < oldState.length; i++)
+				{
+					typedState = oldState[i];
+
+					// if we see a string in oldState, assume both oldState and newState are String Arrays.
+					if (typedState is String || typedState is Array)
+					{
+						if (StandardLib.arrayCompare(oldState as Array, newState as Array) == 0)
+							return undefined; // no diff
+						return newState;
+					}
+
+					//note: there is no error checking here for typedState
+					objectName = typedState[DynamicState.OBJECT_NAME];
+					// use '' instead of null to avoid "null"
+					oldLookup[objectName || ''] = typedState;
+				}
+				if (oldState.length != newState.length)
+					changeDetected = true;
+
+				// create new Array with new DynamicState objects
+				var result:Array = [];
+				for (i = 0; i < newState.length; i++)
+				{
+					typedState = newState[i];
+
+					// if we see a string, assume both are String Arrays.
+					if (typedState is String || typedState is Array)
+					{
+						if (StandardLib.arrayCompare(oldState as Array, newState as Array) == 0)
+							return undefined; // no diff
+						return newState; // TODO: same object pointer.. potential problem?
+					}
+
+					// assume everthing is typed session state
+					//note: there is no error checking here for typedState
+					objectName = typedState[DynamicState.OBJECT_NAME];
+					className = typedState[DynamicState.CLASS_NAME];
+					sessionState = typedState[DynamicState.SESSION_STATE];
+					var oldTypedState:Object = oldLookup[objectName || ''];
+					delete oldLookup[objectName || '']; // remove it from the lookup because it's already been handled
+
+					// If the object specified in newState does not exist in oldState, we don't need to do anything further.
+					// If the class is the same as before, then we can save a diff instead of the entire session state.
+					// If the class changed, we can't save only a diff -- we need to keep the entire session state.
+					// Replace the sessionState in the new DynamicState object with the diff.
+					if (oldTypedState != null && oldTypedState[DynamicState.CLASS_NAME] == className)
+					{
+						className = null; // no change
+						diffValue = computeDiff(oldTypedState[DynamicState.SESSION_STATE], sessionState);
+						if (diffValue === undefined)
+						{
+							// Since the class name is the same and the session state is the same,
+							// we only need to specify that this name is still present.
+							result.push(objectName);
+
+							if (!changeDetected && oldState[i][DynamicState.OBJECT_NAME] != objectName)
+								changeDetected = true;
+
+							continue;
+						}
+						sessionState = diffValue;
+					}
+
+					// save in new array and remove from lookup
+					result.push(new DynamicState(objectName, className, sessionState));
+					changeDetected = true;
+				}
+
+				// Anything remaining in the lookup does not appear in newState.
+				// Add DynamicState entries with an invalid className ("delete") to convey that each of these objects should be removed.
+				for (objectName in oldLookup)
+				{
+					result.push(new DynamicState(objectName, DIFF_DELETE));
+					changeDetected = true;
+				}
+
+				if (changeDetected)
+					return result;
+
+				return undefined; // no diff
+			}
+			else // nested object
+			{
+				var diff:* = undefined; // start with no diff
+
+				// find old properties that changed value
+				for (var oldName:String in oldState)
+				{
+					diffValue = computeDiff(oldState[oldName], newState[oldName]);
+					if (diffValue !== undefined)
+					{
+						if (!diff)
+							diff = {};
+						diff[oldName] = diffValue;
+					}
+				}
+
+				// find new properties
+				for (var newName:String in newState)
+				{
+					if (oldState[newName] === undefined)
+					{
+						if (!diff)
+							diff = {};
+						diff[newName] = newState[newName]; // TODO: same object pointer.. potential problem?
+					}
+				}
+
+				return diff;
+			}
+		}
+
+		/**
+		 * This modifies an existing diff to include an additional diff.
+		 * @param baseDiff The base diff which will be modified to include an additional diff.
+		 * @param diffToAdd The diff to add to the base diff.  This diff will not be modified.
+		 * @return The modified baseDiff, or a new diff object if baseDiff is a primitive value.
+		 */
+		public function combineDiff(baseDiff:Object, diffToAdd:Object):Object
+		{
+			var baseType:String = typeof(baseDiff); // the type of null is 'object'
+			var diffType:String = typeof(diffToAdd);
+
+			// special cases
+			if (baseDiff == null || diffToAdd == null || baseType != diffType || baseType != 'object')
+			{
+				if (diffType == 'object') // not a primitive, so make a copy
+					baseDiff = ObjectUtil.copy(diffToAdd);
+				else
+					baseDiff = diffToAdd;
+			}
+			else if (baseDiff is Array && diffToAdd is Array)
+			{
+				var typedState:Object;
+				var i:int;
+
+				// If a non-String, non-Array is found, treat both as Arrays of DynamicState objects
+				var isTyped:Boolean = false;
+				checkArrays: for each (var array:Array in arguments) // [baseDiff, diffToAdd]
+				{
+					for each (typedState in array)
+					{
+						if (!(typedState is String || typedState is Array))
+						{
+							isTyped = true;
+							break checkArrays;
+						}
+					}
+				}
+				if (isTyped)
+				{
+					var objectName:String;
+
+					// create lookup: objectName -> old diff entry
+					// temporarily turn baseDiff into an Array of object names
+					var baseLookup:Object = {};
+					for (i = 0; i < baseDiff.length; i++)
+					{
+						typedState = baseDiff[i];
+						// note: no error checking for typedState
+						if (typedState is String || typedState == null)
+							objectName = typedState as String;
+						else
+							objectName = typedState[DynamicState.OBJECT_NAME] as String;
+						baseLookup[objectName] = typedState;
+						// temporarily turn baseDiff into an Array of object names
+						baseDiff[i] = objectName;
+					}
+					// apply each typedState diff appearing in diffToAdd
+					for (i = 0; i < diffToAdd.length; i++)
+					{
+						typedState = diffToAdd[i];
+						// note: no error checking for typedState
+						if (typedState is String || typedState == null)
+							objectName = typedState as String;
+						else
+							objectName = typedState[DynamicState.OBJECT_NAME] as String;
+
+						// adjust names list so this name appears at the end
+						if (baseLookup.hasOwnProperty(objectName))
+						{
+							for (var j:int = (baseDiff as Array).indexOf(objectName); j < baseDiff.length - 1; j++)
+								baseDiff[j] = baseDiff[j + 1];
+							baseDiff[baseDiff.length - 1] = objectName;
+						}
+						else
+						{
+							baseDiff.push(objectName);
+						}
+
+						// apply diff
+						var oldTypedState:Object = baseLookup[objectName];
+						if (oldTypedState is String || oldTypedState == null)
+						{
+							if (typedState is String || typedState == null)
+								baseLookup[objectName] = typedState; // avoid unnecessary function call overhead
+							else
+								baseLookup[objectName] = ObjectUtil.copy(typedState);
+						}
+						else if (!(typedState is String || typedState == null)) // update dynamic state
+						{
+							var className:String = typedState[DynamicState.CLASS_NAME];
+							// if new className is different and not null, start with a fresh typedState diff
+							if (className && className != oldTypedState[DynamicState.CLASS_NAME])
+							{
+								baseLookup[objectName] = ObjectUtil.copy(typedState);
+							}
+							else // className hasn't changed, so combine the diffs
+							{
+								oldTypedState[DynamicState.SESSION_STATE] = combineDiff(oldTypedState[DynamicState.SESSION_STATE], typedState[DynamicState.SESSION_STATE]);
+							}
+						}
+					}
+					// change baseDiff back from names to typed states
+					for (i = 0; i < baseDiff.length; i++)
+						baseDiff[i] = baseLookup[baseDiff[i]];
+				}
+				else // not typed session state
+				{
+					// overwrite old Array with new Array's values
+					baseDiff.length = diffToAdd.length;
+					for (i = diffToAdd.length - 1; i >= 0; i--)
+					{
+						var value:Object = diffToAdd[i];
+						if (value is String)
+							baseDiff[i] = value; // avoid function call overhead
+						else
+							baseDiff[i] = combineDiff(baseDiff[i], value);
+					}
+				}
+			}
+			else // nested object
+			{
+				for (var newName:String in diffToAdd)
+					baseDiff[newName] = combineDiff(baseDiff[newName], diffToAdd[newName]);
+			}
+
+			return baseDiff;
+		}
+
+		public function testDiff():void
+		{
+			var states:Array = [
+				[
+					{objectName: 'a', className: 'aClass', sessionState: 'aVal'},
+					{objectName: 'b', className: 'bClass', sessionState: 'bVal1'}
+				],
+				[
+					{objectName: 'b', className: 'bClass', sessionState: 'bVal2'},
+					{objectName: 'a', className: 'aClass', sessionState: 'aVal'}
+				],
+				[
+					{objectName: 'a', className: 'aNewClass', sessionState: 'aVal'},
+					{objectName: 'b', className: 'bClass', sessionState: null}
+				],
+				[
+					{objectName: 'b', className: 'bClass', sessionState: null}
+				]
+			];
+			var diffs:Array = [];
+			var combined:Array = [];
+			var baseDiff:* = null;
+			for (var i:int = 1; i < states.length; i++)
+			{
+				var diff:* = computeDiff(states[i - 1], states[i]);
+				diffs.push(diff);
+				baseDiff = combineDiff(baseDiff, diff);
+				combined.push(ObjectUtil.copy(baseDiff));
+			}
+			trace('diffs',ObjectUtil.toString(diffs));
+			trace('combined',ObjectUtil.toString(combined));
 		}
 	}
 }


Code clean

Code clean

--- collect-flex/collect-flex-client/src/main/flex/org/openforis/collect/presenter/ErrorListPopUpPresenter.as
+++ collect-flex/collect-flex-client/src/main/flex/org/openforis/collect/presenter/ErrorListPopUpPresenter.as
@@ -6,7 +6,6 @@

 	import org.openforis.collect.Application;
 	import org.openforis.collect.i18n.Message;
-	import org.openforis.collect.metamodel.proxy.AttributeDefinitionProxy;
 	import org.openforis.collect.metamodel.proxy.EntityDefinitionProxy;
 	import org.openforis.collect.metamodel.proxy.NodeDefinitionProxy;
 	import org.openforis.collect.metamodel.proxy.SchemaProxy;
@@ -16,8 +15,6 @@
 	import org.openforis.collect.model.proxy.NodeProxy;
 	import org.openforis.collect.model.proxy.RecordProxy;
 	import org.openforis.collect.ui.component.ErrorListPopUp;
-	import org.openforis.collect.ui.component.PopUp;
-	import org.openforis.collect.util.ArrayUtil;
 	import org.openforis.collect.util.StringUtil;
 	import org.openforis.idm.metamodel.validation.ValidationResultFlag;

@@ -77,8 +74,8 @@
 		}

 		protected function createEntityDataGridItems(entity:EntityProxy):IList {
+			var result:IList = new ArrayCollection();
 			var schema:SchemaProxy = Application.activeSurvey.schema;
-			var result:IList = new ArrayCollection();
 			var entityDefn:EntityDefinitionProxy = EntityDefinitionProxy(schema.getDefinitionById(entity.definitionId));
 			var childDefinitions:ListCollectionView = entityDefn.childDefinitions;
 			var messages:Array
@@ -93,7 +90,7 @@
 						messages = [Message.get("edit.validation.maxCount", [childDefn.maxCount > 0 ? childDefn.maxCount: 1])];
 					}
 					var message:String = StringUtil.concat(", ", messages);
-					var label:String = (entity.parent != null ? createNodeLabel(entity) + "/": "") + childDefn.getLabelText();
+					var label:String = (entity.parent != null ? createNodeLabel(entity) + " / ": "") + childDefn.getLabelText();
 					var item:Object = {label:label, message: message};
 					result.addItem(item);
 				}


Transferred from the Adobe Open Source Subversion Repository

Revision: 20931
Author:   cframpto@adobe.com
Date:     2011-03-21T14:21:57.979092Z

Log Message:
-----------
ASdoc changes that missed Flex 4.5 - mostly the mxml syntax sections for the new DataGrid and Image components but also some other errors I noticed in various classes.

Modified Paths:
--------------
<path kind="file" action="M">/private/flex/sdk/trunk/frameworks/projects/spark/src/spark/components/gridClasses/GridItemEditor.as</path>
<path kind="file" action="M">/private/flex/sdk/trunk/frameworks/projects/spark/src/spark/components/DataGrid.as</path>
<path kind="file" action="M">/private/flex/sdk/trunk/frameworks/projects/spark/src/spark/components/Label.as</path>
<path kind="file" action="M">/private/flex/sdk/trunk/frameworks/projects/spark/src/spark/components/supportClasses/ButtonBase.as</path>
<path kind="file" action="M">/private/flex/sdk/trunk/frameworks/projects/spark/src/spark/components/Image.as</path>
<path kind="file" action="M">/private/flex/sdk/trunk/frameworks/projects/spark/src/spark/components/SkinnableDataContainer.as</path>
<path kind="file" action="M">/private/flex/sdk/trunk/frameworks/projects/spark/src/spark/components/NumericStepper.as</path>
<path kind="file" action="M">/private/flex/sdk/trunk/frameworks/projects/spark/src/spark/components/Scroller.as</path>
<path kind="file" action="M">/private/flex/sdk/trunk/frameworks/projects/spark/src/spark/components/gridClasses/GridColumn.as</path>
<path kind="file" action="M">/private/flex/sdk/trunk/frameworks/projects/spark/src/spark/components/GridColumnHeaderGroup.as</path>
<path kind="file" action="M">/private/flex/sdk/trunk/frameworks/projects/spark/src/spark/components/SkinnableContainer.as</path>

git-svn-id: 7146a936b37d36e51ea0d66f8acbe0b8281bc7b7@1280731 13f79535-47bb-0310-9956-ffa450edef68

--- frameworks/projects/spark/src/spark/components/SkinnableDataContainer.as
+++ frameworks/projects/spark/src/spark/components/SkinnableDataContainer.as
@@ -160,7 +160,7 @@
  *    focusAlpha="0.55"
  *    focusColor=""
  *    focusThickness="2"
- *    fontFamily="Times New Roman"
+ *    fontFamily="Arial"
  *    fontLookup="device"
  *    fontSize="12"
  *    fontStyle="normal"


Transferred from the Adobe Open Source Subversion Repository

Revision: 10070
Author:   hmuller@adobe.com
Date:     2009-09-08T22:16:45.074989Z

Log Message:
-----------
SkinnableDataContainer and SkinnableContainer no longer implement IViewport.

GroupBase/renderFillForMouseOpaque() now handles groups whose content size is larger than their actual size.

QE Notes: Cyclone passed
Doc Notes:
Bugs: sdk-20893, sdk-20741, sdk-21730
Reviewer: Ryan

Modified Paths:
--------------
<path kind="file" action="M">/flex/sdk/trunk/frameworks/projects/spark/src/spark/components/SkinnableContainer.as</path>
<path kind="file" action="M">/flex/sdk/trunk/frameworks/projects/spark/src/spark/components/List.as</path>
<path kind="file" action="M">/flex/sdk/trunk/frameworks/projects/spark/src/spark/components/supportClasses/GroupBase.as</path>
<path kind="file" action="M">/flex/sdk/trunk/frameworks/projects/spark/src/spark/components/SkinnableDataContainer.as</path>

git-svn-id: 7146a936b37d36e51ea0d66f8acbe0b8281bc7b7@1280716 13f79535-47bb-0310-9956-ffa450edef68

--- frameworks/projects/spark/src/spark/components/SkinnableDataContainer.as
+++ frameworks/projects/spark/src/spark/components/SkinnableDataContainer.as
@@ -131,7 +131,7 @@
  *  @playerversion AIR 1.5
  *  @productversion Flex 4
  */
-public class SkinnableDataContainer extends SkinnableContainerBase implements IViewport, IItemRendererOwner
+public class SkinnableDataContainer extends SkinnableContainerBase implements IItemRendererOwner
 {
     include "../core/Version.as";

@@ -140,51 +140,36 @@
     //  Class constants
     //
     //--------------------------------------------------------------------------
-
-    /**
-     *  @private
-     */
-    private static const CLIP_AND_ENABLE_SCROLLING_PROPERTY_FLAG:uint = 1 << 0;
-
-    /**
-     *  @private
-     */
-    private static const LAYOUT_PROPERTY_FLAG:uint = 1 << 1;
-
-    /**
-     *  @private
-     */
-    private static const HORIZONTAL_SCROLL_POSITION_PROPERTY_FLAG:uint = 1 << 2;
-
-    /**
-     *  @private
-     */
-    private static const VERTICAL_SCROLL_POSITION_PROPERTY_FLAG:uint = 1 << 3;
-
-    /**
-     *  @private
-     */
-    private static const AUTO_LAYOUT_PROPERTY_FLAG:uint = 1 << 4;
-
-    /**
-     *  @private
-     */
-    private static const DATA_PROVIDER_PROPERTY_FLAG:uint = 1 << 5;
-
-    /**
-     *  @private
-     */
-    private static const ITEM_RENDERER_PROPERTY_FLAG:uint = 1 << 6;
-
-    /**
-     *  @private
-     */
-    private static const ITEM_RENDERER_FUNCTION_PROPERTY_FLAG:uint = 1 << 7;
-
-    /**
-     *  @private
-     */
-    private static const TYPICAL_ITEM_PROPERTY_FLAG:uint = 1 << 8;
+
+    /**
+     *  @private
+     */
+    private static const AUTO_LAYOUT_PROPERTY_FLAG:uint = 1 << 0;
+
+    /**
+     *  @private
+     */
+    private static const DATA_PROVIDER_PROPERTY_FLAG:uint = 1 << 1;
+
+    /**
+     *  @private
+     */
+    private static const ITEM_RENDERER_PROPERTY_FLAG:uint = 1 << 2;
+
+    /**
+     *  @private
+     */
+    private static const ITEM_RENDERER_FUNCTION_PROPERTY_FLAG:uint = 1 << 3;
+
+    /**
+     *  @private
+     */
+    private static const LAYOUT_PROPERTY_FLAG:uint = 1 << 4;
+
+    /**
+     *  @private
+     */
+    private static const TYPICAL_ITEM_PROPERTY_FLAG:uint = 1 << 5;

     //--------------------------------------------------------------------------
     //
@@ -299,83 +284,7 @@
     }

     //----------------------------------
-    //  clipAndEnableScrolling
-    //----------------------------------
-
-    /**
-     *  @inheritDoc
-     *
-     *  @default false
-     *
-     *  @langversion 3.0
-     *  @playerversion Flash 10
-     *  @playerversion AIR 1.5
-     *  @productversion Flex 4
-     */
-    public function get clipAndEnableScrolling():Boolean
-    {
-        return (dataGroup)
-            ? dataGroup.clipAndEnableScrolling
-            : dataGroupProperties.clipAndEnableScrolling;
-    }
-
-    /**
-     *  @private
-     */
-    public function set clipAndEnableScrolling(value:Boolean):void
-    {
-        if (dataGroup)
-        {
-            dataGroup.clipAndEnableScrolling = value;
-            dataGroupProperties = BitFlagUtil.update(dataGroupProperties as uint,
-                                                     CLIP_AND_ENABLE_SCROLLING_PROPERTY_FLAG, true);
-        }
-        else
-            dataGroupProperties.clipAndEnableScrolling = value;
-    }
-
-    //----------------------------------
-    //  contentWidth
-    //----------------------------------
-
-    [Bindable("propertyChange")]
-    [Inspectable(category="General")]
-
-    /**
-     *  @inheritDoc
-     *
-     *  @langversion 3.0
-     *  @playerversion Flash 10
-     *  @playerversion AIR 1.5
-     *  @productversion Flex 4
-     */
-    public function get contentWidth():Number
-    {
-        return (dataGroup) ? dataGroup.contentWidth : 0;
-    }
-
-    //----------------------------------
-    //  contentHeight
-    //----------------------------------
-
-    [Bindable("propertyChange")]
-    [Inspectable(category="General")]
-
-    /**
-     *  @inheritDoc
-     *
-     *  @langversion 3.0
-     *  @playerversion Flash 10
-     *  @playerversion AIR 1.5
-     *  @productversion Flex 4
-     */
-    public function get contentHeight():Number
-    {
-        return (dataGroup) ? dataGroup.contentHeight : 0;
-    }
-
-    //----------------------------------
-    //  content
+    //  dataProvider
     //----------------------------------

     /**
@@ -407,44 +316,6 @@
     }

     //----------------------------------
-    //  horizontalScrollPosition
-    //----------------------------------
-
-    [Bindable("propertyChange")]
-
-    /**
-     *  @inheritDoc
-     *
-     *  @langversion 3.0
-     *  @playerversion Flash 10
-     *  @playerversion AIR 1.5
-     *  @productversion Flex 4
-     */
-    public function get horizontalScrollPosition():Number
-    {
-        if (dataGroup)
-            return dataGroup.horizontalScrollPosition;
-
-        var hsp:Number = dataGroupProperties.horizontalScrollPosition;
-        return isNaN(hsp) ? 0 : hsp;
-    }
-
-    /**
-     *  @private
-     */
-    public function set horizontalScrollPosition(value:Number):void
-    {
-        if (dataGroup)
-        {
-            dataGroup.horizontalScrollPosition = value;
-            dataGroupProperties = BitFlagUtil.update(dataGroupProperties as uint,
-                                                     HORIZONTAL_SCROLL_POSITION_PROPERTY_FLAG, true);
-        }
-        else
-            dataGroupProperties.horizontalScrollPosition = value;
-    }
-
-    //----------------------------------
     //  itemRenderer
     //----------------------------------

@@ -580,82 +451,6 @@
         }
         else
             dataGroupProperties.typicalItem = value;
-    }
-
-    //----------------------------------
-    //  verticalScrollPosition
-    //----------------------------------
-
-    [Bindable("propertyChange")]
-
-    /**
-     *  @inheritDoc
-     *
-     *  @langversion 3.0
-     *  @playerversion Flash 10
-     *  @playerversion AIR 1.5
-     *  @productversion Flex 4
-     */
-    public function get verticalScrollPosition():Number
-    {
-        if (dataGroup)
-            return dataGroup.verticalScrollPosition;
-
-        var vsp:Number = dataGroupProperties.verticalScrollPosition;
-        return isNaN(vsp) ? 0 : vsp;
-    }
-
-    /**
-     *  @private
-     */
-    public function set verticalScrollPosition(value:Number):void
-    {
-        if (dataGroup)
-        {
-            dataGroup.verticalScrollPosition = value;
-            dataGroupProperties = BitFlagUtil.update(dataGroupProperties as uint,
-                                                     VERTICAL_SCROLL_POSITION_PROPERTY_FLAG, true);
-        }
-        else
-            dataGroupProperties.verticalScrollPosition = value;
-    }
-
-    //--------------------------------------------------------------------------
-    //
-    //  Methods proxied to dataGroup
-    //
-    //--------------------------------------------------------------------------
-
-    //----------------------------------
-    //  getHorizontal,VerticalScrollPositionDelta
-    //----------------------------------
-
-    /**
-     *  @inheritDoc
-     *
-     *  @langversion 3.0
-     *  @playerversion Flash 10
-     *  @playerversion AIR 1.5
-     *  @productversion Flex 4
-     */
-    public function getHorizontalScrollPositionDelta(navigationUnit:uint):Number
-    {
-        return (dataGroup) ?
-            dataGroup.getHorizontalScrollPositionDelta(navigationUnit) : 0;
-    }
-
-    /**
-     *  @inheritDoc
-     *
-     *  @langversion 3.0
-     *  @playerversion Flash 10
-     *  @playerversion AIR 1.5
-     *  @productversion Flex 4
-     */
-    public function getVerticalScrollPositionDelta(navigationUnit:uint):Number
-    {
-        return (dataGroup) ?
-            dataGroup.getVerticalScrollPositionDelta(navigationUnit) : 0;
     }

     //--------------------------------------------------------------------------
@@ -728,13 +523,6 @@

             var newDataGroupProperties:uint = 0;

-            if (dataGroupProperties.clipAndEnableScrolling !== undefined)
-            {
-                dataGroup.clipAndEnableScrolling = dataGroupProperties.clipAndEnableScrolling;
-                newDataGroupProperties = BitFlagUtil.update(newDataGroupProperties as uint,
-                                                            CLIP_AND_ENABLE_SCROLLING_PROPERTY_FLAG, true);
-            }
-
             if (dataGroupProperties.layout !== undefined)
             {
                 dataGroup.layout = dataGroupProperties.layout;
@@ -742,20 +530,6 @@
                                                             LAYOUT_PROPERTY_FLAG, true);;
             }

-            if (dataGroupProperties.horizontalScrollPosition !== undefined)
-            {
-                dataGroup.horizontalScrollPosition = dataGroupProperties.horizontalScrollPosition;
-                newDataGroupProperties = BitFlagUtil.update(newDataGroupProperties as uint,
-                                                            HORIZONTAL_SCROLL_POSITION_PROPERTY_FLAG, true);
-            }
-
-            if (dataGroupProperties.verticalScrollPosition !== undefined)
-            {
-                dataGroup.verticalScrollPosition = dataGroupProperties.verticalScrollPosition;
-                newDataGroupProperties = BitFlagUtil.update(newDataGroupProperties as uint,
-                                                            VERTICAL_SCROLL_POSITION_PROPERTY_FLAG, true);
-            }
-
             if (dataGroupProperties.autoLayout !== undefined)
             {
                 dataGroup.autoLayout = dataGroupProperties.autoLayout;
@@ -798,12 +572,6 @@

             // The only reason we have these listeners is to re-dispatch events.
             // We only add as necessary.
-
-            if (hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE))
-            {
-                dataGroup.addEventListener(
-                    PropertyChangeEvent.PROPERTY_CHANGE, dataGroup_propertyChangeHandler);
-            }

             if (hasEventListener(RendererExistenceEvent.RENDERER_ADD))
             {
@@ -826,8 +594,6 @@
     {
         if (instance == dataGroup)
         {
-            dataGroup.removeEventListener(
-                PropertyChangeEvent.PROPERTY_CHANGE, dataGroup_propertyChangeHandler);
             dataGroup.removeEventListener(
                 RendererExistenceEvent.RENDERER_ADD, dispatchEvent);
             dataGroup.removeEventListener(
@@ -837,17 +603,8 @@

             var newDataGroupProperties:Object = {};

-            if (BitFlagUtil.isSet(dataGroupProperties as uint, CLIP_AND_ENABLE_SCROLLING_PROPERTY_FLAG))
-                newDataGroupProperties.clipAndEnableScrolling = dataGroup.clipAndEnableScrolling;
-
             if (BitFlagUtil.isSet(dataGroupProperties as uint, LAYOUT_PROPERTY_FLAG))
                 newDataGroupProperties.layout = dataGroup.layout;
-
-            if (BitFlagUtil.isSet(dataGroupProperties as uint, HORIZONTAL_SCROLL_POSITION_PROPERTY_FLAG))
-                newDataGroupProperties.horizontalScrollPosition = dataGroup.horizontalScrollPosition;
-
-            if (BitFlagUtil.isSet(dataGroupProperties as uint, VERTICAL_SCROLL_POSITION_PROPERTY_FLAG))
-                newDataGroupProperties.verticalScrollPosition = dataGroup.verticalScrollPosition;

             if (BitFlagUtil.isSet(dataGroupProperties as uint, AUTO_LAYOUT_PROPERTY_FLAG))
                 newDataGroupProperties.autoLayout = dataGroup.autoLayout;
@@ -891,11 +648,6 @@
         // exist, don't worry about it.  When the dataGroup,
         // gets created up, we'll check to see whether we need to add this
         // event listener to the dataGroup.
-        if (type == PropertyChangeEvent.PROPERTY_CHANGE && dataGroup)
-        {
-            dataGroup.addEventListener(
-                PropertyChangeEvent.PROPERTY_CHANGE, dataGroup_propertyChangeHandler);
-        }

         if (type == RendererExistenceEvent.RENDERER_ADD && dataGroup)
         {
@@ -923,15 +675,6 @@

         // if no one's listening to us for this event any more, let's
         // remove our underlying event listener from the dataGroup.
-        if (type == PropertyChangeEvent.PROPERTY_CHANGE && dataGroup)
-        {
-            if (!hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE))
-            {
-                dataGroup.removeEventListener(
-                    PropertyChangeEvent.PROPERTY_CHANGE, dataGroup_propertyChangeHandler);
-            }
-        }
-
         if (type == RendererExistenceEvent.RENDERER_ADD && dataGroup)
         {
             if (!hasEventListener(RendererExistenceEvent.RENDERER_ADD))
@@ -950,30 +693,6 @@
             }
         }
     }
-
-    //--------------------------------------------------------------------------
-    //
-    //  Event Handlers
-    //
-    //--------------------------------------------------------------------------
-
-    /**
-     * @private
-     */
-    private function dataGroup_propertyChangeHandler(event:PropertyChangeEvent):void
-    {
-        // Re-dispatch the event if it's one other people are binding too
-        switch (event.property)
-        {
-            case 'contentWidth':
-            case 'contentHeight':
-            case 'horizontalScrollPosition':
-            case 'verticalScrollPosition':
-            {
-                dispatchEvent(event);
-            }
-        }
-    }
 }

 }


Transferred from the Adobe Open Source Subversion Repository

Revision: 6134
Author:   rfrishbe@adobe.com
Date:     2009-04-10T21:10:42.260080Z

Log Message:
-----------
Fix a bug with autoLayout in SkinnableContainer and SkinnableDataContainer.  When autoLayout is explicitely set, there was a bug in the getter to always return false.

QE Notes: -
Doc Notes: -
Bugs: SDK-20601
Reviewer: Hans
tests: checkintests

Modified Paths:
--------------
<path kind="file" action="M">/flex/sdk/trunk/frameworks/projects/flex4/src/spark/components/SkinnableContainer.as</path>
<path kind="file" action="M">/flex/sdk/trunk/frameworks/projects/flex4/src/spark/components/SkinnableDataContainer.as</path>

git-svn-id: 7146a936b37d36e51ea0d66f8acbe0b8281bc7b7@1280693 13f79535-47bb-0310-9956-ffa450edef68

--- frameworks/projects/spark/src/spark/components/SkinnableDataContainer.as
+++ frameworks/projects/spark/src/spark/components/SkinnableDataContainer.as
@@ -228,8 +228,8 @@
         else
         {
             // want the default to be true
-            return (dataGroupProperties.autoLayout === undefined) ?
-                    true : false
+            var v:* = dataGroupProperties.autoLayout;
+            return (v === undefined) ? true : v;
         }
     }