Better method naming.

Code cleanup.
Doc cleanup.
This commit is contained in:
Harald Kuhr 2012-03-13 09:17:20 +01:00
parent 07a5c62a28
commit f7bc246bad
2 changed files with 83 additions and 84 deletions

View File

@ -38,21 +38,27 @@ import java.security.SecureRandom;
import java.util.*;
/**
* A factory for creating UUIDs not directly supported by {@link java.util.UUID}.
* A factory for creating {@code UUID}s not directly supported by {@link java.util.UUID java.util.UUID}.
* <p>
* This class can create
* version 1 time based, using either IEEE 802 (mac) address or random "node" value
* and version 5 SHA1 hash based UUIDs.
* This class can create version 1 time based {@code UUID}s, using either IEEE 802 (mac) address or random "node" value
* as well as version 5 SHA1 hash based {@code UUID}s.
* </p>
* <p>
* The node value for version 1 UUIDs will, by default, reflect the IEEE 802 (mac) address of one of
* The timestamp value for version 1 {@code UUID}s will use a high precision clock, when available to the Java VM.
* If the Java system clock does not offer the needed precision, the timestamps will fall back to 100-nanosecond
* increments, to avoid duplicates.
* </p>
* <p>
* <a name="mac-node"></a>
* The node value for version 1 {@code UUID}s 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}.
* </p>
* <p>
* The node value for the random "node" based version 1 UUIDs will be stable for the lifetime of the VM.
* <a name="random-node"></a>
* The node value for the random "node" based version 1 {@code UUID}s will be stable for the lifetime of the VM.
* </p>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
@ -67,12 +73,12 @@ 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: {@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. Not particularly useful.
* 128 bits set to zero. Not particularly useful, unless as a placeholder or template.
*
* @see <a href="http://tools.ietf.org/html/rfc4122#section-4.1.7">RFC 4122</a>
* @see <a href="http://tools.ietf.org/html/rfc4122#section-4.1.7">RFC 4122 4.1.7. Nil UUID</a>
*/
public static final UUID NIL = new UUID(0l, 0l);
@ -86,34 +92,7 @@ public final class UUIDFactory {
static final long SECURE_RANDOM_NODE = getSecureRandomNode();
private static long getSecureRandomNode() {
/*
Obtain a 47-bit cryptographic quality random
number and use it as the low 47 bits of the node ID, with the least
significant bit of the first octet of the node ID set to one. This
bit is the unicast/multicast bit, which will never be set in IEEE 802
addresses obtained from network cards. Hence, there can never be a
conflict between UUIDs generated by machines with and without network
cards. (Recall that the IEEE 802 spec talks about transmission
order)
*/
/*
In addition, items such as the computer's name and the name of the
operating system, while not strictly speaking random, will help
differentiate the results from those obtained by other systems.
The exact algorithm to generate a node ID using these data is system
specific, because both the data available and the functions to obtain
them are often very system specific. A generic approach, however, is
to accumulate as many sources as possible into a buffer, use a
message digest such as MD5 [4] or SHA-1 [8], take an arbitrary 6
bytes from the hash value, and set the multicast bit as described
above.
*/
// TODO: Verify that nextLong is still cryptographically strong after the bit masking
// TODO: Consider using the hashing approach above
// Creates a completely random "node" value, with the unicast bit set to 1, as outlined in RFC 4122.
return 1l << 40 | SECURE_RANDOM.nextLong() & 0xffffffffffffl;
}
@ -171,11 +150,20 @@ public final class UUIDFactory {
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)
// TODO: Read up on creating these UUIDs in RFC, mentions something about UUID for namespace as input..?
static UUID nameUUIDFromBytesSHA1(byte[] name) {
/**
* Creates a type 5 (name based) {@code UUID} based on the specified byte array.
* This method is effectively identical to {@link UUID#nameUUIDFromBytes}, except that this method
* uses a SHA1 hash instead of the MD5 hash used in the type 3 {@code UUID}s.
* RFC 4122 states that "SHA-1 is preferred" over MD5, without giving a reason for why.
*
* @param name a byte array to be used to construct a {@code UUID}
* @return a {@code UUID} generated from the specified array.
*
* @see <a href="http://tools.ietf.org/html/rfc4122#section-4.3">RFC 4122 4.3. Algorithm for Creating a Name-Based UUID</a>
* @see <a href="http://tools.ietf.org/html/rfc4122#appendix-A">RFC 4122 appendix A</a>
* @see UUID#nameUUIDFromBytes(byte[])
*/
public static UUID nameUUIDv5FromBytes(byte[] name) {
// Based on code from OpenJDK UUID#nameUUIDFromBytes + private byte[] constructor
MessageDigest md;
@ -207,26 +195,39 @@ public final class UUIDFactory {
return new UUID(msb, lsb);
}
// Creatse version 1 node based UUIDs as specified in rfc422
// See http://tools.ietf.org/html/rfc4122#appendix-B
// See http://en.wikipedia.org/wiki/MAC_address
// TODO: Naming (of the method)
static UUID timeNodeBasedV1() {
/**
* Creates a version 1 time (and node) based {@code UUID}.
* The node part is by default the IEE 802 (mac) address of one of the network cards of the current machine.
*
* @return a {@code UUID} based on the current time and the node address of this computer.
* @see <a href="http://tools.ietf.org/html/rfc4122#section-4.2">RFC 4122 4.2. Algorithms for Creating a Time-Based UUID</a>
* @see <a href="http://en.wikipedia.org/wiki/MAC_address">IEEE 802 (mac) address</a>
* @see <a href="#mac-node">Overriding the node address</a>
*
* @throws IllegalStateException if the IEEE 802 (mac) address of the computer (node) cannot be found.
*/
public static UUID timeNodeBasedUUID() {
if (MAC_ADDRESS_NODE == -1) {
// TODO: OR fall back to Random??
throw new IllegalStateException("Could not determine IEEE 802 (mac) address for node");
}
return new UUID(createTimeAndVersion(), createClockSeqAndNode(MAC_ADDRESS_NODE));
}
// Creates version 1 "node" based UUIDs, using 47 bit secure random number + lsb of first octet
// (unicast/multicast bit) set to 1 as described in rfc422: 4.5. Node IDs that Do Not Identify the Host
// 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() {
/**
* Creates a version 1 time based {@code UUID} with the node part replaced by a random based value.
* The node part is computed using a 47 bit secure random number + lsb of first octet (unicast/multicast bit) set to 1.
* These {@code UUID}s can never clash with "real" node based version 1 {@code UUID}s due to the difference in
* the unicast/multicast bit, however, no uniqueness between multiple machines/vms/nodes can be guaranteed.
*
* @return a {@code UUID} based on the current time and a random generated "node" value.
* @see <a href="http://tools.ietf.org/html/rfc4122#section-4.5">RFC 4122 4.5. Node IDs that Do Not Identify the Host</a>
* @see <a href="http://tools.ietf.org/html/rfc4122#appendix-A">RFC 4122 Appendix A</a>
* @see <a href="#random-node">Lifetime of random node value</a>
*
* @throws IllegalStateException if the IEEE 802 (mac) address of the computer (node) cannot be found.
*/
public static UUID timeRandomBasedUUID() {
return new UUID(createTimeAndVersion(), createClockSeqAndNode(SECURE_RANDOM_NODE));
}
@ -261,7 +262,7 @@ public final class UUIDFactory {
* @return a comparator that compares UUIDs as 128 bit unsigned entities.
*
* @see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7025832">java.lang.UUID compareTo() does not do an unsigned compare</a>
* @see
* @see <a href="http://tools.ietf.org/html/rfc4122#appendix-A">RFC 4122 Appendix A</a>
*/
public static Comparator<UUID> comparator() {
return COMPARATOR;
@ -299,14 +300,13 @@ public final class UUIDFactory {
return 0;
}
}
/**
* A high-resolution timer for use in creating version 1 UUIDs.
* A high-resolution timer for use in creating version 1 {@code UUID}s.
*/
static final class Clock {
// Java: 0:00, Jan. 1st, 1970 vs UUID: 0:00, Oct 15th, 1582
private static final long JAVA_EPOCH_OFFSET = 122192928000000000L;
private static final long JAVA_EPOCH_OFFSET_HUNDRED_NANOS = 122192928000000000L;
private static int clockSeq = SECURE_RANDOM.nextInt();
@ -324,7 +324,7 @@ public final class UUIDFactory {
long millis = System.currentTimeMillis();
long nanos = System.nanoTime();
initialTime = JAVA_EPOCH_OFFSET + millis * 10000 + (nanos / 100) % 10000;
initialTime = JAVA_EPOCH_OFFSET_HUNDRED_NANOS + millis * 10000 + (nanos / 100) % 10000;
initialNanos = nanos;
}

View File

@ -51,7 +51,7 @@ public class UUIDFactoryTest {
@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"))));
assertFalse(a.equals(UUIDFactory.nameUUIDv5FromBytes(EXAMPLE_COM_UUID.getBytes("UTF-8"))));
}
@Test
@ -64,27 +64,27 @@ public class UUIDFactoryTest {
@Test
public void testVersion5NameBasedSHA1VariantVersion() throws UnsupportedEncodingException {
UUID a = UUIDFactory.nameUUIDFromBytesSHA1(EXAMPLE_COM_UUID.getBytes("UTF-8"));
UUID a = UUIDFactory.nameUUIDv5FromBytes(EXAMPLE_COM_UUID.getBytes("UTF-8"));
assertEquals(2, a.variant());
assertEquals(5, a.version());
}
@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"));
UUID a = UUIDFactory.nameUUIDv5FromBytes(EXAMPLE_COM_UUID.getBytes("UTF-8"));
UUID b = UUIDFactory.nameUUIDv5FromBytes(EXAMPLE_COM_UUID.getBytes("UTF-8"));
assertEquals(a, b);
}
@Test
public void testVersion5NameBasedSHA1NotEqualMD5() throws UnsupportedEncodingException {
UUID a = UUIDFactory.nameUUIDFromBytesSHA1(EXAMPLE_COM_UUID.getBytes("UTF-8"));
UUID a = UUIDFactory.nameUUIDv5FromBytes(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"));
UUID a = UUIDFactory.nameUUIDv5FromBytes(EXAMPLE_COM_UUID.getBytes("UTF-8"));
assertEquals(a, UUID.fromString(a.toString()));
}
@ -92,37 +92,36 @@ public class UUIDFactoryTest {
@Test
public void testVersion1NodeBasedVariantVersion() {
UUID uuid = UUIDFactory.timeNodeBasedV1();
UUID uuid = UUIDFactory.timeNodeBasedUUID();
assertEquals(2, uuid.variant());
assertEquals(1, uuid.version());
}
@Test
public void testVersion1NodeBasedMacAddress() {
UUID uuid = UUIDFactory.timeNodeBasedV1();
UUID uuid = UUIDFactory.timeNodeBasedUUID();
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?
}
@Test
public void testVersion1NodeBasedFromStringRep() {
UUID uuid = UUIDFactory.timeNodeBasedV1();
UUID uuid = UUIDFactory.timeNodeBasedUUID();
assertEquals(uuid, UUID.fromString(uuid.toString()));
}
@Test
public void testVersion1NodeBasedClockSeq() {
UUID uuid = UUIDFactory.timeNodeBasedV1();
UUID uuid = UUIDFactory.timeNodeBasedUUID();
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);
}
@Test
public void testVersion1NodeBasedTimestamp() {
UUID uuid = UUIDFactory.timeNodeBasedV1();
UUID uuid = UUIDFactory.timeNodeBasedUUID();
// Test time fields (within reasonable limits +/- 100 ms or so?)
assertEquals(UUIDFactory.Clock.currentTimeHundredNanos(), uuid.timestamp(), 1e6);
}
@ -131,7 +130,7 @@ public class UUIDFactoryTest {
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();
UUID uuid = UUIDFactory.timeNodeBasedUUID();
assertEquals(0, (uuid.node() >> 40) & 1);
}
}
@ -139,15 +138,15 @@ public class UUIDFactoryTest {
@Test
public void testVersion1NodeBasedUnique() {
for (int i = 0; i < 100; i++) {
UUID a = UUIDFactory.timeNodeBasedV1();
UUID b = UUIDFactory.timeNodeBasedV1();
UUID a = UUIDFactory.timeNodeBasedUUID();
UUID b = UUIDFactory.timeNodeBasedUUID();
assertFalse(a.equals(b));
}
}
@Test
public void testVersion1SecureRandomVariantVersion() {
UUID uuid = UUIDFactory.timeRandomBasedV1();
UUID uuid = UUIDFactory.timeRandomBasedUUID();
assertEquals(2, uuid.variant());
assertEquals(1, uuid.version());
@ -155,25 +154,25 @@ public class UUIDFactoryTest {
@Test
public void testVersion1SecureRandomNode() {
UUID uuid = UUIDFactory.timeRandomBasedV1();
UUID uuid = UUIDFactory.timeRandomBasedUUID();
assertEquals(UUIDFactory.SECURE_RANDOM_NODE, uuid.node());
}
@Test
public void testVersion1SecureRandomFromStringRep() {
UUID uuid = UUIDFactory.timeRandomBasedV1();
UUID uuid = UUIDFactory.timeRandomBasedUUID();
assertEquals(uuid, UUID.fromString(uuid.toString()));
}
@Test
public void testVersion1SecureRandomClockSeq() {
UUID uuid = UUIDFactory.timeRandomBasedV1();
UUID uuid = UUIDFactory.timeRandomBasedUUID();
assertEquals(UUIDFactory.Clock.getClockSequence(), uuid.clockSequence());
}
@Test
public void testVersion1SecureRandomTimestamp() {
UUID uuid = UUIDFactory.timeRandomBasedV1();
UUID uuid = UUIDFactory.timeRandomBasedUUID();
// Test time fields (within reasonable limits +/- 100 ms or so?)
assertEquals(UUIDFactory.Clock.currentTimeHundredNanos(), uuid.timestamp(), 1e6);
@ -183,7 +182,7 @@ public class UUIDFactoryTest {
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();
UUID uuid = UUIDFactory.timeRandomBasedUUID();
assertEquals(1, (uuid.node() >> 40) & 1);
}
}
@ -191,8 +190,8 @@ public class UUIDFactoryTest {
@Test
public void testVersion1SecureRandomUnique() {
for (int i = 0; i < 100; i++) {
UUID a = UUIDFactory.timeRandomBasedV1();
UUID b = UUIDFactory.timeRandomBasedV1();
UUID a = UUIDFactory.timeRandomBasedUUID();
UUID b = UUIDFactory.timeRandomBasedUUID();
assertFalse(a.equals(b));
}
}
@ -217,7 +216,7 @@ public class UUIDFactoryTest {
service.shutdown();
assertTrue("Execution timed out", service.awaitTermination(10, TimeUnit.SECONDS));
Arrays.sort(times);
Arrays.sort(times); // This is what really takes time...
for (int i = 0, timesLength = times.length; i < timesLength; i++) {
if (i == 0) {