diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/UUIDFactory.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/UUIDFactory.java
index ef8fb9ca..d0e5b3f8 100644
--- a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/UUIDFactory.java
+++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/UUIDFactory.java
@@ -35,16 +35,25 @@ import java.net.SocketException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.UUID;
+import java.util.*;
/**
* A factory for creating UUIDs not directly supported by {@link java.util.UUID}.
+ *
* This class can create
- * version 1 (time based, using either MAC aka IEEE 802 address or Random "node" value)
- * and version 5 (SHA1 hash based) UUIDs.
+ * version 1 time based, using either IEEE 802 (mac) address or random "node" value
+ * and version 5 SHA1 hash based UUIDs.
+ *
+ *
+ * The node value for version 1 UUIDs will, by default, reflect the IEEE 802 (mac) address of one of
+ * the network interfaces of the local computer.
+ * This node value can be manually overridden by setting
+ * the system property {@code "com.twelvemonkeys.util.UUID.node"} to a valid IEEE 802 address, on the form
+ * {@code 12:34:56:78:9a:bc} or {@code 12-34-45-78-9a-bc}.
+ *
+ *
+ * The node value for the random "node" based version 1 UUIDs will be stable for the lifetime of the VM.
+ *
*
* @author Harald Kuhr
* @author last modified by $Author: haraldk$
@@ -54,14 +63,14 @@ import java.util.UUID;
* @see Wikipedia
* @see java.util.UUID
*/
-public class UUIDFactory {
+public final class UUIDFactory {
private static final String NODE_PROPERTY = "com.twelvemonkeys.util.UUID.node";
/**
* Nil UUID: {@code "00000000-0000-0000-0000-000000000000"}.
*
* The nil UUID is special form of UUID that is specified to have all
- * 128 bits set to zero.
+ * 128 bits set to zero. Not particularly useful.
*
* @see RFC 4122
*/
@@ -69,8 +78,9 @@ public class UUIDFactory {
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
- // Assumes MAC address is constant, which it may not be on clients moving from ethernet to wifi etc...
- // TODO: Update at some interval
+ private static final Comparator COMPARATOR = new UUIDComparator();
+
+ // Assumes MAC address is constant, which it may not be if a network card is replaced
static final long MAC_ADDRESS_NODE = getMacAddressNode();
static final long SECURE_RANDOM_NODE = getSecureRandomNode();
@@ -134,7 +144,7 @@ public class UUIDFactory {
String nodesString = nodesStrings[i];
try {
- String[] nodes = nodesString.split("(?<=(^|\\W)[0-9a-fA-F]{2})\\W(?=[0-9a-fA-F]{2}($|\\W))", 6);
+ String[] nodes = nodesString.split("(?<=(^|\\W)[0-9a-fA-F]{2})\\W(?=[0-9a-fA-F]{2}(\\W|$))", 6);
long nodeAddress = 0;
@@ -159,6 +169,8 @@ public class UUIDFactory {
return addressNodes;
}
+ private UUIDFactory() {}
+
// See also http://tools.ietf.org/html/rfc4122#appendix-B
// See http://tools.ietf.org/html/rfc4122: 4.3. Algorithm for Creating a Name-Based UUID
// TODO: Naming (of the method)
@@ -213,6 +225,7 @@ public class UUIDFactory {
// See http://tools.ietf.org/html/rfc4122#appendix-B
// TODO: Naming (of the method)
// TODO: Document that these can never clash with node based v1 UUIDs due to unicast/multicast bit
+ // However, no uniqueness between multiple mavhines/vms/nodes can be guaranteed.
static UUID timeRandomBasedV1() {
return new UUID(createTimeAndVersion(), createClockSeqAndNode(SECURE_RANDOM_NODE));
}
@@ -241,8 +254,52 @@ public class UUIDFactory {
return time;
}
- // TODO: Implement compare as spec'ed, see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7025832
- // - Probably create a comparator
+ /**
+ * Returns a comparator that compares UUIDs as 128 bit unsigned entities, as mentioned in RFC 4122.
+ * This is different than {@link UUID#compareTo(Object)} that compares the UUIDs as signed entities.
+ *
+ * @return a comparator that compares UUIDs as 128 bit unsigned entities.
+ *
+ * @see java.lang.UUID compareTo() does not do an unsigned compare
+ * @see
+ */
+ public static Comparator comparator() {
+ return COMPARATOR;
+ }
+
+ static final class UUIDComparator implements Comparator {
+ public int compare(UUID left, UUID right) {
+ // The ordering is intentionally set up so that the UUIDs
+ // can simply be numerically compared as two *UNSIGNED* numbers
+ if (left.getMostSignificantBits() >>> 32 < right.getMostSignificantBits() >>> 32) {
+ return -1;
+ }
+ else if (left.getMostSignificantBits() >>> 32 > right.getMostSignificantBits() >>> 32) {
+ return 1;
+ }
+ else if ((left.getMostSignificantBits() & 0xffffffffl) < (right.getMostSignificantBits() & 0xffffffffl)) {
+ return -1;
+ }
+ else if ((left.getMostSignificantBits() & 0xffffffffl) > (right.getMostSignificantBits() & 0xffffffffl)) {
+ return 1;
+ }
+ else if (left.getLeastSignificantBits() >>> 32 < right.getLeastSignificantBits() >>> 32) {
+ return -1;
+ }
+ else if (left.getLeastSignificantBits() >>> 32 > right.getLeastSignificantBits() >>> 32) {
+ return 1;
+ }
+ else if ((left.getLeastSignificantBits() & 0xffffffffl) < (right.getLeastSignificantBits() & 0xffffffffl)) {
+ return -1;
+ }
+ else if ((left.getLeastSignificantBits() & 0xffffffffl) > (right.getLeastSignificantBits() & 0xffffffffl)) {
+ return 1;
+ }
+
+ return 0;
+ }
+ }
+
/**
* A high-resolution timer for use in creating version 1 UUIDs.
diff --git a/sandbox/sandbox-common/src/test/java/com/twelvemonkeys/util/UUIDFactoryTest.java b/sandbox/sandbox-common/src/test/java/com/twelvemonkeys/util/UUIDFactoryTest.java
index cecce632..5f19c7ad 100644
--- a/sandbox/sandbox-common/src/test/java/com/twelvemonkeys/util/UUIDFactoryTest.java
+++ b/sandbox/sandbox-common/src/test/java/com/twelvemonkeys/util/UUIDFactoryTest.java
@@ -1,10 +1,12 @@
package com.twelvemonkeys.util;
+import org.junit.Ignore;
import org.junit.Test;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.util.Arrays;
+import java.util.Comparator;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -13,119 +15,186 @@ import java.util.concurrent.TimeUnit;
import static org.junit.Assert.*;
public class UUIDFactoryTest {
+ private static final String EXAMPLE_COM_UUID = "http://www.example.com/uuid/";
// Nil UUID
@Test
public void testNilUUID() {
- UUID nil = UUIDFactory.NIL;
UUID a = UUID.fromString("00000000-0000-0000-0000-000000000000");
UUID b = new UUID(0l, 0l);
- assertEquals(nil, b);
- assertEquals(nil, a);
- assertEquals(a, b);
+ assertEquals(UUIDFactory.NIL, b);
+ assertEquals(UUIDFactory.NIL, a);
+ assertEquals(a, b); // Sanity
- assertEquals(0, nil.variant());
- assertEquals(0, nil.version());
+ assertEquals(0, UUIDFactory.NIL.variant());
+ assertEquals(0, UUIDFactory.NIL.version());
}
// Version 3 UUIDs (for comparison with v5)
@Test
- public void testVersion3NameBasedMD5() throws UnsupportedEncodingException {
- String name = "http://www.example.com/uuid/";
- UUID a = UUID.nameUUIDFromBytes(name.getBytes("UTF-8"));
- assertEquals(3, a.version());
+ public void testVersion3NameBasedMD5VariantVersion() throws UnsupportedEncodingException {
+ UUID a = UUID.nameUUIDFromBytes(EXAMPLE_COM_UUID.getBytes("UTF-8"));
assertEquals(2, a.variant());
+ assertEquals(3, a.version());
+ }
- UUID b = UUID.nameUUIDFromBytes(name.getBytes("UTF-8"));
+ @Test
+ public void testVersion3NameBasedMD5Equals() throws UnsupportedEncodingException {
+ UUID a = UUID.nameUUIDFromBytes(EXAMPLE_COM_UUID.getBytes("UTF-8"));
+ UUID b = UUID.nameUUIDFromBytes(EXAMPLE_COM_UUID.getBytes("UTF-8"));
assertEquals(a, b);
+ }
- assertFalse(a.equals(UUIDFactory.nameUUIDFromBytesSHA1(name.getBytes("UTF-8"))));
+ @Test
+ public void testVersion3NameBasedMD5NotEqualSHA1() throws UnsupportedEncodingException {
+ UUID a = UUID.nameUUIDFromBytes(EXAMPLE_COM_UUID.getBytes("UTF-8"));
+ assertFalse(a.equals(UUIDFactory.nameUUIDFromBytesSHA1(EXAMPLE_COM_UUID.getBytes("UTF-8"))));
+ }
+ @Test
+ public void testVersion3NameBasedMD5FromStringRep() throws UnsupportedEncodingException {
+ UUID a = UUID.nameUUIDFromBytes(EXAMPLE_COM_UUID.getBytes("UTF-8"));
assertEquals(a, UUID.fromString(a.toString()));
}
// Version 5 UUIDs
@Test
- public void testVersion5NameBasedSHA1() throws UnsupportedEncodingException {
- String name = "http://www.example.com/uuid/";
- UUID a = UUIDFactory.nameUUIDFromBytesSHA1(name.getBytes("UTF-8"));
- assertEquals(5, a.version());
+ public void testVersion5NameBasedSHA1VariantVersion() throws UnsupportedEncodingException {
+ UUID a = UUIDFactory.nameUUIDFromBytesSHA1(EXAMPLE_COM_UUID.getBytes("UTF-8"));
assertEquals(2, a.variant());
+ assertEquals(5, a.version());
+ }
- UUID b = UUIDFactory.nameUUIDFromBytesSHA1(name.getBytes("UTF-8"));
+ @Test
+ public void testVersion5NameBasedSHA1Equals() throws UnsupportedEncodingException {
+ UUID a = UUIDFactory.nameUUIDFromBytesSHA1(EXAMPLE_COM_UUID.getBytes("UTF-8"));
+ UUID b = UUIDFactory.nameUUIDFromBytesSHA1(EXAMPLE_COM_UUID.getBytes("UTF-8"));
assertEquals(a, b);
+ }
- assertFalse(a.equals(UUID.nameUUIDFromBytes(name.getBytes("UTF-8"))));
+ @Test
+ public void testVersion5NameBasedSHA1NotEqualMD5() throws UnsupportedEncodingException {
+ UUID a = UUIDFactory.nameUUIDFromBytesSHA1(EXAMPLE_COM_UUID.getBytes("UTF-8"));
+ assertFalse(a.equals(UUID.nameUUIDFromBytes(EXAMPLE_COM_UUID.getBytes("UTF-8"))));
+ }
+
+ @Test
+ public void testVersion5NameBasedSHA1FromStringRep() throws UnsupportedEncodingException {
+ UUID a = UUIDFactory.nameUUIDFromBytesSHA1(EXAMPLE_COM_UUID.getBytes("UTF-8"));
assertEquals(a, UUID.fromString(a.toString()));
}
// Version 1 UUIDs
@Test
- public void testVersion1NodeBased() {
+ public void testVersion1NodeBasedVariantVersion() {
UUID uuid = UUIDFactory.timeNodeBasedV1();
- System.err.println("uuid: " + uuid);
-
- assertEquals(1, uuid.version());
assertEquals(2, uuid.variant());
+ assertEquals(1, uuid.version());
+ }
+ @Test
+ public void testVersion1NodeBasedMacAddress() {
+ UUID uuid = UUIDFactory.timeNodeBasedV1();
assertEquals(UUIDFactory.MAC_ADDRESS_NODE, uuid.node());
- // TODO: Test that this is actually a Mac address from the local computer, or specified through system property
+ // TODO: Test that this is actually a Mac address from the local computer, or specified through system property?
+ }
+ @Test
+ public void testVersion1NodeBasedFromStringRep() {
+ UUID uuid = UUIDFactory.timeNodeBasedV1();
assertEquals(uuid, UUID.fromString(uuid.toString()));
+ }
+ @Test
+ public void testVersion1NodeBasedClockSeq() {
+ UUID uuid = UUIDFactory.timeNodeBasedV1();
assertEquals(UUIDFactory.Clock.getClockSequence(), uuid.clockSequence());
+
// Test time fields (within reasonable limits +/- 100 ms or so?)
// TODO: Compare with system clock for sloppier resolution
assertEquals(UUIDFactory.Clock.currentTimeHundredNanos(), uuid.timestamp(), 1e6);
+ }
- assertEquals(0, (uuid.node() >> 40) & 1);
+ @Test
+ public void testVersion1NodeBasedTimestamp() {
+ UUID uuid = UUIDFactory.timeNodeBasedV1();
+ // Test time fields (within reasonable limits +/- 100 ms or so?)
+ assertEquals(UUIDFactory.Clock.currentTimeHundredNanos(), uuid.timestamp(), 1e6);
+ }
+
+ @Test
+ public void testVersion1NodeBasedUniMulticastBitUnset() {
+ // Do it a couple of times, to avoid accidentally have correct bit
+ for (int i = 0; i < 100; i++) {
+ UUID uuid = UUIDFactory.timeNodeBasedV1();
+ assertEquals(0, (uuid.node() >> 40) & 1);
+ }
}
@Test
public void testVersion1NodeBasedUnique() {
- UUID a = UUIDFactory.timeNodeBasedV1();
- UUID b = UUIDFactory.timeNodeBasedV1();
-
- System.err.println("a: " + a);
- System.err.println("b: " + b);
-
- assertFalse(a.equals(b));
+ for (int i = 0; i < 100; i++) {
+ UUID a = UUIDFactory.timeNodeBasedV1();
+ UUID b = UUIDFactory.timeNodeBasedV1();
+ assertFalse(a.equals(b));
+ }
}
@Test
- public void testVersion1SecureRandom() {
+ public void testVersion1SecureRandomVariantVersion() {
UUID uuid = UUIDFactory.timeRandomBasedV1();
- System.err.println("uuid: " + uuid);
- assertEquals(1, uuid.version());
assertEquals(2, uuid.variant());
+ assertEquals(1, uuid.version());
+ }
+ @Test
+ public void testVersion1SecureRandomNode() {
+ UUID uuid = UUIDFactory.timeRandomBasedV1();
assertEquals(UUIDFactory.SECURE_RANDOM_NODE, uuid.node());
+ }
+ @Test
+ public void testVersion1SecureRandomFromStringRep() {
+ UUID uuid = UUIDFactory.timeRandomBasedV1();
assertEquals(uuid, UUID.fromString(uuid.toString()));
+ }
+ @Test
+ public void testVersion1SecureRandomClockSeq() {
+ UUID uuid = UUIDFactory.timeRandomBasedV1();
assertEquals(UUIDFactory.Clock.getClockSequence(), uuid.clockSequence());
+ }
- // TODO: Test time fields (within reasonable limits +/- 100 ms or so?)
+ @Test
+ public void testVersion1SecureRandomTimestamp() {
+ UUID uuid = UUIDFactory.timeRandomBasedV1();
+
+ // Test time fields (within reasonable limits +/- 100 ms or so?)
assertEquals(UUIDFactory.Clock.currentTimeHundredNanos(), uuid.timestamp(), 1e6);
+ }
- assertEquals(1, (uuid.node() >> 40) & 1);
+ @Test
+ public void testVersion1SecureRandomUniMulticastBit() {
+ // Do it a couple of times, to avoid accidentally have correct bit
+ for (int i = 0; i < 100; i++) {
+ UUID uuid = UUIDFactory.timeRandomBasedV1();
+ assertEquals(1, (uuid.node() >> 40) & 1);
+ }
}
@Test
public void testVersion1SecureRandomUnique() {
- UUID a = UUIDFactory.timeRandomBasedV1();
- UUID b = UUIDFactory.timeRandomBasedV1();
-
- System.err.println("a: " + a);
- System.err.println("b: " + b);
-
- assertFalse(a.equals(b));
+ for (int i = 0; i < 100; i++) {
+ UUID a = UUIDFactory.timeRandomBasedV1();
+ UUID b = UUIDFactory.timeRandomBasedV1();
+ assertFalse(a.equals(b));
+ }
}
// Clock tests
@@ -242,10 +311,66 @@ public class UUIDFactoryTest {
UUIDFactory.parseMacAddressNodes("00:11:22:33:44:55:99");
}
- // Various testing
+ // Comparator test
+ @Test
+ public void testComparator() {
+ UUID min = new UUID(0, 0);
+ // Long.MAX_VALUE and MIN_VALUE are really adjacent values when comparing unsigned...
+ UUID midLow = new UUID(Long.MAX_VALUE, Long.MAX_VALUE);
+ UUID midHigh = new UUID(Long.MIN_VALUE, Long.MIN_VALUE);
+ UUID max = new UUID(-1l, -1l);
+
+ Comparator comparator = UUIDFactory.comparator();
+
+ assertEquals(0, comparator.compare(min, min));
+ assertEquals(-1, comparator.compare(min, midLow));
+ assertEquals(-1, comparator.compare(min, midHigh));
+ assertEquals(-1, comparator.compare(min, max));
+
+ assertEquals(1, comparator.compare(midLow, min));
+ assertEquals(0, comparator.compare(midLow, midLow));
+ assertEquals(-1, comparator.compare(midLow, midHigh));
+ assertEquals(-1, comparator.compare(midLow, max));
+
+ assertEquals(1, comparator.compare(midHigh, min));
+ assertEquals(1, comparator.compare(midHigh, midLow));
+ assertEquals(0, comparator.compare(midHigh, midHigh));
+ assertEquals(-1, comparator.compare(midHigh, max));
+
+ assertEquals(1, comparator.compare(max, min));
+ assertEquals(1, comparator.compare(max, midLow));
+ assertEquals(1, comparator.compare(max, midHigh));
+ assertEquals(0, comparator.compare(max, max));
+ }
+
+ @Test
+ public void testComparatorRandom() {
+ final Comparator comparator = UUIDFactory.comparator();
+
+ for (int i = 0; i < 1000; i++) {
+ UUID one = UUID.randomUUID();
+ UUID two = UUID.randomUUID();
+
+ if (one.getMostSignificantBits() < 0 && two.getMostSignificantBits() >= 0
+ || one.getMostSignificantBits() >= 0 && two.getMostSignificantBits() < 0
+ || one.getLeastSignificantBits() < 0 && two.getLeastSignificantBits() >= 0
+ || one.getLeastSignificantBits() >= 0 && two.getLeastSignificantBits() < 0) {
+ // These will differ due to the differing signs
+ assertEquals(-one.compareTo(two), comparator.compare(one, two));
+ }
+ else {
+ assertEquals(one.compareTo(two), comparator.compare(one, two));
+ }
+ }
+ }
+
+ // Various testing
+
+ @Ignore("Development testing only")
@Test
public void testOracleSYS_GUID() {
+ // TODO: Consider including this as a "fromCompactString" or similar...
String str = "AEB87F28E222D08AE043803BD559D08A";
BigInteger bigInteger = new BigInteger(str, 16); // ALT: Create byte array of every 2 chars.
long msb = bigInteger.shiftRight(64).longValue();