1 package org
.argeo
.util
;
3 import static java
.lang
.System
.Logger
.Level
.DEBUG
;
5 import java
.lang
.System
.Logger
;
6 import java
.net
.InetAddress
;
7 import java
.net
.NetworkInterface
;
8 import java
.net
.SocketException
;
9 import java
.net
.UnknownHostException
;
10 import java
.nio
.charset
.StandardCharsets
;
11 import java
.security
.MessageDigest
;
12 import java
.security
.NoSuchAlgorithmException
;
13 import java
.security
.SecureRandom
;
14 import java
.time
.Duration
;
15 import java
.time
.LocalDateTime
;
16 import java
.time
.ZoneOffset
;
17 import java
.util
.BitSet
;
18 import java
.util
.Objects
;
19 import java
.util
.Random
;
20 import java
.util
.UUID
;
21 import java
.util
.concurrent
.atomic
.AtomicInteger
;
24 * Static utilities to simplify and extend usage of {@link UUID}. Only the RFC
25 * 4122 variant (also known as Leach–Salz variant) is supported.
27 * @see https://datatracker.ietf.org/doc/html/rfc4122
29 public class UuidUtils
{
30 /** Nil UUID (00000000-0000-0000-0000-000000000000). */
31 public final static UUID NIL_UUID
= UUID
.fromString("00000000-0000-0000-0000-000000000000");
33 /** Start of the Gregorian time, used by time-based UUID. */
34 public final static LocalDateTime GREGORIAN_START
= LocalDateTime
.of(1582, 10, 15, 0, 0, 0);
37 * Standard DNS namespace ID for type 3 or 5 UUID (as defined in Appendix C of
40 public final static UUID NS_DNS
= UUID
.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8");
42 * Standard URL namespace ID for type 3 or 5 UUID (as defined in Appendix C of
45 public final static UUID NS_URL
= UUID
.fromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8");
47 * Standard OID namespace ID (typically an LDAP type) for type 3 or 5 UUID (as
48 * defined in Appendix C of RFC4122).
50 public final static UUID NS_OID
= UUID
.fromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8");
52 * Standard X500 namespace ID (typically an LDAP DN) for type 3 or 5 UUID (as
53 * defined in Appendix C of RFC4122).
55 public final static UUID NS_X500
= UUID
.fromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8");
58 * INTERNAL STATIC UTILITIES
60 private final static Logger logger
= System
.getLogger(UuidUtils
.class.getName());
62 private final static long MOST_SIG_VERSION1
= (1l << 12);
63 private final static long LEAST_SIG_RFC4122_VARIANT
= (1l << 63);
64 private final static int MAX_CLOCKSEQUENCE
= 16384;
66 private final static SecureRandom SECURE_RANDOM
;
67 private final static Random UNSECURE_RANDOM
;
68 private final static byte[] HARDWARE_ADDRESS
;
70 private final static AtomicInteger CLOCK_SEQUENCE
;
72 /** A start timestamp to which {@link System#nanoTime()}/100 can be added. */
73 private final static long START_TIMESTAMP
;
76 SECURE_RANDOM
= new SecureRandom();
77 UNSECURE_RANDOM
= new Random();
78 CLOCK_SEQUENCE
= new AtomicInteger(SECURE_RANDOM
.nextInt(MAX_CLOCKSEQUENCE
));
79 HARDWARE_ADDRESS
= getHardwareAddress();
81 long nowVm
= System
.nanoTime() / 100;
82 Duration duration
= Duration
.between(GREGORIAN_START
, LocalDateTime
.now(ZoneOffset
.UTC
));
83 START_TIMESTAMP
= (duration
.getSeconds() * 10000000 + duration
.getNano() / 100) - nowVm
;
87 * TIME-BASED (version 1)
90 private static byte[] getHardwareAddress() {
91 InetAddress localHost
;
93 localHost
= InetAddress
.getLocalHost();
95 NetworkInterface nic
= NetworkInterface
.getByInetAddress(localHost
);
96 return nic
.getHardwareAddress();
97 } catch (SocketException e
) {
100 } catch (UnknownHostException e
) {
106 private synchronized static long nextClockSequence() {
107 int i
= CLOCK_SEQUENCE
.incrementAndGet();
108 while (i
< 0 || i
>= MAX_CLOCKSEQUENCE
) {
109 CLOCK_SEQUENCE
.set(SECURE_RANDOM
.nextInt(MAX_CLOCKSEQUENCE
));
110 i
= CLOCK_SEQUENCE
.incrementAndGet();
115 public static UUID
timeUUIDwithRandomNode() {
116 long timestamp
= START_TIMESTAMP
+ System
.nanoTime() / 100;
117 return timeUUID(timestamp
, SECURE_RANDOM
);
120 public static UUID
timeUUIDwithUnsecureRandomNode() {
121 long timestamp
= START_TIMESTAMP
+ System
.nanoTime() / 100;
122 return timeUUID(timestamp
, UNSECURE_RANDOM
);
125 public static UUID
timeUUID(long timestamp
, Random random
) {
126 byte[] node
= new byte[6];
127 random
.nextBytes(node
);
128 node
[0] = (byte) (node
[0] | 1);
129 long clockSequence
= nextClockSequence();
130 return timeUUID(timestamp
, clockSequence
, node
);
133 public static UUID
timeUUID() {
134 long timestamp
= START_TIMESTAMP
+ System
.nanoTime() / 100;
135 return timeUUID(timestamp
);
138 public static UUID
timeUUID(long timestamp
) {
139 if (HARDWARE_ADDRESS
== null)
140 return timeUUID(timestamp
, SECURE_RANDOM
);
141 long clockSequence
= nextClockSequence();
142 return timeUUID(timestamp
, clockSequence
, HARDWARE_ADDRESS
);
145 public static UUID
timeUUID(long timestamp
, NetworkInterface nic
) {
148 node
= nic
.getHardwareAddress();
149 } catch (SocketException e
) {
150 throw new IllegalStateException("Cannot get hardware address", e
);
152 long clockSequence
= nextClockSequence();
153 return timeUUID(timestamp
, clockSequence
, node
);
156 public static UUID
timeUUID(LocalDateTime time
, long clockSequence
, byte[] node
) {
157 Duration duration
= Duration
.between(GREGORIAN_START
, time
);
158 // Number of 100 ns intervals in one second: 1000000000 / 100 = 10000000
159 long timestamp
= duration
.getSeconds() * 10000000 + duration
.getNano() / 100;
160 return timeUUID(timestamp
, clockSequence
, node
);
163 public static UUID
timeUUID(long timestamp
, long clockSequence
, byte[] node
) {
164 Objects
.requireNonNull(node
, "Node array cannot be null");
166 throw new IllegalArgumentException("Node array must be at least 6 bytes long");
168 long mostSig
= MOST_SIG_VERSION1
// base for version 1 UUID
169 | ((timestamp
& 0xFFFFFFFFL
) << 32) // time_low
170 | (((timestamp
>> 32) & 0xFFFFL
) << 16) // time_mid
171 | ((timestamp
>> 48) & 0x0FFFL
);// time_hi_and_version
173 long leastSig
= LEAST_SIG_RFC4122_VARIANT
// base for Leach–Salz UUID
174 | (((clockSequence
& 0x3F00) >> 8) << 56) // clk_seq_hi_res
175 | ((clockSequence
& 0xFF) << 48) // clk_seq_low
176 | (node
[0] & 0xFFL
) //
177 | ((node
[1] & 0xFFL
) << 8) //
178 | ((node
[2] & 0xFFL
) << 16) //
179 | ((node
[3] & 0xFFL
) << 24) //
180 | ((node
[4] & 0xFFL
) << 32) //
181 | ((node
[5] & 0xFFL
) << 40); //
182 UUID uuid
= new UUID(mostSig
, leastSig
);
185 assert uuid
.node() == BitSet
.valueOf(node
).toLongArray()[0];
186 assert uuid
.timestamp() == timestamp
;
187 assert uuid
.clockSequence() == clockSequence
188 : "uuid.clockSequence()=" + uuid
.clockSequence() + " clockSequence=" + clockSequence
;
189 assert uuid
.version() == 1;
190 assert uuid
.variant() == 2;
195 // public static UUID timeBasedUUID() {
196 // return timeBasedUUID(LocalDateTime.now(ZoneOffset.UTC));
200 // public static UUID timeBasedRandomUUID() {
201 // return timeBasedRandomUUID(LocalDateTime.now(ZoneOffset.UTC), RANDOM);
205 // public static UUID timeBasedUUID(LocalDateTime time) {
206 // if (HARDWARE_ADDRESS == null)
207 // return timeBasedRandomUUID(time, RANDOM);
208 // return timeBasedUUID(time, BitSet.valueOf(HARDWARE_ADDRESS));
212 // public static UUID timeBasedAddressUUID(LocalDateTime time, NetworkInterface nic) throws SocketException {
213 // byte[] nodeBytes = nic.getHardwareAddress();
214 // BitSet node = BitSet.valueOf(nodeBytes);
215 // return timeBasedUUID(time, node);
219 // public static UUID timeBasedRandomUUID(LocalDateTime time, Random random) {
220 // byte[] nodeBytes = new byte[6];
221 // random.nextBytes(nodeBytes);
222 // BitSet node = BitSet.valueOf(nodeBytes);
223 // // set random marker
224 // node.set(0, true);
225 // return timeBasedUUID(time, node);
229 // public static UUID timeBasedUUID(LocalDateTime time, BitSet node) {
230 // // most significant
231 // Duration duration = Duration.between(GREGORIAN_START, time);
233 // // Number of 100 ns intervals in one second: 1000000000 / 100 = 10000000
234 // long timeNanos = duration.getSeconds() * 10000000 + duration.getNano() / 100;
235 // BitSet timeBits = BitSet.valueOf(new long[] { timeNanos });
236 // assert timeBits.length() <= 60;
238 // int clockSequence;
239 // synchronized (CLOCK_SEQUENCE) {
240 // clockSequence = CLOCK_SEQUENCE.incrementAndGet();
241 // if (clockSequence > 16384)
242 // CLOCK_SEQUENCE.set(0);
244 // BitSet clockSequenceBits = BitSet.valueOf(new long[] { clockSequence });
246 // // Build the UUID, bit by bit
247 // // see https://tools.ietf.org/html/rfc4122#section-4.2.2
249 // BitSet time_low = new BitSet(32);
250 // BitSet time_mid = new BitSet(16);
251 // BitSet time_hi_and_version = new BitSet(16);
253 // for (int i = 0; i < 60; i++) {
255 // time_low.set(i, timeBits.get(i));
257 // time_mid.set(i - 32, timeBits.get(i));
259 // time_hi_and_version.set(i - 48, timeBits.get(i));
262 // time_hi_and_version.set(12, true);
263 // time_hi_and_version.set(13, false);
264 // time_hi_and_version.set(14, false);
265 // time_hi_and_version.set(15, false);
268 // BitSet clk_seq_hi_res = new BitSet(8);
269 // BitSet clk_seq_low = new BitSet(8);
270 // for (int i = 0; i < 8; i++) {
271 // clk_seq_low.set(i, clockSequenceBits.get(i));
273 // for (int i = 8; i < 14; i++) {
274 // clk_seq_hi_res.set(i - 8, clockSequenceBits.get(i));
277 // clk_seq_hi_res.set(6, false);
278 // clk_seq_hi_res.set(7, true);
280 //// String str = toHexString(time_low.toLongArray()[0]) + "-" + toHexString(time_mid.toLongArray()[0]) + "-"
281 //// + toHexString(time_hi_and_version.toLongArray()[0]) + "-"
282 //// + toHexString(clock_seq_hi_and_reserved.toLongArray()[0]) + toHexString(clock_seq_low.toLongArray()[0])
283 //// + "-" + toHexString(node.toLongArray()[0]);
284 //// UUID uuid = UUID.fromString(str);
286 // BitSet uuidBits = new BitSet(128);
287 // for (int i = 0; i < 128; i++) {
289 // uuidBits.set(i, node.get(i));
291 // uuidBits.set(i, clk_seq_low.get(i - 48));
293 // uuidBits.set(i, clk_seq_hi_res.get(i - 56));
295 // uuidBits.set(i, time_hi_and_version.get(i - 64));
297 // uuidBits.set(i, time_mid.get(i - 80));
299 // uuidBits.set(i, time_low.get(i - 96));
302 // long[] uuidLongs = uuidBits.toLongArray();
303 // assert uuidLongs.length == 2;
304 // UUID uuid = new UUID(uuidLongs[1], uuidLongs[0]);
307 // assert uuid.node() == node.toLongArray()[0];
308 // assert uuid.timestamp() == timeNanos;
309 // assert uuid.clockSequence() == clockSequence;
310 // assert uuid.version() == 1;
311 // assert uuid.variant() == 2;
316 * NAME BASED (version 3 and 5)
319 public final static String MD5
= "MD5";
320 public final static String SHA1
= "SHA1";
322 public static UUID
nameUUIDv5(UUID namespace
, String name
) {
323 Objects
.requireNonNull(namespace
, "Namespace cannot be null");
324 Objects
.requireNonNull(name
, "Name cannot be null");
325 return nameUUIDv5(namespace
, name
.getBytes(StandardCharsets
.UTF_8
));
328 public static UUID
nameUUIDv5(UUID namespace
, byte[] name
) {
329 byte[] bytes
= DigestUtils
.sha1(toBytes(namespace
), name
);
331 bytes
[6] |= 0x50;// v5
333 bytes
[8] |= 0x80;// variant 1
334 UUID result
= fromBytes(bytes
, 0);
338 public static UUID
nameUUIDv3(UUID namespace
, String name
) {
339 Objects
.requireNonNull(namespace
, "Namespace cannot be null");
340 Objects
.requireNonNull(name
, "Name cannot be null");
341 return nameUUIDv3(namespace
, name
.getBytes(StandardCharsets
.UTF_8
));
344 public static UUID
nameUUIDv3(UUID namespace
, byte[] name
) {
345 byte[] arr
= new byte[name
.length
+ 16];
346 copyBytes(namespace
, arr
, 0);
347 System
.arraycopy(name
, 0, arr
, 16, name
.length
);
348 return UUID
.nameUUIDFromBytes(arr
);
351 static byte[] sha1(byte[]... bytes
) {
353 MessageDigest digest
= MessageDigest
.getInstance(SHA1
);
354 for (byte[] arr
: bytes
)
356 byte[] checksum
= digest
.digest();
358 } catch (NoSuchAlgorithmException e
) {
359 throw new UnsupportedOperationException("SHA1 is not avalaible", e
);
366 public static UUID
unsecureRandomUUID() {
367 byte[] arr
= new byte[16];
368 UNSECURE_RANDOM
.nextBytes(arr
);
372 arr
[8] |= 0x80;// variant 1
373 return fromBytes(arr
);
380 * Convert bytes to an UUID. Byte array must not be null and be exactly of
383 public static UUID
fromBytes(byte[] data
) {
384 Objects
.requireNonNull(data
, "Byte array must not be null");
385 if (data
.length
!= 16)
386 throw new IllegalArgumentException("Byte array as length " + data
.length
);
387 return fromBytes(data
, 0);
391 * Convert bytes to an UUID, starting to read the array at this offset.
393 public static UUID
fromBytes(byte[] data
, int offset
) {
394 Objects
.requireNonNull(data
, "Byte array cannot be null");
397 for (int i
= offset
; i
< 8 + offset
; i
++)
398 msb
= (msb
<< 8) | (data
[i
] & 0xff);
399 for (int i
= 8 + offset
; i
< 16 + offset
; i
++)
400 lsb
= (lsb
<< 8) | (data
[i
] & 0xff);
401 return new UUID(msb
, lsb
);
404 public static byte[] toBytes(UUID uuid
) {
405 Objects
.requireNonNull(uuid
, "UUID cannot be null");
406 long msb
= uuid
.getMostSignificantBits();
407 long lsb
= uuid
.getLeastSignificantBits();
408 return toBytes(msb
, lsb
);
411 public static void copyBytes(UUID uuid
, byte[] arr
, int offset
) {
412 Objects
.requireNonNull(uuid
, "UUID cannot be null");
413 long msb
= uuid
.getMostSignificantBits();
414 long lsb
= uuid
.getLeastSignificantBits();
415 copyBytes(msb
, lsb
, arr
, offset
);
419 * Converts an UUID to a binary string (list of 0 and 1), with a separator to
420 * make it more readable.
422 public static String
toBinaryString(UUID uuid
, int charsPerSegment
, char separator
) {
423 String binaryString
= toBinaryString(uuid
);
424 StringBuilder sb
= new StringBuilder(128 + (128 / charsPerSegment
));
425 for (int i
= 0; i
< binaryString
.length(); i
++) {
426 if (i
!= 0 && i
% charsPerSegment
== 0)
427 sb
.append(separator
);
428 sb
.append(binaryString
.charAt(i
));
430 return sb
.toString();
433 /** Converts an UUID to a binary string (list of 0 and 1). */
434 public static String
toBinaryString(UUID uuid
) {
435 String most
= zeroTo64Chars(Long
.toBinaryString(uuid
.getMostSignificantBits()));
436 String least
= zeroTo64Chars(Long
.toBinaryString(uuid
.getLeastSignificantBits()));
437 String binaryString
= most
+ least
;
438 assert binaryString
.length() == 128;
442 private static String
zeroTo64Chars(String str
) {
443 assert str
.length() <= 64;
444 if (str
.length() < 64) {
445 StringBuilder sb
= new StringBuilder(64);
446 for (int i
= 0; i
< 64 - str
.length(); i
++)
449 return sb
.toString();
455 * Converts an UUID hex representation without '-' to the standard form (with
458 public static String
compactToStd(String compact
) {
459 if (compact
.length() != 32)
460 throw new IllegalArgumentException(
461 "Compact UUID '" + compact
+ "' has length " + compact
.length() + " and not 32.");
462 StringBuilder sb
= new StringBuilder(36);
463 for (int i
= 0; i
< 32; i
++) {
464 if (i
== 8 || i
== 12 || i
== 16 || i
== 20)
466 sb
.append(compact
.charAt(i
));
468 String std
= sb
.toString();
469 assert std
.length() == 36;
470 assert UUID
.fromString(std
).toString().equals(std
);
475 * Converts an UUID hex representation without '-' to an {@link UUID}.
477 public static UUID
fromCompact(String compact
) {
478 return UUID
.fromString(compactToStd(compact
));
481 /** To a 32 characters hex string without '-'. */
482 public static String
toCompact(UUID uuid
) {
483 return toHexString(toBytes(uuid
));
486 public static boolean isRandom(UUID uuid
) {
487 return uuid
.version() == 4;
490 public static boolean isTimeBased(UUID uuid
) {
491 return uuid
.version() == 1;
494 public static boolean isTimeBasedRandom(UUID uuid
) {
495 if (uuid
.version() == 1) {
496 BitSet node
= BitSet
.valueOf(new long[] { uuid
.node() });
502 public static boolean isNameBased(UUID uuid
) {
503 return uuid
.version() == 3 || uuid
.version() == 5;
506 final private static char[] hexArray
= "0123456789abcdef".toCharArray();
508 /** Convert two longs to a byte array with length 16. */
509 public static byte[] toBytes(long long1
, long long2
) {
510 byte[] result
= new byte[16];
511 for (int i
= 0; i
< 8; i
++)
512 result
[i
] = (byte) ((long1
>> ((7 - i
) * 8)) & 0xff);
513 for (int i
= 8; i
< 16; i
++)
514 result
[i
] = (byte) ((long2
>> ((15 - i
) * 8)) & 0xff);
518 public static void copyBytes(long long1
, long long2
, byte[] arr
, int offset
) {
519 assert arr
.length
>= 16 + offset
;
520 for (int i
= offset
; i
< 8 + offset
; i
++)
521 arr
[i
] = (byte) ((long1
>> ((7 - i
) * 8)) & 0xff);
522 for (int i
= 8 + offset
; i
< 16 + offset
; i
++)
523 arr
[i
] = (byte) ((long2
>> ((15 - i
) * 8)) & 0xff);
526 /** Converts a byte array to an hex String. */
527 public static String
toHexString(byte[] bytes
) {
528 char[] hexChars
= new char[bytes
.length
* 2];
529 for (int j
= 0; j
< bytes
.length
; j
++) {
530 int v
= bytes
[j
] & 0xFF;
531 hexChars
[j
* 2] = hexArray
[v
>>> 4];
532 hexChars
[j
* 2 + 1] = hexArray
[v
& 0x0F];
534 return new String(hexChars
);
538 private UuidUtils() {
545 static boolean smokeTests() throws AssertionError
{
547 // warm up a bit before measuring perf and logging it
548 int warmUpCycles
= 10;
549 // int warmUpCycles = 10000000;
550 if (logger
.isLoggable(DEBUG
))
551 for (int i
= 0; i
< warmUpCycles
; i
++) {
553 unsecureRandomUUID();
555 timeUUIDwithRandomNode();
556 nameUUIDv5(NS_DNS
, "example.org");
557 nameUUIDv3(NS_DNS
, "example.org");
563 begin
= System
.nanoTime();
564 UUID uuid
= UUID
.randomUUID();
565 long duration
= System
.nanoTime() - begin
;
566 assert isRandom(uuid
);
567 logger
.log(DEBUG
, () -> uuid
.toString() + " in " + duration
+ " ns, isRandom=" + isRandom(uuid
));
571 begin
= System
.nanoTime();
572 UUID uuid
= unsecureRandomUUID();
573 long duration
= System
.nanoTime() - begin
;
574 assert isRandom(uuid
);
575 logger
.log(DEBUG
, () -> uuid
.toString() + " in " + duration
+ " ns, isRandom=" + isRandom(uuid
));
579 begin
= System
.nanoTime();
580 UUID uuid
= timeUUID();
581 long duration
= System
.nanoTime() - begin
;
582 assert isTimeBased(uuid
);
584 () -> uuid
.toString() + " in " + duration
+ " ns, isTimeBasedRandom=" + isTimeBasedRandom(uuid
));
588 begin
= System
.nanoTime();
589 UUID uuid
= timeUUIDwithRandomNode();
590 long duration
= System
.nanoTime() - begin
;
591 assert isTimeBasedRandom(uuid
);
593 () -> uuid
.toString() + " in " + duration
+ " ns, isTimeBasedRandom=" + isTimeBasedRandom(uuid
));
597 begin
= System
.nanoTime();
598 UUID uuid
= nameUUIDv5(NS_DNS
, "example.org");
599 long duration
= System
.nanoTime() - begin
;
600 assert isNameBased(uuid
);
601 // uuidgen --sha1 --namespace @dns --name example.org
602 assert "aad03681-8b63-5304-89e0-8ca8f49461b5".equals(uuid
.toString());
603 logger
.log(DEBUG
, () -> uuid
.toString() + " in " + duration
+ " ns, isNameBased=" + isNameBased(uuid
));
607 begin
= System
.nanoTime();
608 UUID uuid
= nameUUIDv3(NS_DNS
, "example.org");
609 long duration
= System
.nanoTime() - begin
;
610 assert isNameBased(uuid
);
611 // uuidgen --md5 --namespace @dns --name example.org
612 assert "04738bdf-b25a-3829-a801-b21a1d25095b".equals(uuid
.toString());
613 logger
.log(DEBUG
, () -> uuid
.toString() + " in " + duration
+ " ns, isNameBased=" + isNameBased(uuid
));
615 return UuidUtils
.class.desiredAssertionStatus();
618 public static void main(String
[] args
) {