blob: 59eeb55e1e8982adc3a57a7551e738b06de1bdf4 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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 mx.core.IPropertyChangeNotifier;
import mx.core.IUIComponent;
import mx.core.IUID;
import mx.core.mx_internal;
use namespace mx_internal;
/**
* The UIDUtil class is an all-static class
* with methods for working with UIDs (unique identifiers) within Flex.
* You do not create instances of UIDUtil;
* instead you simply call static methods such as the
* <code>UIDUtil.createUID()</code> method.
*
* <p><b>Note</b>: If you have a dynamic object that has no [Bindable] properties
* (which force the object to implement the IUID interface), Flex adds an
* <code>mx_internal_uid</code> property that contains a UID to the object.
* To avoid having this field
* in your dynamic object, make it [Bindable], implement the IUID interface
* in the object class, or set a <coded>uid</coded> property with a value.</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public class UIDUtil
{
include "../core/Version.as";
//--------------------------------------------------------------------------
//
// Class constants
//
//--------------------------------------------------------------------------
/**
* @private
* Char codes for 0123456789ABCDEF
*/
private static const ALPHA_CHAR_CODES:Array = [48, 49, 50, 51, 52, 53, 54,
55, 56, 57, 65, 66, 67, 68, 69, 70];
private static const DASH:int = 45; // dash ascii
private static const UIDBuffer:ByteArray = new ByteArray(); // static ByteArray used for UID generation to save memory allocation cost
//--------------------------------------------------------------------------
//
// Class variables
//
//--------------------------------------------------------------------------
/**
* This Dictionary records all generated uids for all existing items.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
private static var uidDictionary:Dictionary = new Dictionary(true);
//--------------------------------------------------------------------------
//
// Class methods
//
//--------------------------------------------------------------------------
/**
* Generates a UID (unique identifier) based on ActionScript's
* pseudo-random number generator and the current time.
*
* <p>The UID has the form
* <code>"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"</code>
* where X is a hexadecimal digit (0-9, A-F).</p>
*
* <p>This UID will not be truly globally unique; but it is the best
* we can do without player support for UID generation.</p>
*
* @return The newly-generated UID.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public static function createUID():String
{
UIDBuffer.position = 0;
var i:int;
var j:int;
for (i = 0; i < 8; i++)
{
UIDBuffer.writeByte(ALPHA_CHAR_CODES[int(Math.random() * 16)]);
}
for (i = 0; i < 3; i++)
{
UIDBuffer.writeByte(DASH);
for (j = 0; j < 4; j++)
{
UIDBuffer.writeByte(ALPHA_CHAR_CODES[int(Math.random() * 16)]);
}
}
UIDBuffer.writeByte(DASH);
var time:uint = new Date().getTime(); // extract last 8 digits
var timeString:String = time.toString(16).toUpperCase();
// 0xFFFFFFFF milliseconds ~= 3 days, so timeString may have between 1 and 8 digits, hence we need to pad with 0s to 8 digits
for (i = 8; i > timeString.length; i--)
UIDBuffer.writeByte(48);
UIDBuffer.writeUTFBytes(timeString);
for (i = 0; i < 4; i++)
{
UIDBuffer.writeByte(ALPHA_CHAR_CODES[int(Math.random() * 16)]);
}
return UIDBuffer.toString();
}
/**
* Converts a 128-bit UID encoded as a ByteArray to a String representation.
* The format matches that generated by createUID. If a suitable ByteArray
* is not provided, null is returned.
*
* @param ba ByteArray 16 bytes in length representing a 128-bit UID.
*
* @return String representation of the UID, or null if an invalid
* ByteArray is provided.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public static function fromByteArray(ba:ByteArray):String
{
if (ba != null && ba.length >= 16 && ba.bytesAvailable >= 16)
{
UIDBuffer.position = 0;
var index:uint = 0;
for (var i:uint = 0; i < 16; i++)
{
if (i == 4 || i == 6 || i == 8 || i == 10)
UIDBuffer.writeByte(DASH); // Hyphen char code
var b:int = ba.readByte();
UIDBuffer.writeByte(ALPHA_CHAR_CODES[(b & 0xF0) >>> 4]);
UIDBuffer.writeByte(ALPHA_CHAR_CODES[(b & 0x0F)]);
}
return UIDBuffer.toString();
}
return null;
}
/**
* A utility method to check whether a String value represents a
* correctly formatted UID value. UID values are expected to be
* in the format generated by createUID(), implying that only
* capitalized A-F characters in addition to 0-9 digits are
* supported.
*
* @param uid The value to test whether it is formatted as a UID.
*
* @return Returns true if the value is formatted as a UID.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public static function isUID(uid:String):Boolean
{
if (uid != null && uid.length == 36)
{
for (var i:uint = 0; i < 36; i++)
{
var c:Number = uid.charCodeAt(i);
// Check for correctly placed hyphens
if (i == 8 || i == 13 || i == 18 || i == 23)
{
if (c != DASH)
{
return false;
}
}
// We allow capital alpha-numeric hex digits only
else if (c < 48 || c > 70 || (c > 57 && c < 65))
{
return false;
}
}
return true;
}
return false;
}
/**
* Converts a UID formatted String to a ByteArray. The UID must be in the
* format generated by createUID, otherwise null is returned.
*
* @param String representing a 128-bit UID
*
* @return ByteArray 16 bytes in length representing the 128-bits of the
* UID or null if the uid could not be converted.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public static function toByteArray(uid:String):ByteArray
{
if (isUID(uid))
{
var result:ByteArray = new ByteArray();
for (var i:uint = 0; i < uid.length; i++)
{
var c:String = uid.charAt(i);
if (c == "-")
continue;
var h1:uint = getDigit(c);
i++;
var h2:uint = getDigit(uid.charAt(i));
result.writeByte(((h1 << 4) | h2) & 0xFF);
}
result.position = 0;
return result;
}
return null;
}
/**
* Returns the UID (unique identifier) for the specified object.
* If the specified object doesn't have an UID
* then the method assigns one to it.
* If a map is specified this method will use the map
* to construct the UID.
* As a special case, if the item passed in is null,
* this method returns a null UID.
*
* @param item Object that we need to find the UID for.
*
* @return The UID that was either found or generated.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public static function getUID(item:Object):String
{
var result:String = null;
if (item == null)
return result;
if (item is IUID)
{
result = IUID(item).uid;
if (result == null || result.length == 0)
{
result = createUID();
IUID(item).uid = result;
}
}
else if ((item is IPropertyChangeNotifier) &&
!(item is IUIComponent))
{
result = IPropertyChangeNotifier(item).uid;
if (result == null || result.length == 0)
{
result = createUID();
IPropertyChangeNotifier(item).uid = result;
}
}
else if (item is String)
{
return item as String;
}
else
{
try
{
// We don't create uids for XMLLists, but if
// there's only a single XML node, we'll extract it.
if (item is XMLList && item.length == 1)
item = item[0];
if (item is XML)
{
// XML nodes carry their UID on the
// function-that-is-a-hashtable they can carry around.
// To decorate an XML node with a UID,
// we need to first initialize it for notification.
// There is a potential performance issue here,
// since notification does have a cost,
// but most use cases for needing a UID on an XML node also
// require listening for change notifications on the node.
var xitem:XML = XML(item);
var nodeKind:String = xitem.nodeKind();
if (nodeKind == "text" || nodeKind == "attribute")
return xitem.toString();
var notificationFunction:Function = xitem.notification();
if (!(notificationFunction is Function))
{
// The xml node hasn't already been initialized
// for notification, so do so now.
notificationFunction =
XMLNotifier.initializeXMLForNotification();
xitem.setNotification(notificationFunction);
}
// Generate a new uid for the node if necessary.
if (notificationFunction["uid"] == undefined)
result = notificationFunction["uid"] = createUID();
result = notificationFunction["uid"];
}
else
{
if ("mx_internal_uid" in item)
return item.mx_internal_uid;
if ("uid" in item)
return item.uid;
result = uidDictionary[item];
if (!result)
{
result = createUID();
try
{
item.mx_internal_uid = result;
}
catch(e:Error)
{
uidDictionary[item] = result;
}
}
}
}
catch(e:Error)
{
result = item.toString();
}
}
return result;
}
/**
* Returns the decimal representation of a hex digit.
* @private
*/
private static function getDigit(hex:String):uint
{
switch (hex)
{
case "A":
case "a":
return 10;
case "B":
case "b":
return 11;
case "C":
case "c":
return 12;
case "D":
case "d":
return 13;
case "E":
case "e":
return 14;
case "F":
case "f":
return 15;
default:
return new uint(hex);
}
}
}
}