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"); } }