package org.doomdark.uuid;
import java.io.PrintStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
/* JUG Java Uuid Generator
*
* Copyright (c) 2002 Tatu Saloranta, tatu.saloranta@iki.fi
*
* You can redistribute this work and/or modify it under the terms of
* LGPL (Lesser General Public License), as published by
* Free Software Foundation (http://www.fsf.org). No warranty is
* implied. See LICENSE for details about licensing.
*/
/**
* UUIDGenerator is the class that contains factory methods for
* generating UUIDs using one of the three specified 'standard'
* UUID generation methods:
* (see <a href="http://www1.ics.uci.edu/~ejw/authoring/uuid-guid/draft-leach-uuids-guids-01.txt">draft-leach-uuids-guids-01.txt</a> for details)
* <ul>
* <li>Time-based generation generates UUID using spatial and
* temporal uniqueness. Spatial uniqueness is derived from
* ethernet address (MAC, 802.1); temporal from system clock.
* See the details from the explanation of
* {@link #generateTimeBasedUUID} function.
* <li>Name-based method uses MD5 hash (or, optionally any user-specified
* digest method) of the string formed from
* a name space and name.
* <li>Random method uses Java2 API's SecureRandom to produce
* cryptographically secure UUIDs.
* <li>Tag URI - method uses a variation of name-based method; instead of
* using a name space UUID and name, a hash (MD5 by default) is calculated
* from URI-tag-prefix, 2 obligatory strings (URL, path) and one
* optional string (current date). The resulting UUID is still considered
* to be 'name-based UUID' as the specification does not have additional
* UUID type ids available.
* Note that this is a non-standard method and not strictly UUID-'standard'
* compliant.
* </ul>
*
* Some comments about performance:
* <ul>
* <li>For non-performance critical generation, all methods with default
* arguments (default random number generator, default hash algorithm)
* should do just fine.
* <li>When optimizing performance, it's better to use explicit random
* number generator and/or hash algorithm; this way global instance
* sharing need not be synchronized
* <li>Which of the 3 methods is fastest? It depends, and the best way
* is to just measure performance, discarding the first UUID generated
* with the methods. With time-based method, main overhead comes from
* synchronization, with name-based (MD5-)hashing, and with random-based
* the speed of random-number generator. Additionally, all methods may
* incur some overhead when using the shared global random nunber
* generator or hash algorithm.
* <li>When generating the first UUID with random-/time-based methods,
* there may be noticeable delay, as the random number generator is
* initialized. This can be avoided by either pre-initialising the
* random number generator passed (with random-based method), or by
* generating a dummy UUID on a separate thread, when starting a
* program needs to generate UUIDs at a later point.
*
* </ul>
*/
public final class UUIDGenerator
{
private final static UUIDGenerator sSingleton = new UUIDGenerator();
/**
* Random-generator, used by various UUID-generation methods:
*/
private Random mRnd = null;
// Ethernet address for time-based UUIDs:
private final Object mDummyAddressLock = new Object();
private EthernetAddress mDummyAddress = null;
private final Object mTimerLock = new Object();
private UUIDTimer mTimer = null;
/**
* MD5 hasher for name-based digests:
*/
private MessageDigest mHasher = null;
/**
* Constructor is private to enforce singleton access.
*/
private UUIDGenerator() { }
/**
* Method used for accessing the singleton generator instance.
*/
public static UUIDGenerator getInstance()
{
return sSingleton;
}
/**
* Method that returns a randomly generated dummy ethernet address.
* To prevent collision with real addresses, the returned address has
* the broadcast bit set, ie. it doesn't represent address of any existing
* NIC.
*
* Note that this dummy address will be shared for the lifetime of
* this UUIDGenerator, ie. only one is ever generated independent of
* how many times this methods is called.
*
* @return Randomly generated dummy ethernet broadcast address.
*/
public EthernetAddress getDummyAddress()
{
synchronized (mDummyAddressLock) {
if (mDummyAddress == null) {
Random rnd = getRandomNumberGenerator();
byte[] dummy = new byte[6];
rnd.nextBytes(dummy);
/* Need to set the broadcast bit to indicate it's not a real
* address.
*/
/* 08-Feb-2004, TSa: Note: it's the least bit, not highest;
* thanks to Ralf S. Engelschall for fix:
*/
dummy[0] |= (byte) 0x01;
try {
mDummyAddress = new EthernetAddress(dummy);
} catch (NumberFormatException nex) {
/* Let's just let this cause a null-pointer exception
* later on...
*/
}
}
}
return mDummyAddress;
}
/**
* Method for getting the shared random number generator used for
* generating the UUIDs. This way the initialization cost is only
* taken once; access need not be synchronized (or in cases where
* it has to, SecureRandom takes care of it); it might even be good
* for getting really 'random' stuff to get shared access...
*/
public Random getRandomNumberGenerator()
{
/* Could be synchronized, but since side effects are trivial
* (ie. possibility of generating more than one SecureRandom,
* of which all but one are dumped) let's not add synchronization
* overhead:
*/
if (mRnd == null) {
mRnd = new SecureRandom();
}
return mRnd;
}
/**
* Method that can be called to specify alternative random
* number generator to use. This is usually done to use
* implementation that is faster than
* {@link SecureRAndom} that is used by default.
*<p>
* Note that to avoid first-time initialization penalty
* of using {@link SecureRandom}, this method has to be called
* before generating the first random-number based UUID.
*/
public void setRandomNumberGenerator(Random r)
{
mRnd = r;
}
/* Method for getting the shared message digest (hash) algorithm.
* Whether to use the shared one or not depends; using shared instance
* adds synchronization overhead (access has to be sync'ed), but
* using multiple separate digests wastes memory.
*/
public MessageDigest getHashAlgorithm()
{
/* Similar to the shared random number generator, it's not necessary
* to synchronize initialization. However, use of the hash instance
* HAS to be synchronized by the caller to prevent problems with
* multiple threads updating digest etc.
*/
if (mHasher == null) {
try {
mHasher = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException nex) {
throw new Error("Couldn't instantiate an MD5 MessageDigest instance: "+nex.toString());
}
}
return mHasher;
}
/**
* Method for generating (pseudo-)random based UUIDs, using the
* default (shared) SecureRandom object.
*
* Note that the first time
* SecureRandom object is used, there is noticeable delay between
* calling the method and getting the reply. This is because SecureRandom
* has to initialize itself to reasonably random state. Thus, if you
* want to lessen delay, it may be be a good idea to either get the
* first random UUID asynchronously from a separate thread, or to
* use the other generateRandomBasedUUID passing a previously initialized
* SecureRandom instance.
*
* @return UUID generated using (pseudo-)random based method
*/
public UUID generateRandomBasedUUID()
{
return generateRandomBasedUUID(getRandomNumberGenerator());
}
/**
* Method for generating (pseudo-)random based UUIDs, using the
* specified SecureRandom object. To prevent/avoid delay JDK's
* default SecureRandom object causes when first random number
* is generated, it may be a good idea to initialize the SecureRandom
* instance (on a separate thread for example) when app starts.
*
* @param randomGenerator Random number generator to use for getting the
* random number from which UUID will be composed.
*
* @return UUID generated using (pseudo-)random based method
*/
public UUID generateRandomBasedUUID(Random randomGenerator)
{
byte[] rnd = new byte[16];
randomGenerator.nextBytes(rnd);
return new UUID(UUID.TYPE_RANDOM_BASED, rnd);
}
/**
* Method for generating time based UUIDs. Note that this version
* doesn't use any existing Hardware address (because none is available
* for some reason); instead it uses randomly generated dummy broadcast
* address.
*
* Note that since the dummy address is only to be created once and
* shared from there on, there is some synchronization overhead.
*
* @param hwAddress Hardware address (802.1) to use for generating
* spatially unique part of UUID. If system has more than one NIC,
* any address is usable. If no NIC is available (or its address
* not accessible; often the case with java apps), a randomly
* generated broadcast address is acceptable. If so, use the
* alternative method that takes no arguments.
*
* @return UUID generated using time based method
*/
public UUID generateTimeBasedUUID()
{
return generateTimeBasedUUID(getDummyAddress());
}
/**
* Method for generating time based UUIDs.
*
* @param hwAddress Hardware address (802.1) to use for generating
* spatially unique part of UUID. If system has more than one NIC,
* any address is usable. If no NIC is available (or its address
* not accessible; often the case with java apps), a randomly
* generated broadcast address is acceptable. If so, use the
* alternative method that takes no arguments.
*
* @return UUID generated using time based method
*/
public UUID generateTimeBasedUUID(EthernetAddress addr)
{
byte[] contents = new byte[16];
addr.toByteArray(contents, 10);
synchronized (mTimerLock) {
if (mTimer == null) {
mTimer = new UUIDTimer(getRandomNumberGenerator());
}
mTimer.getTimestamp(contents);
}
return new UUID(UUID.TYPE_TIME_BASED, contents);
}
/**
* Method for generating name-based UUIDs, using the standard
* name-based generation method described in the UUID specs,
* and the caller supplied hashing method.
*
* Note that this method is not synchronized, so caller has to make
* sure the digest object will not be accessed from other threads.
*
* Note that if you call this method directly (instead of calling
* the version with one less argument), you have to make sure that
* access to 'hash' is synchronized; either by only generating UUIDs
* from one single thread, or by using explicit sync'ing.
*
* @param nameSpaceUUID UUID of the namespace, as defined by the
* spec. UUID has 4 pre-defined "standard" name space strings
* that can be passed to UUID constructor (see example below).
* Note that this argument is optional; if no namespace is needed
* (for example when name includes namespace prefix), null may be
* passed.
* @param name Name to base the UUID on; for example,
* IP-name ("www.w3c.org") of the system for UUID.NAMESPACE_DNS,
* URL ("http://www.w3c.org/index.html") for UUID.NAMESPACE_URL
* and so on.
* @param hash Instance of MessageDigest to use for hashing the name
* value. hash.reset() will be called before calculating the has
* value, to make sure digest state is not random and UUID will
* not be randomised.
*
* @return UUID generated using name-based method based on the
* arguments given.
*
* Example:
* <code>
* UUID uuid = gen.generateNameBasedUUID(
* new UUID(UUID.NAMESPACE_DNS, "www.w3c.org"));
* </code>
*/
public UUID generateNameBasedUUID(UUID nameSpaceUUID, String name,
MessageDigest digest)
{
digest.reset();
if (nameSpaceUUID != null) {
digest.update(nameSpaceUUID.asByteArray());
}
digest.update(name.getBytes());
return new UUID(UUID.TYPE_NAME_BASED, digest.digest());
}
/**
* Method similar to the previous one; the difference being that a
* shared MD5 digest instance will be used. This also means that there is
* some synchronization overhead as MD5-instances are not thread-safe
* per se.
*/
public UUID generateNameBasedUUID(UUID nameSpaceUUID, String name)
{
MessageDigest hasher = getHashAlgorithm();
synchronized (hasher) {
return generateNameBasedUUID(nameSpaceUUID, name, getHashAlgorithm());
}
}
/**
* Method for generating UUIDs using tag URIs. A hash is calculated from
* the given tag URI (default being MD5 hash). The resulting UUIDs
* are reproducible, ie. given the same tag URI, same UUID will always
* result, much like with the default name-based generation method.
*
* Note that this a non-standard way of generating UUIDs; it will create
* UUIDs that appear to be name-based (and which are, but not using the
* method specified in UUID specs).
*
* @param name tag URI to base UUID on.
*/
public UUID generateTagURIBasedUUID(TagURI name)
{
return generateNameBasedUUID(null, name.toString());
}
/**
* Method for generating UUIDs using tag URIs. A hash is calculated from
* the given tag URI using the specified hashing algorith,.
* The resulting UUIDs are reproducible, ie. given the same tag URI and
* hash algorithm, same UUID will always result, much like with the
* default name-based generation method.
*
* Note that this a non-standard way of generating UUIDs; it will create
* UUIDs that appear to be name-based (and which are, but not using the
* method specified in UUID specs).
*
* @param name tag URI to base UUID on.
* @param hasher Hashing algorithm to use. Note that the caller has to
* make sure that it's thread-safe to use 'hasher', either by never
* calling this method from multiple threads, or by explicitly sync'ing
* the calls.
*/
public UUID generateTagURIBasedUUID(TagURI name, MessageDigest hasher)
{
return generateNameBasedUUID(null, name.toString(), hasher);
}
/**
* A simple test harness is added to make (automated) testing of the
* class easier.
*/
public static void main(String[] args)
{
UUIDGenerator g = UUIDGenerator.getInstance();
UUID nsUUID = new UUID(UUID.NAMESPACE_URL);
System.out.println("UUIDGenerator.main()");
System.out.println("--------------------");
System.out.println();
/* Let's test equality testing and ordering by using TreeSet;
* since all UUIDs should be unique set should contain them all,
* and in the specified order.
*/
final int ROUNDS = 4;
final int UUID_COUNT = ROUNDS * 3;
Set uuids = new TreeSet();
List timebased = new ArrayList(ROUNDS);
/* First we'll create the UUIDs and do conversion tests:
*/
for (int i = 0; i < ROUNDS; ++i) {
System.out.print("Random UUID: ");
UUID u = g.generateRandomBasedUUID();
uuids.add(u);
doTest(u, System.out, UUID.TYPE_RANDOM_BASED);
System.out.print("Time-based UUID: ");
u = g.generateTimeBasedUUID();
uuids.add(u);
timebased.add(u);
doTest(u, System.out, UUID.TYPE_TIME_BASED);
String name = "test-round-"+i;
System.out.print("Named-based UUID: (namespace URL, name '"
+name+"')");
u = g.generateNameBasedUUID(nsUUID, name);
uuids.add(u);
doTest(u, System.out, UUID.TYPE_NAME_BASED);
}
/* And then we'll see if comparision & sorting work as
* expected:
*/
int count = uuids.size();
System.out.print("Created "+UUID_COUNT+" uuids; ordered treeset contains "+count);
System.out.println((count == UUID_COUNT) ? " [OK]" : " [FAIL]");
System.out.println("Checking ordering:");
// First, major ordering by type:
Iterator it = uuids.iterator();
int prevType = -1;
System.out.print("Overall ordering by type: ");
while (it.hasNext()) {
System.out.print(".");
UUID uuid = (UUID) it.next();
int currType = uuid.getType();
if (currType < prevType) {
break;
}
prevType = currType;
}
System.out.println(it.hasNext() ? "FAIL" : "OK");
// And then ordering of time-based UUIDs:
it = uuids.iterator();
int lastIndex = -1;
System.out.print("Time-based UUID ordering on creation time: ");
while (it.hasNext()) {
UUID uuid = (UUID) it.next();
int index = timebased.indexOf(uuid);
if (index >= 0) {
System.out.print("[");
System.out.print(index);
System.out.print("]");
if (index <= lastIndex) {
break;
}
}
}
System.out.println(it.hasNext() ? "FAIL" : "OK");
/* Then we'll see if both shared and explicit null UUIDs are
* recognized as null UUIDs:
*/
doTestNull();
}
private final static void doTest(UUID uuid, PrintStream out, int type)
{
System.out.print(uuid.toString());
System.out.print(" [type: "+uuid.getType());
System.out.print(", expected "+type);
System.out.print(type == uuid.getType() ? ": OK" : ": FAIL");
System.out.println("]");
// Conversion test, UUID <-> string
System.out.print("... conversion UUID<->String: ");
try {
UUID uuid2 = UUID.valueOf(uuid.toString());
System.out.println(uuid2.toString());
System.out.print(" -> ");
System.out.println(uuid.equals(uuid2) ? "OK" : "FAIL");
} catch (NumberFormatException nex) {
System.out.println("[FAIL: "+nex.toString()+"]");
}
// Conversion test, UUID <-> byte array
System.out.print("... conversion UUID<->byte array: ");
{
UUID uuid3 = UUID.valueOf(uuid.asByteArray());
System.out.println(uuid3.toString());
System.out.print(" -> ");
System.out.println(uuid.equals(uuid3) ? "OK" : "FAIL");
}
System.out.print("... considered null? ");
boolean isNull = uuid.isNullUUID();
System.out.print(isNull);
System.out.print(" (shouldn't be) -> ");
System.out.println(isNull ? "FAIL" : "OK");
}
private final static void doTestNull()
{
UUID sharedNull = UUID.getNullUUID();
System.out.println("Testing null UUID checks:");
System.out.println("-------------------------");
System.out.print("Testing shared null uuid; considered null: ");
boolean ok = sharedNull.isNullUUID();
System.out.print(ok);
System.out.print("; expected true -> ");
System.out.println(ok ? "OK" : "FAIL");
UUID localNull = new UUID(new byte[16]); // java runtime clears the array
System.out.print("Testing explicit null uuid; considered null: ");
ok = localNull.isNullUUID();
System.out.print(ok);
System.out.print("; expected true -> ");
System.out.println(ok ? "OK" : "FAIL");
}
}