//////////////////////////////////////////////////////////////////////////////// // // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to You under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance with // the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// package mx.utils { import flash.utils.ByteArray; import flash.utils.Dictionary; import flash.utils.getQualifiedClassName; import flash.xml.XMLNode; import mx.collections.IList; /** * The ObjectUtil class is an all-static class with methods for * working with Objects within Flex. * You do not create instances of ObjectUtil; * instead you simply call static methods such as the * ObjectUtil.isSimple() method. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public class ObjectUtil { include "../core/Version.as"; /** * Array of properties to exclude from debugging output. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ private static var defaultToStringExcludes:Array = ["password", "credentials"]; //-------------------------------------------------------------------------- // // Class methods // //-------------------------------------------------------------------------- /** * Compares the Objects and returns an integer value * indicating if the first item is less than greater than or equal to * the second item. * This method will recursively compare properties on nested objects and * will return as soon as a non-zero result is found. * By default this method will recurse to the deepest level of any property. * To change the depth for comparison specify a non-negative value for * the depth parameter. * * @param a Object. * * @param b Object. * * @param depth Indicates how many levels should be * recursed when performing the comparison. * Set this value to 0 for a shallow comparison of only the primitive * representation of each property. * For example:
     *  var a:Object = {name:"Bob", info:[1,2,3]};
     *  var b:Object = {name:"Alice", info:[5,6,7]};
     *  var c:int = ObjectUtil.compare(a, b, 0);
* *

In the above example the complex properties of a and * b will be flattened by a call to toString() * when doing the comparison. * In this case the info property will be turned into a string * when performing the comparison.

