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