1//////////////////////////////////////////////////////////////////////////////// 2// 3// ADOBE SYSTEMS INCORPORATED 4// Copyright 2005-2007 Adobe Systems Incorporated 5// All Rights Reserved. 6// 7// NOTICE: Adobe permits you to use, modify, and distribute this file 8// in accordance with the terms of the license agreement accompanying it. 9// 10//////////////////////////////////////////////////////////////////////////////// 11 12package mx.utils 13{ 14 15import flash.utils.ByteArray; 16import flash.utils.Dictionary; 17import flash.utils.getQualifiedClassName; 18import flash.utils.describeType; 19import flash.xml.XMLNode; 20 21import mx.collections.IList; 22 23/** 24 * The RPCObjectUtil class is a subset of ObjectUtil, removing methods 25 * that create dependency issues when RPC messages are in a bootstrap loader. 26 * 27 * @langversion 3.0 28 * @playerversion Flash 9 29 * @playerversion AIR 1.1 30 * @productversion Flex 3 31 */ 32public class RPCObjectUtil 33{ 34 include "../core/Version.as"; 35 36 /** 37 * Array of properties to exclude from debugging output. 38 * 39 * @langversion 3.0 40 * @playerversion Flash 9 41 * @playerversion AIR 1.1 42 * @productversion Flex 3 43 */ 44 private static var defaultToStringExcludes:Array = ["password", "credentials"]; 45 46 //-------------------------------------------------------------------------- 47 // 48 // Class methods 49 // 50 //-------------------------------------------------------------------------- 51 52 53 /** 54 * Pretty-prints the specified Object into a String. 55 * All properties will be in alpha ordering. 56 * Each object will be assigned an id during printing; 57 * this value will be displayed next to the object type token 58 * preceded by a '#', for example: 59 * 60 * <pre> 61 * (mx.messaging.messages::AsyncMessage)#2.</pre> 62 * 63 * <p>This id is used to indicate when a circular reference occurs. 64 * Properties of an object that are of the <code>Class</code> type will 65 * appear only as the assigned type. 66 * For example a custom definition like the following:</p> 67 * 68 * <pre> 69 * public class MyCustomClass { 70 * public var clazz:Class; 71 * }</pre> 72 * 73 * <p>With the <code>clazz</code> property assigned to <code>Date</code> 74 * will display as shown below:</p> 75 * 76 * <pre> 77 * (somepackage::MyCustomClass)#0 78 * clazz = (Date)</pre> 79 * 80 * @param obj Object to be pretty printed. 81 * 82 * @param namespaceURIs Array of namespace URIs for properties 83 * that should be included in the output. 84 * By default only properties in the public namespace will be included in 85 * the output. 86 * To get all properties regardless of namespace pass an array with a 87 * single element of "*". 88 * 89 * @param exclude Array of the property names that should be 90 * excluded from the output. 91 * Use this to remove data from the formatted string. 92 * 93 * @return String containing the formatted version 94 * of the specified object. 95 * 96 * @example 97 * <pre> 98 * // example 1 99 * var obj:AsyncMessage = new AsyncMessage(); 100 * obj.body = []; 101 * obj.body.push(new AsyncMessage()); 102 * obj.headers["1"] = { name: "myName", num: 15.3}; 103 * obj.headers["2"] = { name: "myName", num: 15.3}; 104 * obj.headers["10"] = { name: "myName", num: 15.3}; 105 * obj.headers["11"] = { name: "myName", num: 15.3}; 106 * trace(ObjectUtil.toString(obj)); 107 * 108 * // will output to flashlog.txt 109 * (mx.messaging.messages::AsyncMessage)#0 110 * body = (Array)#1 111 * [0] (mx.messaging.messages::AsyncMessage)#2 112 * body = (Object)#3 113 * clientId = (Null) 114 * correlationId = "" 115 * destination = "" 116 * headers = (Object)#4 117 * messageId = "378CE96A-68DB-BC1B-BCF7FFFFFFFFB525" 118 * sequenceId = (Null) 119 * sequencePosition = 0 120 * sequenceSize = 0 121 * timeToLive = 0 122 * timestamp = 0 123 * clientId = (Null) 124 * correlationId = "" 125 * destination = "" 126 * headers = (Object)#5 127 * 1 = (Object)#6 128 * name = "myName" 129 * num = 15.3 130 * 10 = (Object)#7 131 * name = "myName" 132 * num = 15.3 133 * 11 = (Object)#8 134 * name = "myName" 135 * num = 15.3 136 * 2 = (Object)#9 137 * name = "myName" 138 * num = 15.3 139 * messageId = "1D3E6E96-AC2D-BD11-6A39FFFFFFFF517E" 140 * sequenceId = (Null) 141 * sequencePosition = 0 142 * sequenceSize = 0 143 * timeToLive = 0 144 * timestamp = 0 145 * 146 * // example 2 with circular references 147 * obj = {}; 148 * obj.prop1 = new Date(); 149 * obj.prop2 = []; 150 * obj.prop2.push(15.2); 151 * obj.prop2.push("testing"); 152 * obj.prop2.push(true); 153 * obj.prop3 = {}; 154 * obj.prop3.circular = obj; 155 * obj.prop3.deeper = new ErrorMessage(); 156 * obj.prop3.deeper.rootCause = obj.prop3.deeper; 157 * obj.prop3.deeper2 = {}; 158 * obj.prop3.deeper2.deeperStill = {}; 159 * obj.prop3.deeper2.deeperStill.yetDeeper = obj; 160 * trace(ObjectUtil.toString(obj)); 161 * 162 * // will output to flashlog.txt 163 * (Object)#0 164 * prop1 = Tue Apr 26 13:59:17 GMT-0700 2005 165 * prop2 = (Array)#1 166 * [0] 15.2 167 * [1] "testing" 168 * [2] true 169 * prop3 = (Object)#2 170 * circular = (Object)#0 171 * deeper = (mx.messaging.messages::ErrorMessage)#3 172 * body = (Object)#4 173 * clientId = (Null) 174 * code = (Null) 175 * correlationId = "" 176 * destination = "" 177 * details = (Null) 178 * headers = (Object)#5 179 * level = (Null) 180 * message = (Null) 181 * messageId = "14039376-2BBA-0D0E-22A3FFFFFFFF140A" 182 * rootCause = (mx.messaging.messages::ErrorMessage)#3 183 * sequenceId = (Null) 184 * sequencePosition = 0 185 * sequenceSize = 0 186 * timeToLive = 0 187 * timestamp = 0 188 * deeper2 = (Object)#6 189 * deeperStill = (Object)#7 190 * yetDeeper = (Object)#0 191 * </pre> 192 * 193 * 194 * @langversion 3.0 195 * @playerversion Flash 9 196 * @playerversion AIR 1.1 197 * @productversion Flex 3 198 */ 199 public static function toString(value:Object, 200 namespaceURIs:Array = null, 201 exclude:Array = null):String 202 { 203 if (exclude == null) 204 { 205 exclude = defaultToStringExcludes; 206 } 207 208 refCount = 0; 209 return internalToString(value, 0, null, namespaceURIs, exclude); 210 } 211 212 /** 213 * This method cleans up all of the additional parameters that show up in AsDoc 214 * code hinting tools that developers shouldn't ever see. 215 * @private 216 */ 217 private static function internalToString(value:Object, 218 indent:int = 0, 219 refs:Dictionary= null, 220 namespaceURIs:Array = null, 221 exclude:Array = null):String 222 { 223 var str:String; 224 var type:String = value == null ? "null" : typeof(value); 225 switch (type) 226 { 227 case "boolean": 228 case "number": 229 { 230 return value.toString(); 231 } 232 233 case "string": 234 { 235 return "\"" + value.toString() + "\""; 236 } 237 238 case "object": 239 { 240 if (value is Date) 241 { 242 return value.toString(); 243 } 244 else if (value is XMLNode) 245 { 246 return value.toString(); 247 } 248 else if (value is Class) 249 { 250 return "(" + getQualifiedClassName(value) + ")"; 251 } 252 else 253 { 254 var classInfo:Object = getClassInfo(value, exclude, 255 { includeReadOnly: true, uris: namespaceURIs }); 256 257 var properties:Array = classInfo.properties; 258 259 str = "(" + classInfo.name + ")"; 260 261 // refs help us avoid circular reference infinite recursion. 262 // Each time an object is encoumtered it is pushed onto the 263 // refs stack so that we can determine if we have visited 264 // this object already. 265 if (refs == null) 266 refs = new Dictionary(true); 267 268 // Check to be sure we haven't processed this object before 269 var id:Object = refs[value]; 270 if (id != null) 271 { 272 str += "#" + int(id); 273 return str; 274 } 275 276 if (value != null) 277 { 278 str += "#" + refCount.toString(); 279 refs[value] = refCount; 280 refCount++; 281 } 282 283 var isArray:Boolean = value is Array; 284 var prop:*; 285 indent += 2; 286 287 // Print all of the variable values. 288 for (var j:int = 0; j < properties.length; j++) 289 { 290 str = newline(str, indent); 291 prop = properties[j]; 292 if (isArray) 293 str += "["; 294 str += prop.toString(); 295 if (isArray) 296 str += "] "; 297 else 298 str += " = "; 299 try 300 { 301 str += internalToString( 302 value[prop], indent, refs, namespaceURIs, 303 exclude); 304 } 305 catch(e:Error) 306 { 307 // value[prop] can cause an RTE 308 // for certain properties of certain objects. 309 // For example, accessing the properties 310 // actionScriptVersion 311 // childAllowsParent 312 // frameRate 313 // height 314 // loader 315 // parentAllowsChild 316 // sameDomain 317 // swfVersion 318 // width 319 // of a Stage's loaderInfo causes 320 // Error #2099: The loading object is not 321 // sufficiently loaded to provide this information 322 // In this case, we simply output ? for the value. 323 str += "?"; 324 } 325 } 326 indent -= 2; 327 return str; 328 } 329 break; 330 } 331 332 case "xml": 333 { 334 return value.toString(); 335 } 336 337 default: 338 { 339 return "(" + type + ")"; 340 } 341 } 342 343 return "(unknown)"; 344 } 345 346 /** 347 * @private 348 * This method will append a newline and the specified number of spaces 349 * to the given string. 350 */ 351 private static function newline(str:String, n:int = 0):String 352 { 353 var result:String = str; 354 result += "\n"; 355 356 for (var i:int = 0; i < n; i++) 357 { 358 result += " "; 359 } 360 return result; 361 } 362 363 364 /** 365 * Returns information about the class, and properties of the class, for 366 * the specified Object. 367 * 368 * @param obj The Object to inspect. 369 * 370 * @param exclude Array of Strings specifying the property names that should be 371 * excluded from the returned result. For example, you could specify 372 * <code>["currentTarget", "target"]</code> for an Event object since these properties 373 * can cause the returned result to become large. 374 * 375 * @param options An Object containing one or more properties 376 * that control the information returned by this method. 377 * The properties include the following: 378 * 379 * <ul> 380 * <li><code>includeReadOnly</code>: If <code>false</code>, 381 * exclude Object properties that are read-only. 382 * The default value is <code>true</code>.</li> 383 * <li><code>includeTransient</code>: If <code>false</code>, 384 * exclude Object properties and variables that have <code>[Transient]</code> metadata. 385 * The default value is <code>true</code>.</li> 386 * <li><code>uris</code>: Array of Strings of all namespaces that should be included in the output. 387 * It does allow for a wildcard of "~~". 388 * By default, it is null, meaning no namespaces should be included. 389 * For example, you could specify <code>["mx_internal", "mx_object"]</code> 390 * or <code>["~~"]</code>.</li> 391 * </ul> 392 * 393 * @return An Object containing the following properties: 394 * <ul> 395 * <li><code>name</code>: String containing the name of the class;</li> 396 * <li><code>properties</code>: Sorted list of the property names of the specified object.</li> 397 * </ul> 398 * 399 * @langversion 3.0 400 * @playerversion Flash 9 401 * @playerversion AIR 1.1 402 * @productversion Flex 3 403 */ 404 public static function getClassInfo(obj:Object, 405 excludes:Array = null, 406 options:Object = null):Object 407 { 408 var n:int; 409 var i:int; 410 411 // this version doesn't handle ObjectProxy 412 413 if (options == null) 414 options = { includeReadOnly: true, uris: null, includeTransient: true }; 415 416 var result:Object; 417 var propertyNames:Array = []; 418 var cacheKey:String; 419 420 var className:String; 421 var classAlias:String; 422 var properties:XMLList; 423 var prop:XML; 424 var dynamic:Boolean = false; 425 var metadataInfo:Object; 426 427 if (typeof(obj) == "xml") 428 { 429 className = "XML"; 430 properties = obj.text(); 431 if (properties.length()) 432 propertyNames.push("*"); 433 properties = obj.attributes(); 434 } 435 else 436 { 437 // don't cache describe type. Makes it slower, but fewer dependencies 438 var classInfo:XML = describeType(obj); 439 className = classInfo.@name.toString(); 440 classAlias = classInfo.@alias.toString(); 441 dynamic = (classInfo.@isDynamic.toString() == "true"); 442 443 if (options.includeReadOnly) 444 properties = classInfo..accessor.(@access != "writeonly") + classInfo..variable; 445 else 446 properties = classInfo..accessor.(@access == "readwrite") + classInfo..variable; 447 448 var numericIndex:Boolean = false; 449 } 450 451 // If type is not dynamic, check our cache for class info... 452 if (!dynamic) 453 { 454 cacheKey = getCacheKey(obj, excludes, options); 455 result = CLASS_INFO_CACHE[cacheKey]; 456 if (result != null) 457 return result; 458 } 459 460 result = {}; 461 result["name"] = className; 462 result["alias"] = classAlias; 463 result["properties"] = propertyNames; 464 result["dynamic"] = dynamic; 465 result["metadata"] = metadataInfo = recordMetadata(properties); 466 467 var excludeObject:Object = {}; 468 if (excludes) 469 { 470 n = excludes.length; 471 for (i = 0; i < n; i++) 472 { 473 excludeObject[excludes[i]] = 1; 474 } 475 } 476 477 var isArray:Boolean = className == "Array"; 478 if (dynamic) 479 { 480 for (var p:String in obj) 481 { 482 if (excludeObject[p] != 1) 483 { 484 if (isArray) 485 { 486 var pi:Number = parseInt(p); 487 if (isNaN(pi)) 488 propertyNames.push(new QName("", p)); 489 else 490 propertyNames.push(pi); 491 } 492 else 493 { 494 propertyNames.push(new QName("", p)); 495 } 496 } 497 } 498 numericIndex = isArray && !isNaN(Number(p)); 499 } 500 501 if (className == "Object" || isArray) 502 { 503 // Do nothing since we've already got the dynamic members 504 } 505 else if (className == "XML") 506 { 507 n = properties.length(); 508 for (i = 0; i < n; i++) 509 { 510 p = properties[i].name(); 511 if (excludeObject[p] != 1) 512 propertyNames.push(new QName("", "@" + p)); 513 } 514 } 515 else 516 { 517 n = properties.length(); 518 var uris:Array = options.uris; 519 var uri:String; 520 var qName:QName; 521 for (i = 0; i < n; i++) 522 { 523 prop = properties[i]; 524 p = prop.@name.toString(); 525 uri = prop.@uri.toString(); 526 527 if (excludeObject[p] == 1) 528 continue; 529 530 if (!options.includeTransient && internalHasMetadata(metadataInfo, p, "Transient")) 531 continue; 532 533 if (uris != null) 534 { 535 if (uris.length == 1 && uris[0] == "*") 536 { 537 qName = new QName(uri, p); 538 try 539 { 540 obj[qName]; // access the property to ensure it is supported 541 propertyNames.push(); 542 } 543 catch(e:Error) 544 { 545 // don't keep property name 546 } 547 } 548 else 549 { 550 for (var j:int = 0; j < uris.length; j++) 551 { 552 uri = uris[j]; 553 if (prop.@uri.toString() == uri) 554 { 555 qName = new QName(uri, p); 556 try 557 { 558 obj[qName]; 559 propertyNames.push(qName); 560 } 561 catch(e:Error) 562 { 563 // don't keep property name 564 } 565 } 566 } 567 } 568 } 569 else if (uri.length == 0) 570 { 571 qName = new QName(uri, p); 572 try 573 { 574 obj[qName]; 575 propertyNames.push(qName); 576 } 577 catch(e:Error) 578 { 579 // don't keep property name 580 } 581 } 582 } 583 } 584 585 propertyNames.sort(Array.CASEINSENSITIVE | 586 (numericIndex ? Array.NUMERIC : 0)); 587 // remove any duplicates, i.e. any items that can't be distingushed by toString() 588 for (i = 0; i < propertyNames.length - 1; i++) 589 { 590 // the list is sorted so any duplicates should be adjacent 591 // two properties are only equal if both the uri and local name are identical 592 if (propertyNames[i].toString() == propertyNames[i + 1].toString()) 593 { 594 propertyNames.splice(i, 1); 595 i--; // back up 596 } 597 } 598 599 // For normal, non-dynamic classes we cache the class info 600 if (!dynamic) 601 { 602 cacheKey = getCacheKey(obj, excludes, options); 603 CLASS_INFO_CACHE[cacheKey] = result; 604 } 605 606 return result; 607 } 608 609 /** 610 * @private 611 */ 612 private static function internalHasMetadata(metadataInfo:Object, propName:String, metadataName:String):Boolean 613 { 614 if (metadataInfo != null) 615 { 616 var metadata:Object = metadataInfo[propName]; 617 if (metadata != null) 618 { 619 if (metadata[metadataName] != null) 620 return true; 621 } 622 } 623 return false; 624 } 625 626 /** 627 * @private 628 */ 629 private static function recordMetadata(properties:XMLList):Object 630 { 631 var result:Object = null; 632 633 try 634 { 635 for each (var prop:XML in properties) 636 { 637 var propName:String = prop.attribute("name").toString(); 638 var metadataList:XMLList = prop.metadata; 639 640 if (metadataList.length() > 0) 641 { 642 if (result == null) 643 result = {}; 644 645 var metadata:Object = {}; 646 result[propName] = metadata; 647 648 for each (var md:XML in metadataList) 649 { 650 var mdName:String = md.attribute("name").toString(); 651 652 var argsList:XMLList = md.arg; 653 var value:Object = {}; 654 655 for each (var arg:XML in argsList) 656 { 657 var argKey:String = arg.attribute("key").toString(); 658 if (argKey != null) 659 { 660 var argValue:String = arg.attribute("value").toString(); 661 value[argKey] = argValue; 662 } 663 } 664 665 var existing:Object = metadata[mdName]; 666 if (existing != null) 667 { 668 var existingArray:Array; 669 if (existing is Array) 670 existingArray = existing as Array; 671 else 672 existingArray = []; 673 existingArray.push(value); 674 existing = existingArray; 675 } 676 else 677 { 678 existing = value; 679 } 680 metadata[mdName] = existing; 681 } 682 } 683 } 684 } 685 catch(e:Error) 686 { 687 } 688 689 return result; 690 } 691 692 /** 693 * @private 694 */ 695 private static function getCacheKey(o:Object, excludes:Array = null, options:Object = null):String 696 { 697 var key:String = getQualifiedClassName(o); 698 699 if (excludes != null) 700 { 701 for (var i:uint = 0; i < excludes.length; i++) 702 { 703 var excl:String = excludes[i] as String; 704 if (excl != null) 705 key += excl; 706 } 707 } 708 709 if (options != null) 710 { 711 for (var flag:String in options) 712 { 713 key += flag; 714 var value:String = options[flag] as String; 715 if (value != null) 716 key += value; 717 } 718 } 719 return key; 720 } 721 722 723 /** 724 * @private 725 */ 726 private static var refCount:int = 0; 727 728 /** 729 * @private 730 */ 731 private static var CLASS_INFO_CACHE:Object = {}; 732} 733 734}