| /* |
| * 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 flex.messaging.util; |
| |
| import java.util.Random; |
| import java.util.UUID; |
| |
| public class UUIDUtils |
| { |
| private static Random _weakRand = new Random(); |
| |
| /** |
| * The spec indicates that our time value should be based on 100 nano |
| * second increments but our time granularity is in milliseconds. |
| * The spec also says we can approximate the time by doing an increment |
| * when we dole out new ids in the same millisecond. We can fit 10,000 |
| * 100 nanos into a single millisecond. |
| */ |
| private static final int MAX_IDS_PER_MILLI = 10000; |
| |
| /** |
| * Any given time-of-day value can only be used once; remember the last used |
| * value so we don't reuse them. |
| * <p>NOTE: this algorithm assumes the clock will not be turned back. |
| */ |
| private static long lastUsedTOD = 0; |
| /** Counter to use when we need more than one id in the same millisecond. */ |
| private static int numIdsThisMilli = 0; |
| |
| /** Hex digits, used for padding UUID strings with random characters. */ |
| private static final String alphaNum = "0123456789ABCDEF"; |
| |
| /** 4 bits per hex character. */ |
| private static final int BITS_PER_DIGIT = 4; |
| |
| private static final int BITS_PER_INT = 32; |
| private static final int BITS_PER_LONG = 64; |
| private static final int DIGITS_PER_INT = BITS_PER_INT / BITS_PER_DIGIT; |
| private static final int DIGITS_PER_LONG = BITS_PER_LONG / BITS_PER_DIGIT; |
| |
| /** |
| * @private |
| */ |
| private static char[] UPPER_DIGITS = new char[] { |
| '0', '1', '2', '3', '4', '5', '6', '7', |
| '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', |
| }; |
| |
| /** |
| * Private constructor to prevent instances from being created. |
| */ |
| private UUIDUtils() |
| { |
| } |
| |
| /** |
| * |
| * Use the createUUID function when you need a unique string that you will |
| * use as a persistent identifier in a distributed environment. To a very |
| * high degree of certainty, this function returns a unique value; no other |
| * invocation on the same or any other system should return the same value. |
| * |
| * @return a Universally Unique Identifier (UUID) |
| * Proper Format: `XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' |
| * where `X' stands for a hexadecimal digit (0-9 or A-F). |
| */ |
| public static String createUUID() |
| { |
| return createUUID(false); |
| } |
| |
| public static String createUUID(boolean secure) throws Error |
| { |
| Random rand = _weakRand; |
| if (secure) |
| throw new Error("Secure UUIDs not implemented"); |
| |
| StringBuffer s = new StringBuffer(36); |
| |
| appendHexString(uniqueTOD(), false, 11, s); |
| |
| // Just use random padding characters, but ensure that the high bit |
| // is set to eliminate chances of collision with an IEEE 802 address. |
| s.append( alphaNum.charAt( rand.nextInt(16) | 8 ) ); |
| |
| // Add random padding characters. |
| appendRandomHexChars(32 - s.length(), rand, s); |
| |
| //insert dashes in proper position. so the format matches CF |
| s.insert(8,"-"); |
| s.insert(13,"-"); |
| s.insert(18,"-"); |
| s.insert(23,"-"); |
| |
| return s.toString(); |
| } |
| |
| /** |
| * Converts a 128-bit UID encoded as a byte[] to a String representation. |
| * The format matches that generated by createUID. If a suitable byte[] |
| * is not provided, null is returned. |
| * |
| * @param ba byte[] 16 bytes in length representing a 128-bit UID. |
| * |
| * @return String representation of the UID, or null if an invalid |
| * byte[] is provided. |
| */ |
| public static String fromByteArray(byte[] ba) |
| { |
| if (ba == null || ba.length != 16) |
| return null; |
| |
| StringBuffer result = new StringBuffer(36); |
| for (int i = 0; i < 16; i++) |
| { |
| if (i == 4 || i == 6 || i == 8 || i == 10) |
| result.append('-'); |
| |
| result.append(UPPER_DIGITS[(ba[i] & 0xF0) >>> 4]); |
| result.append(UPPER_DIGITS[(ba[i] & 0x0F)]); |
| } |
| return result.toString(); |
| } |
| |
| |
| /** |
| * 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. |
| */ |
| public static boolean isUID(String uid) |
| { |
| if (uid == null || uid.length() != 36) |
| return false; |
| |
| char[] chars = uid.toCharArray(); |
| for (int i = 0; i < 36; i++) |
| { |
| char c = chars[i]; |
| |
| // Check for correctly placed hyphens |
| if (i == 8 || i == 13 || i == 18 || i == 23) |
| { |
| if (c != '-') |
| return false; |
| } |
| // We allow capital alpha-numeric hex digits only |
| else if (c < 48 || c > 70 || (c > 57 && c < 65)) |
| { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Converts a UID formatted String to a byte[]. The UID must be in the |
| * format generated by createUID, otherwise null is returned. |
| * |
| * @param uid String representing a 128-bit UID. |
| * |
| * @return byte[] 16 bytes in length representing the 128-bits of the |
| * UID or null if the uid could not be converted. |
| */ |
| public static byte[] toByteArray(String uid) |
| { |
| if (!isUID(uid)) |
| return null; |
| |
| byte[] result = new byte[16]; |
| char[] chars = uid.toCharArray(); |
| int r = 0; |
| |
| for (int i = 0; i < chars.length; i++) |
| { |
| if (chars[i] == '-') |
| continue; |
| int h1 = Character.digit(chars[i], 16); |
| i++; |
| int h2 = Character.digit(chars[i], 16); |
| result[r++] = (byte)(((h1 << 4) | h2) & 0xFF); |
| } |
| return result; |
| } |
| |
| private static void appendRandomHexChars(int n, Random rand, StringBuffer result) |
| { |
| int digitsPerInt = DIGITS_PER_INT; |
| while (n > 0) |
| { |
| int digitsToUse = Math.min(n, digitsPerInt); |
| n -= digitsToUse; |
| appendHexString(rand.nextInt(), true, digitsToUse, result); |
| } |
| } |
| |
| private static void appendHexString |
| (long value, boolean prependZeroes, int nLeastSignificantDigits, |
| StringBuffer result) |
| { |
| int bitsPerDigit = BITS_PER_DIGIT; |
| |
| long mask = (1L << bitsPerDigit) - 1; |
| |
| if (nLeastSignificantDigits < DIGITS_PER_LONG) |
| { |
| // Clear the bits that we don't care about. |
| value &= (1L << (bitsPerDigit * nLeastSignificantDigits)) - 1; |
| } |
| |
| // Reorder the sequence so that the first set of bits will become the |
| // last set of bits. |
| int i = 0; |
| long reorderedValue = 0; |
| if (value == 0) |
| { |
| // One zero is dumped. |
| i++; |
| } |
| else |
| { |
| do |
| { |
| reorderedValue = (reorderedValue << bitsPerDigit) | (value & mask); |
| value >>>= bitsPerDigit; |
| i++; |
| } while (value != 0); |
| } |
| |
| if (prependZeroes) |
| { |
| for (int j = nLeastSignificantDigits - i; j > 0; j--) |
| { |
| result.append('0'); |
| } |
| } |
| |
| |
| // Dump the reordered sequence, with the most significant character |
| // first. |
| for (; i > 0; i--) |
| { |
| result.append(alphaNum.charAt((int) (reorderedValue & mask))); |
| reorderedValue >>>= bitsPerDigit; |
| } |
| } |
| |
| private static String createInsecureUUID() |
| { |
| StringBuffer s = new StringBuffer(36); |
| |
| appendHexString(uniqueTOD(), false, 11, s); |
| |
| // Just use random padding characters, but ensure that the high bit |
| // is set to eliminate chances of collision with an IEEE 802 address. |
| s.append( alphaNum.charAt( _weakRand.nextInt(16) | 8 ) ); |
| |
| // Add random padding characters. |
| appendRandomHexChars(32 - s.length(), _weakRand, s); |
| |
| //insert dashes in proper position. so the format matches CF |
| s.insert(8,"-"); |
| s.insert(13,"-"); |
| s.insert(18,"-"); |
| s.insert(23,"-"); |
| |
| return s.toString(); |
| } |
| |
| /** |
| * @return a time value, unique for calls to this method loaded by the same classloader. |
| */ |
| private static synchronized long uniqueTOD() |
| { |
| long currentTOD = System.currentTimeMillis(); |
| |
| // Clock was set back... do not hang in this case waiting to catch up. |
| // Instead, rely on the random number part to differentiate the ids. |
| if (currentTOD < lastUsedTOD) |
| lastUsedTOD = currentTOD; |
| |
| if (currentTOD == lastUsedTOD) |
| { |
| numIdsThisMilli++; |
| /* |
| * Fall back to the old technique of sleeping if we allocate |
| * too many ids in one time interval. |
| */ |
| if (numIdsThisMilli >= MAX_IDS_PER_MILLI) |
| { |
| while ( currentTOD == lastUsedTOD ) |
| { |
| try { Thread.sleep(1); } catch ( Exception interrupt ) { /* swallow, wake up */ } |
| currentTOD = System.currentTimeMillis(); |
| } |
| lastUsedTOD = currentTOD; |
| numIdsThisMilli = 0; |
| } |
| } |
| else |
| { |
| // We have a new TOD, reset the counter |
| lastUsedTOD = currentTOD; |
| numIdsThisMilli = 0; |
| } |
| |
| return lastUsedTOD * MAX_IDS_PER_MILLI + (long)numIdsThisMilli; |
| } |
| } |