* * @return Return 0 if a and b are null, NaN, or equal. * Return 1 if a is null or greater than b. * Return -1 if b is null or greater than a. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function compare(a:Object, b:Object, depth:int = -1):int { return internalCompare(a, b, 0, depth, new Dictionary(true)); } /** * Copies the specified Object and returns a reference to the copy. * The copy is made using a native serialization technique. * This means that custom serialization will be respected during the copy. * *

This method is designed for copying data objects, * such as elements of a collection. It is not intended for copying * a UIComponent object, such as a TextInput control. If you want to create copies * of specific UIComponent objects, you can create a subclass of the component and implement * a clone() method, or other method to perform the copy.

* * @param value Object that should be copied. * * @return Copy of the specified Object. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function copy(value:Object):Object { var buffer:ByteArray = new ByteArray(); buffer.writeObject(value); buffer.position = 0; var result:Object = buffer.readObject(); return result; } /** * Clones the specified Object and returns a reference to the clone. * The clone is made using a native serialization technique. * This means that custom serialization will be respected during the * cloning. clone() differs from copy() in that the uid property of * each object instance is retained. * *

This method is designed for cloning data objects, * such as elements of a collection. It is not intended for cloning * a UIComponent object, such as a TextInput control. If you want to clone * specific UIComponent objects, you can create a subclass of the component * and implement a clone() method.

* * @param value Object that should be cloned. * * @return Clone of the specified Object. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 4 */ public static function clone(value:Object):Object { var result:Object = copy(value); cloneInternal(result, value); return result; } /** * Recursive helper used by the public clone method. * @private */ private static function cloneInternal(result:Object, value:Object):void { if (value && value.hasOwnProperty("uid")) result.uid = value.uid; var classInfo:Object = getClassInfo(value); var v:Object; for each (var p:* in classInfo.properties) { v = value[p]; if (v && v.hasOwnProperty("uid")) cloneInternal(result[p], v); } } /** * Returns true if the object reference specified * is a simple data type. The simple data types include the following: * * * @param value Object inspected. * * @return true if the object specified * is one of the types above; false otherwise. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function isSimple(value:Object):Boolean { var type:String = typeof(value); switch (type) { case "number": case "string": case "boolean": { return true; } case "object": { return (value is Date) || (value is Array); } } return false; } /** * Compares two numeric values. * * @param a First number. * * @param b Second number. * * @return 0 is both numbers are NaN. * 1 if only a is a NaN. * -1 if only b is a NaN. * -1 if a is less than b. * 1 if a is greater than b. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function numericCompare(a:Number, b:Number):int { if (isNaN(a) && isNaN(b)) return 0; if (isNaN(a)) return 1; if (isNaN(b)) return -1; if (a < b) return -1; if (a > b) return 1; return 0; } /** * Compares two String values. * * @param a First String value. * * @param b Second String value. * * @param caseInsensitive Specifies to perform a case insensitive compare, * true, or not, false. * * @return 0 is both Strings are null. * 1 if only a is null. * -1 if only b is null. * -1 if a precedes b. * 1 if b precedes a. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function stringCompare(a:String, b:String, caseInsensitive:Boolean = false):int { if (a == null && b == null) return 0; if (a == null) return 1; if (b == null) return -1; // Convert to lowercase if we are case insensitive. if (caseInsensitive) { a = a.toLocaleLowerCase(); b = b.toLocaleLowerCase(); } var result:int = a.localeCompare(b); if (result < -1) result = -1; else if (result > 1) result = 1; return result; } /** * Compares the two Date objects and returns an integer value * indicating if the first Date object is before, equal to, * or after the second item. * * @param a Date object. * * @param b Date object. * * @return 0 if a and b are equal * (or both are null); * -1 if a is before b * (or b is null); * 1 if a is after b * (or a is null). * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function dateCompare(a:Date, b:Date):int { if (a == null && b == null) return 0; if (a == null) return 1; if (b == null) return -1; var na:Number = a.getTime(); var nb:Number = b.getTime(); if (na < nb) return -1; if (na > nb) return 1; return 0; } /** * Pretty-prints the specified Object into a String. * All properties will be in alpha ordering. * Each object will be assigned an id during printing; * this value will be displayed next to the object type token * preceded by a '#', for example: * *
     *  (mx.messaging.messages::AsyncMessage)#2.
* *

This id is used to indicate when a circular reference occurs. * Properties of an object that are of the Class type will * appear only as the assigned type. * For example a custom definition like the following:

* *
     *    public class MyCustomClass {
     *      public var clazz:Class;
     *    }
* *

With the clazz property assigned to Date * will display as shown below:

* *
     *   (somepackage::MyCustomClass)#0
     *      clazz = (Date)
* * @param obj Object to be pretty printed. * * @param namespaceURIs Array of namespace URIs for properties * that should be included in the output. * By default only properties in the public namespace will be included in * the output. * To get all properties regardless of namespace pass an array with a * single element of "*". * * @param exclude Array of the property names that should be * excluded from the output. * Use this to remove data from the formatted string. * * @return String containing the formatted version * of the specified object. * * @example *
     *  // example 1
     *  var obj:AsyncMessage = new AsyncMessage();
     *  obj.body = [];
     *  obj.body.push(new AsyncMessage());
     *  obj.headers["1"] = { name: "myName", num: 15.3};
     *  obj.headers["2"] = { name: "myName", num: 15.3};
     *  obj.headers["10"] = { name: "myName", num: 15.3};
     *  obj.headers["11"] = { name: "myName", num: 15.3};
     *  trace(ObjectUtil.toString(obj));
     *
     *  // will output to flashlog.txt
     *  (mx.messaging.messages::AsyncMessage)#0
     *    body = (Array)#1
     *      [0] (mx.messaging.messages::AsyncMessage)#2
     *        body = (Object)#3
     *        clientId = (Null)
     *        correlationId = ""
     *        destination = ""
     *        headers = (Object)#4
     *        messageId = "378CE96A-68DB-BC1B-BCF7FFFFFFFFB525"
     *        sequenceId = (Null)
     *        sequencePosition = 0
     *        sequenceSize = 0
     *        timeToLive = 0
     *        timestamp = 0
     *    clientId = (Null)
     *    correlationId = ""
     *    destination = ""
     *    headers = (Object)#5
     *      1 = (Object)#6
     *        name = "myName"
     *        num = 15.3
     *      10 = (Object)#7
     *        name = "myName"
     *        num = 15.3
     *      11 = (Object)#8
     *        name = "myName"
     *        num = 15.3
     *      2 = (Object)#9
     *        name = "myName"
     *        num = 15.3
     *    messageId = "1D3E6E96-AC2D-BD11-6A39FFFFFFFF517E"
     *    sequenceId = (Null)
     *    sequencePosition = 0
     *    sequenceSize = 0
     *    timeToLive = 0
     *    timestamp = 0
     *
     *  // example 2 with circular references
     *  obj = {};
     *  obj.prop1 = new Date();
     *  obj.prop2 = [];
     *  obj.prop2.push(15.2);
     *  obj.prop2.push("testing");
     *  obj.prop2.push(true);
     *  obj.prop3 = {};
     *  obj.prop3.circular = obj;
     *  obj.prop3.deeper = new ErrorMessage();
     *  obj.prop3.deeper.rootCause = obj.prop3.deeper;
     *  obj.prop3.deeper2 = {};
     *  obj.prop3.deeper2.deeperStill = {};
     *  obj.prop3.deeper2.deeperStill.yetDeeper = obj;
     *  trace(ObjectUtil.toString(obj));
     *
     *  // will output to flashlog.txt
     *  (Object)#0
     *    prop1 = Tue Apr 26 13:59:17 GMT-0700 2005
     *    prop2 = (Array)#1
     *      [0] 15.2
     *      [1] "testing"
     *      [2] true
     *    prop3 = (Object)#2
     *      circular = (Object)#0
     *      deeper = (mx.messaging.messages::ErrorMessage)#3
     *        body = (Object)#4
     *        clientId = (Null)
     *        code = (Null)
     *        correlationId = ""
     *        destination = ""
     *        details = (Null)
     *        headers = (Object)#5
     *        level = (Null)
     *        message = (Null)
     *        messageId = "14039376-2BBA-0D0E-22A3FFFFFFFF140A"
     *        rootCause = (mx.messaging.messages::ErrorMessage)#3
     *        sequenceId = (Null)
     *        sequencePosition = 0
     *        sequenceSize = 0
     *        timeToLive = 0
     *        timestamp = 0
     *      deeper2 = (Object)#6
     *        deeperStill = (Object)#7
     *          yetDeeper = (Object)#0
     * 
     * // example 3 with Dictionary
     * var point:Point = new Point(100, 100);
     * var point2:Point = new Point(100, 100);
     * var obj:Dictionary = new Dictionary();
     * obj[point] = "point";
     * obj[point2] = "point2";
     * obj["1"] = { name: "one", num: 1};
     * obj["two"] = { name: "2", num: 2};
     * obj[3] = 3;
     * trace(ObjectUtil.toString(obj));
     * 
     * // will output to flashlog.txt
     * (flash.utils::Dictionary)#0
     *   {(flash.geom::Point)#1
     *     length = 141.4213562373095
     *     x = 100
     *     y = 100} = "point2"
     *   {(flash.geom::Point)#2
     *     length = 141.4213562373095
     *     x = 100
     *     y = 100} = "point"
     *   {1} = (Object)#3
     *     name = "one"
     *     num = 1
     *   {3} = 3
     *   {"two"} = (Object)#4
     *     name = "2"
     *     num = 2
     * 
     * 
* * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function toString(value:Object, namespaceURIs:Array = null, exclude:Array = null):String { if (exclude == null) { exclude = defaultToStringExcludes; } refCount = 0; return internalToString(value, 0, null, namespaceURIs, exclude); } /** * This method cleans up all of the additional parameters that show up in AsDoc * code hinting tools that developers shouldn't ever see. * @private */ private static function internalToString(value:Object, indent:int = 0, refs:Dictionary= null, namespaceURIs:Array = null, exclude:Array = null):String { var str:String; var type:String = value == null ? "null" : typeof(value); switch (type) { case "boolean": case "number": { return value.toString(); } case "string": { return "\"" + value.toString() + "\""; } case "object": { if (value is Date) { return value.toString(); } else if (value is XMLNode) { return value.toString(); } else if (value is Class) { return "(" + getQualifiedClassName(value) + ")"; } else { var classInfo:Object = getClassInfo(value, exclude, { includeReadOnly: true, uris: namespaceURIs }); var properties:Array = classInfo.properties; str = "(" + classInfo.name + ")"; // refs help us avoid circular reference infinite recursion. // Each time an object is encoumtered it is pushed onto the // refs stack so that we can determine if we have visited // this object already. if (refs == null) refs = new Dictionary(true); // Check to be sure we haven't processed this object before // Dictionary has some bugs, so we want to work around them as best we can try { var id:Object = refs[value]; if (id != null) { str += "#" + int(id); return str; } } catch (e:Error) { //Since we can't test for infinite loop, we simply return toString. return String(value); } if (value != null) { str += "#" + refCount.toString(); refs[value] = refCount; refCount++; } var isArray:Boolean = value is Array; var isDict:Boolean = value is Dictionary; var prop:*; indent += 2; // Print all of the variable values. for (var j:int = 0; j < properties.length; j++) { str = newline(str, indent); prop = properties[j]; if (isArray) str += "["; else if (isDict) str += "{"; if (isDict) { // in dictionaries, recurse on the key, because it can be a complex object str += internalToString(prop, indent, refs, namespaceURIs, exclude); } else { str += prop.toString(); } if (isArray) str += "] "; else if (isDict) str += "} = "; else str += " = "; try { // print the value str += internalToString(value[prop], indent, refs, namespaceURIs, exclude); } catch(e:Error) { // value[prop] can cause an RTE // for certain properties of certain objects. // For example, accessing the properties // actionScriptVersion // childAllowsParent // frameRate // height // loader // parentAllowsChild // sameDomain // swfVersion // width // of a Stage's loaderInfo causes // Error #2099: The loading object is not // sufficiently loaded to provide this information // In this case, we simply output ? for the value. str += "?"; } } indent -= 2; return str; } break; } case "xml": { return value.toXMLString(); } default: { return "(" + type + ")"; } } return "(unknown)"; } /** * @private * This method will append a newline and the specified number of spaces * to the given string. */ private static function newline(str:String, n:int = 0):String { var result:String = str; result += "\n"; for (var i:int = 0; i < n; i++) { result += " "; } return result; } private static function internalCompare(a:Object, b:Object, currentDepth:int, desiredDepth:int, refs:Dictionary):int { if (a == null && b == null) return 0; if (a == null) return 1; if (b == null) return -1; if (a is ObjectProxy) a = ObjectProxy(a).object_proxy::object; if (b is ObjectProxy) b = ObjectProxy(b).object_proxy::object; var typeOfA:String = typeof(a); var typeOfB:String = typeof(b); var result:int = 0; if (typeOfA == typeOfB) { switch(typeOfA) { case "boolean": { result = numericCompare(Number(a), Number(b)); break; } case "number": { result = numericCompare(a as Number, b as Number); break; } case "string": { result = stringCompare(a as String, b as String); break; } case "object": { var newDepth:int = desiredDepth > 0 ? desiredDepth -1 : desiredDepth; // refs help us avoid circular reference infinite recursion. var aRef:Object = getRef(a,refs); var bRef:Object = getRef(b,refs); if (aRef == bRef) return 0; // the cool thing about our dictionary is that if // we've seen objects and determined that they are inequal, then // we would've already exited out of this compare() call. So the // only info in the dictionary are sets of equal items // let's first define them as equal // this stops an "infinite loop" problem where A.i = B and B.i = A // if we later find that an object (one of the subobjects) is in fact unequal, // then we will return false and quit out of everything. These refs are thrown away // so it doesn't matter if it's correct. refs[bRef] = aRef; if (desiredDepth != -1 && (currentDepth > desiredDepth)) { // once we try to go beyond the desired depth we should // toString() our way out result = stringCompare(a.toString(), b.toString()); } else if ((a is Array) && (b is Array)) { result = arrayCompare(a as Array, b as Array, currentDepth, desiredDepth, refs); } else if ((a is Date) && (b is Date)) { result = dateCompare(a as Date, b as Date); } else if ((a is IList) && (b is IList)) { result = listCompare(a as IList, b as IList, currentDepth, desiredDepth, refs); } else if ((a is ByteArray) && (b is ByteArray)) { result = byteArrayCompare(a as ByteArray, b as ByteArray); } else if (getQualifiedClassName(a) == getQualifiedClassName(b)) { var aProps:Array = getClassInfo(a).properties; var bProps:Array; // if the objects are dynamic they could have different // # of properties and should be treated on that basis first var isDynamicObject:Boolean = isDynamicObject(a); // if it's dynamic, check to see that they have all the same properties if (isDynamicObject) { bProps = getClassInfo(b).properties; result = arrayCompare(aProps, bProps, currentDepth, newDepth, refs); if (result != 0) return result; } // now that we know we have the same properties, let's compare the values var propName:QName; var aProp:Object; var bProp:Object; for (var i:int = 0; i < aProps.length; i++) { propName = aProps[i]; aProp = a[propName]; bProp = b[propName]; result = internalCompare(aProp, bProp, currentDepth+1, newDepth, refs); if (result != 0) { return result; } } } else { // We must be inequal, so return 1 return 1; } break; } } } else // be consistent with the order we return here { return stringCompare(typeOfA, typeOfB); } return result; } /** * Returns information about the class, and properties of the class, for * the specified Object. * * @param obj The Object to inspect. * * @param exclude Array of Strings specifying the property names that should be * excluded from the returned result. For example, you could specify * ["currentTarget", "target"] for an Event object since these properties * can cause the returned result to become large. * * @param options An Object containing one or more properties * that control the information returned by this method. * The properties include the following: * * * * @return An Object containing the following properties: * * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function getClassInfo(obj:Object, excludes:Array = null, options:Object = null):Object { var n:int; var i:int; if (obj is ObjectProxy) obj = ObjectProxy(obj).object_proxy::object; if (options == null) options = { includeReadOnly: true, uris: null, includeTransient: true }; var result:Object; var propertyNames:Array = []; var cacheKey:String; var className:String; var classAlias:String; var properties:XMLList; var prop:XML; var dynamic:Boolean = false; var metadataInfo:Object; if (typeof(obj) == "xml") { className = "XML"; properties = obj.text(); if (properties.length()) propertyNames.push("*"); properties = obj.attributes(); } else { var classInfo:XML = DescribeTypeCache.describeType(obj).typeDescription; className = classInfo.@name.toString(); classAlias = classInfo.@alias.toString(); dynamic = (classInfo.@isDynamic.toString() == "true"); if (options.includeReadOnly) properties = classInfo..accessor.(@access != "writeonly") + classInfo..variable; else properties = classInfo..accessor.(@access == "readwrite") + classInfo..variable; var numericIndex:Boolean = false; } // If type is not dynamic, check our cache for class info... if (!dynamic) { cacheKey = getCacheKey(obj, excludes, options); result = CLASS_INFO_CACHE[cacheKey]; if (result != null) return result; } result = {}; result["name"] = className; result["alias"] = classAlias; result["properties"] = propertyNames; result["dynamic"] = dynamic; result["metadata"] = metadataInfo = recordMetadata(properties); var excludeObject:Object = {}; if (excludes) { n = excludes.length; for (i = 0; i < n; i++) { excludeObject[excludes[i]] = 1; } } // TODO (pfarland): this seems slightly fragile, why not use the 'is' operator? var isArray:Boolean = (className == "Array"); var isDict:Boolean = (className == "flash.utils::Dictionary"); if (isDict) { // dictionaries can have multiple keys of the same type, // (they can index by reference rather than QName, String, or number), // which cannot be looked up by QName, so use references to the actual key for (var key:* in obj) { propertyNames.push(key); } } else if (dynamic) { for (var p:String in obj) { if (excludeObject[p] != 1) { if (isArray) { var pi:Number = parseInt(p); if (isNaN(pi)) propertyNames.push(new QName("", p)); else propertyNames.push(pi); } else { propertyNames.push(new QName("", p)); } } } numericIndex = isArray && !isNaN(Number(p)); } if (isArray || isDict || className == "Object") { // Do nothing since we've already got the dynamic members } else if (className == "XML") { n = properties.length(); for (i = 0; i < n; i++) { p = properties[i].name(); if (excludeObject[p] != 1) propertyNames.push(new QName("", "@" + p)); } } else { n = properties.length(); var uris:Array = options.uris; var uri:String; var qName:QName; for (i = 0; i < n; i++) { prop = properties[i]; p = prop.@name.toString(); uri = prop.@uri.toString(); if (excludeObject[p] == 1) continue; if (!options.includeTransient && internalHasMetadata(metadataInfo, p, "Transient")) continue; if (uris != null) { if (uris.length == 1 && uris[0] == "*") { qName = new QName(uri, p); try { obj[qName]; // access the property to ensure it is supported propertyNames.push(); } catch(e:Error) { // don't keep property name } } else { for (var j:int = 0; j < uris.length; j++) { uri = uris[j]; if (prop.@uri.toString() == uri) { qName = new QName(uri, p); try { obj[qName]; propertyNames.push(qName); } catch(e:Error) { // don't keep property name } } } } } else if (uri.length == 0) { qName = new QName(uri, p); try { obj[qName]; propertyNames.push(qName); } catch(e:Error) { // don't keep property name } } } } propertyNames.sort(Array.CASEINSENSITIVE | (numericIndex ? Array.NUMERIC : 0)); // dictionary keys can be indexed by an object reference // there's a possibility that two keys will have the same toString() // so we don't want to remove dupes if (!isDict) { // for Arrays, etc., on the other hand... // remove any duplicates, i.e. any items that can't be distingushed by toString() for (i = 0; i < propertyNames.length - 1; i++) { // the list is sorted so any duplicates should be adjacent // two properties are only equal if both the uri and local name are identical if (propertyNames[i].toString() == propertyNames[i + 1].toString()) { propertyNames.splice(i, 1); i--; // back up } } } // For normal, non-dynamic classes we cache the class info if (!dynamic) { cacheKey = getCacheKey(obj, excludes, options); CLASS_INFO_CACHE[cacheKey] = result; } return result; } /** * Uses getClassInfo and examines the metadata information to * determine whether a property on a given object has the specified * metadata. * * @param obj The object holding the property. * @param propName The property to check for metadata. * @param metadataName The name of the metadata to check on the property. * @param excludes If any properties need to be excluded when generating class info. (Optional) * @param options If any options flags need to changed when generating class info. (Optional) * @return true if the property has the specified metadata. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function hasMetadata(obj:Object, propName:String, metadataName:String, excludes:Array = null, options:Object = null):Boolean { var classInfo:Object = getClassInfo(obj, excludes, options); var metadataInfo:Object = classInfo["metadata"]; return internalHasMetadata(metadataInfo, propName, metadataName); } /** * Returns true if the object is an instance of a dynamic class. * * @param obj The object. * * @return true if the object is an instance of a dynamic class. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function isDynamicObject(obj:Object):Boolean { try { // this test for checking whether an object is dynamic or not is // pretty hacky, but it assumes that no-one actually has a // property defined called "wootHackwoot" obj["wootHackwoot"]; } catch (e:Error) { // our object isn't from a dynamic class return false; } return true; } /** * @private */ private static function internalHasMetadata(metadataInfo:Object, propName:String, metadataName:String):Boolean { if (metadataInfo != null) { var metadata:Object = metadataInfo[propName]; if (metadata != null) { if (metadata[metadataName] != null) return true; } } return false; } /** * @private */ private static function recordMetadata(properties:XMLList):Object { var result:Object = null; try { for each (var prop:XML in properties) { var propName:String = prop.attribute("name").toString(); var metadataList:XMLList = prop.metadata; if (metadataList.length() > 0) { if (result == null) result = {}; var metadata:Object = {}; result[propName] = metadata; for each (var md:XML in metadataList) { var mdName:String = md.attribute("name").toString(); var argsList:XMLList = md.arg; var value:Object = {}; for each (var arg:XML in argsList) { var argKey:String = arg.attribute("key").toString(); if (argKey != null) { var argValue:String = arg.attribute("value").toString(); value[argKey] = argValue; } } var existing:Object = metadata[mdName]; if (existing != null) { var existingArray:Array; if (existing is Array) existingArray = existing as Array; else { existingArray = [existing]; delete metadata[mdName]; } existingArray.push(value); existing = existingArray; } else { existing = value; } metadata[mdName] = existing; } } } } catch(e:Error) { } return result; } /** * @private */ private static function getCacheKey(o:Object, excludes:Array = null, options:Object = null):String { var key:String = getQualifiedClassName(o); if (excludes != null) { for (var i:uint = 0; i < excludes.length; i++) { var excl:String = excludes[i] as String; if (excl != null) key += excl; } } if (options != null) { for (var flag:String in options) { key += flag; var value:String = options[flag] as String; if (value != null) key += value; } } return key; } /** * @private */ private static function arrayCompare(a:Array, b:Array, currentDepth:int, desiredDepth:int, refs:Dictionary):int { var result:int = 0; if (a.length != b.length) { if (a.length < b.length) result = -1; else result = 1; } else { var key:Object; for (key in a) { if (b.hasOwnProperty(key)) { result = internalCompare(a[key], b[key], currentDepth, desiredDepth, refs); if (result != 0) return result; } else { return -1; } } for (key in b) { if (!a.hasOwnProperty(key)) { return 1; } } } return result; } /** * @private */ private static function byteArrayCompare(a:ByteArray, b:ByteArray):int { var result:int = 0; if (a == b) return result; if (a.length != b.length) { if (a.length < b.length) result = -1; else result = 1; } else { for (var i:int = 0; i < a.length; i++) { result = numericCompare(a[i], b[i]); if (result != 0) { i = a.length; } } } return result; } /** * @private */ private static function listCompare(a:IList, b:IList, currentDepth:int, desiredDepth:int, refs:Dictionary):int { var result:int = 0; if (a.length != b.length) { if (a.length < b.length) result = -1; else result = 1; } else { for (var i:int = 0; i < a.length; i++) { result = internalCompare(a.getItemAt(i), b.getItemAt(i), currentDepth+1, desiredDepth, refs); if (result != 0) { i = a.length; } } } return result; } /** * @private * This is the "find" for our union-find algorithm when doing object searches. * The dictionary keeps track of sets of equal objects */ private static function getRef(o:Object, refs:Dictionary):Object { var oRef:Object = refs[o]; while (oRef && oRef != refs[oRef]) { oRef = refs[oRef]; } if (!oRef) oRef = o; if (oRef != refs[o]) refs[o] = oRef; return oRef } /** * @private */ private static var refCount:int = 0; /** * @private */ private static var CLASS_INFO_CACHE:Object = {}; } }