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; 17 18import mx.core.IPropertyChangeNotifier; 19import mx.core.IUIComponent; 20import mx.core.IUID; 21import mx.core.mx_internal; 22 23use namespace mx_internal; 24 25/** 26 * The UIDUtil class is an all-static class 27 * with methods for working with UIDs (unique identifiers) within Flex. 28 * You do not create instances of UIDUtil; 29 * instead you simply call static methods such as the 30 * <code>UIDUtil.createUID()</code> method. 31 * 32 * <p><b>Note</b>: If you have a dynamic object that has no [Bindable] properties 33 * (which force the object to implement the IUID interface), Flex adds an 34 * <code>mx_internal_uid</code> property that contains a UID to the object. 35 * To avoid having this field 36 * in your dynamic object, make it [Bindable], implement the IUID interface 37 * in the object class, or set a <coded>uid</coded> property with a value.</p> 38 * 39 * @langversion 3.0 40 * @playerversion Flash 9 41 * @playerversion AIR 1.1 42 * @productversion Flex 3 43 */ 44public class UIDUtil 45{ 46 include "../core/Version.as"; 47 48 //-------------------------------------------------------------------------- 49 // 50 // Class constants 51 // 52 //-------------------------------------------------------------------------- 53 54 /** 55 * @private 56 * Char codes for 0123456789ABCDEF 57 */ 58 private static const ALPHA_CHAR_CODES:Array = [48, 49, 50, 51, 52, 53, 54, 59 55, 56, 57, 65, 66, 67, 68, 69, 70]; 60 61 //-------------------------------------------------------------------------- 62 // 63 // Class variables 64 // 65 //-------------------------------------------------------------------------- 66 67 /** 68 * This Dictionary records all generated uids for all existing items. 69 * 70 * @langversion 3.0 71 * @playerversion Flash 9 72 * @playerversion AIR 1.1 73 * @productversion Flex 3 74 */ 75 private static var uidDictionary:Dictionary = new Dictionary(true); 76 77 //-------------------------------------------------------------------------- 78 // 79 // Class methods 80 // 81 //-------------------------------------------------------------------------- 82 83 /** 84 * Generates a UID (unique identifier) based on ActionScript's 85 * pseudo-random number generator and the current time. 86 * 87 * <p>The UID has the form 88 * <code>"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"</code> 89 * where X is a hexadecimal digit (0-9, A-F).</p> 90 * 91 * <p>This UID will not be truly globally unique; but it is the best 92 * we can do without player support for UID generation.</p> 93 * 94 * @return The newly-generated UID. 95 * 96 * @langversion 3.0 97 * @playerversion Flash 9 98 * @playerversion AIR 1.1 99 * @productversion Flex 3 100 */ 101 public static function createUID():String 102 { 103 var uid:Array = new Array(36); 104 var index:int = 0; 105 106 var i:int; 107 var j:int; 108 109 for (i = 0; i < 8; i++) 110 { 111 uid[index++] = ALPHA_CHAR_CODES[Math.floor(Math.random() * 16)]; 112 } 113 114 for (i = 0; i < 3; i++) 115 { 116 uid[index++] = 45; // charCode for "-" 117 118 for (j = 0; j < 4; j++) 119 { 120 uid[index++] = ALPHA_CHAR_CODES[Math.floor(Math.random() * 16)]; 121 } 122 } 123 124 uid[index++] = 45; // charCode for "-" 125 126 var time:Number = new Date().getTime(); 127 // Note: time is the number of milliseconds since 1970, 128 // which is currently more than one trillion. 129 // We use the low 8 hex digits of this number in the UID. 130 // Just in case the system clock has been reset to 131 // Jan 1-4, 1970 (in which case this number could have only 132 // 1-7 hex digits), we pad on the left with 7 zeros 133 // before taking the low digits. 134 var timeString:String = ("0000000" + time.toString(16).toUpperCase()).substr(-8); 135 136 for (i = 0; i < 8; i++) 137 { 138 uid[index++] = timeString.charCodeAt(i); 139 } 140 141 for (i = 0; i < 4; i++) 142 { 143 uid[index++] = ALPHA_CHAR_CODES[Math.floor(Math.random() * 16)]; 144 } 145 146 return String.fromCharCode.apply(null, uid); 147 } 148 149 /** 150 * Converts a 128-bit UID encoded as a ByteArray to a String representation. 151 * The format matches that generated by createUID. If a suitable ByteArray 152 * is not provided, null is returned. 153 * 154 * @param ba ByteArray 16 bytes in length representing a 128-bit UID. 155 * 156 * @return String representation of the UID, or null if an invalid 157 * ByteArray is provided. 158 * 159 * @langversion 3.0 160 * @playerversion Flash 9 161 * @playerversion AIR 1.1 162 * @productversion Flex 3 163 */ 164 public static function fromByteArray(ba:ByteArray):String 165 { 166 if (ba != null && ba.length >= 16 && ba.bytesAvailable >= 16) 167 { 168 var chars:Array = new Array(36); 169 var index:uint = 0; 170 for (var i:uint = 0; i < 16; i++) 171 { 172 if (i == 4 || i == 6 || i == 8 || i == 10) 173 chars[index++] = 45; // Hyphen char code 174 175 var b:int = ba.readByte(); 176 chars[index++] = ALPHA_CHAR_CODES[(b & 0xF0) >>> 4]; 177 chars[index++] = ALPHA_CHAR_CODES[(b & 0x0F)]; 178 } 179 return String.fromCharCode.apply(null, chars); 180 } 181 182 return null; 183 } 184 185 /** 186 * A utility method to check whether a String value represents a 187 * correctly formatted UID value. UID values are expected to be 188 * in the format generated by createUID(), implying that only 189 * capitalized A-F characters in addition to 0-9 digits are 190 * supported. 191 * 192 * @param uid The value to test whether it is formatted as a UID. 193 * 194 * @return Returns true if the value is formatted as a UID. 195 * 196 * @langversion 3.0 197 * @playerversion Flash 9 198 * @playerversion AIR 1.1 199 * @productversion Flex 3 200 */ 201 public static function isUID(uid:String):Boolean 202 { 203 if (uid != null && uid.length == 36) 204 { 205 for (var i:uint = 0; i < 36; i++) 206 { 207 var c:Number = uid.charCodeAt(i); 208 209 // Check for correctly placed hyphens 210 if (i == 8 || i == 13 || i == 18 || i == 23) 211 { 212 if (c != 45) 213 { 214 return false; 215 } 216 } 217 // We allow capital alpha-numeric hex digits only 218 else if (c < 48 || c > 70 || (c > 57 && c < 65)) 219 { 220 return false; 221 } 222 } 223 224 return true; 225 } 226 227 return false; 228 } 229 230 /** 231 * Converts a UID formatted String to a ByteArray. The UID must be in the 232 * format generated by createUID, otherwise null is returned. 233 * 234 * @param String representing a 128-bit UID 235 * 236 * @return ByteArray 16 bytes in length representing the 128-bits of the 237 * UID or null if the uid could not be converted. 238 * 239 * @langversion 3.0 240 * @playerversion Flash 9 241 * @playerversion AIR 1.1 242 * @productversion Flex 3 243 */ 244 public static function toByteArray(uid:String):ByteArray 245 { 246 if (isUID(uid)) 247 { 248 var result:ByteArray = new ByteArray(); 249 250 for (var i:uint = 0; i < uid.length; i++) 251 { 252 var c:String = uid.charAt(i); 253 if (c == "-") 254 continue; 255 var h1:uint = getDigit(c); 256 i++; 257 var h2:uint = getDigit(uid.charAt(i)); 258 result.writeByte(((h1 << 4) | h2) & 0xFF); 259 } 260 result.position = 0; 261 return result; 262 } 263 264 return null; 265 } 266 267 /** 268 * Returns the UID (unique identifier) for the specified object. 269 * If the specified object doesn't have an UID 270 * then the method assigns one to it. 271 * If a map is specified this method will use the map 272 * to construct the UID. 273 * As a special case, if the item passed in is null, 274 * this method returns a null UID. 275 * 276 * @param item Object that we need to find the UID for. 277 * 278 * @return The UID that was either found or generated. 279 * 280 * @langversion 3.0 281 * @playerversion Flash 9 282 * @playerversion AIR 1.1 283 * @productversion Flex 3 284 */ 285 public static function getUID(item:Object):String 286 { 287 var result:String = null; 288 289 if (item == null) 290 return result; 291 292 if (item is IUID) 293 { 294 result = IUID(item).uid; 295 if (result == null || result.length == 0) 296 { 297 result = createUID(); 298 IUID(item).uid = result; 299 } 300 } 301 else if ((item is IPropertyChangeNotifier) && 302 !(item is IUIComponent)) 303 { 304 result = IPropertyChangeNotifier(item).uid; 305 if (result == null || result.length == 0) 306 { 307 result = createUID(); 308 IPropertyChangeNotifier(item).uid = result; 309 } 310 } 311 else if (item is String) 312 { 313 return item as String; 314 } 315 else 316 { 317 try 318 { 319 // We don't create uids for XMLLists, but if 320 // there's only a single XML node, we'll extract it. 321 if (item is XMLList && item.length == 1) 322 item = item[0]; 323 324 if (item is XML) 325 { 326 // XML nodes carry their UID on the 327 // function-that-is-a-hashtable they can carry around. 328 // To decorate an XML node with a UID, 329 // we need to first initialize it for notification. 330 // There is a potential performance issue here, 331 // since notification does have a cost, 332 // but most use cases for needing a UID on an XML node also 333 // require listening for change notifications on the node. 334 var xitem:XML = XML(item); 335 var nodeKind:String = xitem.nodeKind(); 336 if (nodeKind == "text" || nodeKind == "attribute") 337 return xitem.toString(); 338 339 var notificationFunction:Function = xitem.notification(); 340 if (!(notificationFunction is Function)) 341 { 342 // The xml node hasn't already been initialized 343 // for notification, so do so now. 344 notificationFunction = 345 XMLNotifier.initializeXMLForNotification(); 346 xitem.setNotification(notificationFunction); 347 } 348 349 // Generate a new uid for the node if necessary. 350 if (notificationFunction["uid"] == undefined) 351 result = notificationFunction["uid"] = createUID(); 352 353 result = notificationFunction["uid"]; 354 } 355 else 356 { 357 if ("mx_internal_uid" in item) 358 return item.mx_internal_uid; 359 360 if ("uid" in item) 361 return item.uid; 362 363 result = uidDictionary[item]; 364 365 if (!result) 366 { 367 result = createUID(); 368 try 369 { 370 item.mx_internal_uid = result; 371 } 372 catch(e:Error) 373 { 374 uidDictionary[item] = result; 375 } 376 } 377 } 378 } 379 catch(e:Error) 380 { 381 result = item.toString(); 382 } 383 } 384 385 return result; 386 } 387 388 /** 389 * Returns the decimal representation of a hex digit. 390 * @private 391 */ 392 private static function getDigit(hex:String):uint 393 { 394 switch (hex) 395 { 396 case "A": 397 case "a": 398 return 10; 399 case "B": 400 case "b": 401 return 11; 402 case "C": 403 case "c": 404 return 12; 405 case "D": 406 case "d": 407 return 13; 408 case "E": 409 case "e": 410 return 14; 411 case "F": 412 case "f": 413 return 15; 414 default: 415 return new uint(hex); 416 } 417 } 418} 419 420} 421