1 package org
.argeo
.util
;
3 import java
.net
.InetAddress
;
4 import java
.net
.NetworkInterface
;
5 import java
.net
.SocketException
;
6 import java
.net
.UnknownHostException
;
7 import java
.security
.SecureRandom
;
8 import java
.time
.Duration
;
9 import java
.time
.LocalDateTime
;
10 import java
.time
.ZoneOffset
;
11 import java
.util
.BitSet
;
12 import java
.util
.Random
;
13 import java
.util
.UUID
;
14 import java
.util
.concurrent
.atomic
.AtomicInteger
;
17 * Utilities to simplify and extends usage of {@link UUID}. Only the RFC 4122
18 * variant (also known as Leach–Salz variant) is supported.
20 public class UuidUtils
{
21 /** Nil UUID (00000000-0000-0000-0000-000000000000). */
22 public final static UUID NIL_UUID
= UUID
.fromString("00000000-0000-0000-0000-000000000000");
23 public final static LocalDateTime GREGORIAN_START
= LocalDateTime
.of(1582, 10, 15, 0, 0, 0);
25 private final static long MOST_SIG_VERSION1
= (1l << 12);
26 private final static long LEAST_SIG_RFC4122_VARIANT
= (1l << 63);
28 private final static SecureRandom RANDOM
;
29 private final static AtomicInteger CLOCK_SEQUENCE
;
30 private final static byte[] HARDWARE_ADDRESS
;
31 /** A start timestamp to which {@link System#nanoTime()}/100 can be added. */
32 private final static long START_TIMESTAMP
;
34 RANDOM
= new SecureRandom();
35 CLOCK_SEQUENCE
= new AtomicInteger(RANDOM
.nextInt(16384));
36 HARDWARE_ADDRESS
= getHardwareAddress();
38 long nowVm
= System
.nanoTime() / 100;
39 Duration duration
= Duration
.between(GREGORIAN_START
, LocalDateTime
.now(ZoneOffset
.UTC
));
40 START_TIMESTAMP
= (duration
.getSeconds() * 10000000 + duration
.getNano() / 100) - nowVm
;
43 private static byte[] getHardwareAddress() {
44 InetAddress localHost
;
46 localHost
= InetAddress
.getLocalHost();
48 NetworkInterface nic
= NetworkInterface
.getByInetAddress(localHost
);
49 return nic
.getHardwareAddress();
50 } catch (SocketException e
) {
53 } catch (UnknownHostException e
) {
59 public static UUID
timeUUIDwithRandomNode() {
60 long timestamp
= START_TIMESTAMP
+ System
.nanoTime() / 100;
61 return timeUUID(timestamp
, RANDOM
);
64 public static UUID
timeUUID(long timestamp
, Random random
) {
65 byte[] node
= new byte[6];
66 random
.nextBytes(node
);
67 node
[0] = (byte) (node
[0] | 1);
68 long clockSequence
= CLOCK_SEQUENCE
.incrementAndGet();
69 return timeUUID(timestamp
, clockSequence
, node
);
72 public static UUID
timeUUID() {
73 long timestamp
= START_TIMESTAMP
+ System
.nanoTime() / 100;
74 return timeUUID(timestamp
);
77 public static UUID
timeUUID(long timestamp
) {
78 if (HARDWARE_ADDRESS
== null)
79 return timeUUID(timestamp
, RANDOM
);
80 long clockSequence
= CLOCK_SEQUENCE
.incrementAndGet();
81 return timeUUID(timestamp
, clockSequence
, HARDWARE_ADDRESS
);
84 public static UUID
timeUUID(long timestamp
, NetworkInterface nic
) {
87 node
= nic
.getHardwareAddress();
88 } catch (SocketException e
) {
89 throw new IllegalStateException("Cannot get hardware address", e
);
91 long clockSequence
= CLOCK_SEQUENCE
.incrementAndGet();
92 return timeUUID(timestamp
, clockSequence
, node
);
95 public static UUID
timeUUID(LocalDateTime time
, long clockSequence
, byte[] node
) {
96 Duration duration
= Duration
.between(GREGORIAN_START
, time
);
97 // Number of 100 ns intervals in one second: 1000000000 / 100 = 10000000
98 long timestamp
= duration
.getSeconds() * 10000000 + duration
.getNano() / 100;
99 return timeUUID(timestamp
, clockSequence
, node
);
102 public static UUID
timeUUID(long timestamp
, long clockSequence
, byte[] node
) {
103 assert node
.length
>= 6;
105 long mostSig
= MOST_SIG_VERSION1
// base for version 1 UUID
106 | ((timestamp
& 0xFFFFFFFFL
) << 32) // time_low
107 | (((timestamp
>> 32) & 0xFFFFL
) << 16) // time_mid
108 | ((timestamp
>> 48) & 0x0FFFL
);// time_hi_and_version
110 long leastSig
= LEAST_SIG_RFC4122_VARIANT
// base for Leach–Salz UUID
111 | (((clockSequence
& 0x3F00) >> 8) << 56) // clk_seq_hi_res
112 | ((clockSequence
& 0xFF) << 48) // clk_seq_low
113 | (node
[0] & 0xFFL
) //
114 | ((node
[1] & 0xFFL
) << 8) //
115 | ((node
[2] & 0xFFL
) << 16) //
116 | ((node
[3] & 0xFFL
) << 24) //
117 | ((node
[4] & 0xFFL
) << 32) //
118 | ((node
[5] & 0xFFL
) << 40); //
119 // for (int i = 0; i < 6; i++) {
120 // leastSig = leastSig | ((node[i] & 0xFFL) << (8 * i));
122 UUID uuid
= new UUID(mostSig
, leastSig
);
125 assert uuid
.node() == BitSet
.valueOf(node
).toLongArray()[0];
126 assert uuid
.timestamp() == timestamp
;
127 assert uuid
.clockSequence() == clockSequence
;
128 assert uuid
.version() == 1;
129 assert uuid
.variant() == 2;
134 public static UUID
timeBasedUUID() {
135 return timeBasedUUID(LocalDateTime
.now(ZoneOffset
.UTC
));
139 public static UUID
timeBasedRandomUUID() {
140 return timeBasedRandomUUID(LocalDateTime
.now(ZoneOffset
.UTC
), RANDOM
);
144 public static UUID
timeBasedUUID(LocalDateTime time
) {
145 if (HARDWARE_ADDRESS
== null)
146 return timeBasedRandomUUID(time
, RANDOM
);
147 return timeBasedUUID(time
, BitSet
.valueOf(HARDWARE_ADDRESS
));
151 public static UUID
timeBasedAddressUUID(LocalDateTime time
, NetworkInterface nic
) throws SocketException
{
152 byte[] nodeBytes
= nic
.getHardwareAddress();
153 BitSet node
= BitSet
.valueOf(nodeBytes
);
154 return timeBasedUUID(time
, node
);
158 public static UUID
timeBasedRandomUUID(LocalDateTime time
, Random random
) {
159 byte[] nodeBytes
= new byte[6];
160 random
.nextBytes(nodeBytes
);
161 BitSet node
= BitSet
.valueOf(nodeBytes
);
164 return timeBasedUUID(time
, node
);
168 public static UUID
timeBasedUUID(LocalDateTime time
, BitSet node
) {
170 Duration duration
= Duration
.between(GREGORIAN_START
, time
);
172 // Number of 100 ns intervals in one second: 1000000000 / 100 = 10000000
173 long timeNanos
= duration
.getSeconds() * 10000000 + duration
.getNano() / 100;
174 BitSet timeBits
= BitSet
.valueOf(new long[] { timeNanos
});
175 assert timeBits
.length() <= 60;
178 synchronized (CLOCK_SEQUENCE
) {
179 clockSequence
= CLOCK_SEQUENCE
.incrementAndGet();
180 if (clockSequence
> 16384)
181 CLOCK_SEQUENCE
.set(0);
183 BitSet clockSequenceBits
= BitSet
.valueOf(new long[] { clockSequence
});
185 // Build the UUID, bit by bit
186 // see https://tools.ietf.org/html/rfc4122#section-4.2.2
188 BitSet time_low
= new BitSet(32);
189 BitSet time_mid
= new BitSet(16);
190 BitSet time_hi_and_version
= new BitSet(16);
192 for (int i
= 0; i
< 60; i
++) {
194 time_low
.set(i
, timeBits
.get(i
));
196 time_mid
.set(i
- 32, timeBits
.get(i
));
198 time_hi_and_version
.set(i
- 48, timeBits
.get(i
));
201 time_hi_and_version
.set(12, true);
202 time_hi_and_version
.set(13, false);
203 time_hi_and_version
.set(14, false);
204 time_hi_and_version
.set(15, false);
207 BitSet clk_seq_hi_res
= new BitSet(8);
208 BitSet clk_seq_low
= new BitSet(8);
209 for (int i
= 0; i
< 8; i
++) {
210 clk_seq_low
.set(i
, clockSequenceBits
.get(i
));
212 for (int i
= 8; i
< 14; i
++) {
213 clk_seq_hi_res
.set(i
- 8, clockSequenceBits
.get(i
));
216 clk_seq_hi_res
.set(6, false);
217 clk_seq_hi_res
.set(7, true);
219 // String str = toHexString(time_low.toLongArray()[0]) + "-" + toHexString(time_mid.toLongArray()[0]) + "-"
220 // + toHexString(time_hi_and_version.toLongArray()[0]) + "-"
221 // + toHexString(clock_seq_hi_and_reserved.toLongArray()[0]) + toHexString(clock_seq_low.toLongArray()[0])
222 // + "-" + toHexString(node.toLongArray()[0]);
223 // UUID uuid = UUID.fromString(str);
225 BitSet uuidBits
= new BitSet(128);
226 for (int i
= 0; i
< 128; i
++) {
228 uuidBits
.set(i
, node
.get(i
));
230 uuidBits
.set(i
, clk_seq_low
.get(i
- 48));
232 uuidBits
.set(i
, clk_seq_hi_res
.get(i
- 56));
234 uuidBits
.set(i
, time_hi_and_version
.get(i
- 64));
236 uuidBits
.set(i
, time_mid
.get(i
- 80));
238 uuidBits
.set(i
, time_low
.get(i
- 96));
241 long[] uuidLongs
= uuidBits
.toLongArray();
242 assert uuidLongs
.length
== 2;
243 UUID uuid
= new UUID(uuidLongs
[1], uuidLongs
[0]);
246 assert uuid
.node() == node
.toLongArray()[0];
247 assert uuid
.timestamp() == timeNanos
;
248 assert uuid
.clockSequence() == clockSequence
;
249 assert uuid
.version() == 1;
250 assert uuid
.variant() == 2;
254 public static String
toBinaryString(UUID uuid
, int charsPerSegment
, char separator
) {
255 String binaryString
= toBinaryString(uuid
);
256 StringBuilder sb
= new StringBuilder(128 + (128 / charsPerSegment
));
257 for (int i
= 0; i
< binaryString
.length(); i
++) {
258 if (i
!= 0 && i
% charsPerSegment
== 0)
259 sb
.append(separator
);
260 sb
.append(binaryString
.charAt(i
));
262 return sb
.toString();
265 public static String
toBinaryString(UUID uuid
) {
266 String most
= zeroTo64Chars(Long
.toBinaryString(uuid
.getMostSignificantBits()));
267 String least
= zeroTo64Chars(Long
.toBinaryString(uuid
.getLeastSignificantBits()));
268 String binaryString
= most
+ least
;
269 assert binaryString
.length() == 128;
273 private static String
zeroTo64Chars(String str
) {
274 assert str
.length() <= 64;
275 if (str
.length() < 64) {
276 StringBuilder sb
= new StringBuilder(64);
277 for (int i
= 0; i
< 64 - str
.length(); i
++)
280 return sb
.toString();
285 public static String
compactToStd(String compact
) {
286 if (compact
.length() != 32)
287 throw new IllegalArgumentException(
288 "Compact UUID '" + compact
+ "' has length " + compact
.length() + " and not 32.");
289 StringBuilder sb
= new StringBuilder(36);
290 for (int i
= 0; i
< 32; i
++) {
291 if (i
== 8 || i
== 12 || i
== 16 || i
== 20)
293 sb
.append(compact
.charAt(i
));
295 String std
= sb
.toString();
296 assert std
.length() == 36;
297 assert UUID
.fromString(std
).toString().equals(std
);
301 public static UUID
compactToUuid(String compact
) {
302 return UUID
.fromString(compactToStd(compact
));
305 public static boolean isRandom(UUID uuid
) {
306 return uuid
.version() == 4;
309 public static boolean isTimeBased(UUID uuid
) {
310 return uuid
.version() == 1;
313 public static boolean isTimeBasedRandom(UUID uuid
) {
314 if (uuid
.version() == 1) {
315 BitSet node
= BitSet
.valueOf(new long[] { uuid
.node() });
321 public static boolean isNameBased(UUID uuid
) {
322 return uuid
.version() == 3 || uuid
.version() == 5;
326 private UuidUtils() {
329 public final static void main(String
[] args
) throws Exception
{
332 // uuid = compactToUuid("996b1f5122de4b2f94e49168d32f22d1");
333 // System.out.println(uuid.toString() + ", isRandom=" + isRandom(uuid));
335 // warm up before measuring perf
336 for (int i
= 0; i
< 10; i
++) {
339 timeUUIDwithRandomNode();
340 timeBasedRandomUUID();
347 begin
= System
.nanoTime();
348 uuid
= UUID
.randomUUID();
349 duration
= System
.nanoTime() - begin
;
350 System
.out
.println(uuid
.toString() + " in " + duration
+ " ns, isRandom=" + isRandom(uuid
));
352 begin
= System
.nanoTime();
354 duration
= System
.nanoTime() - begin
;
355 System
.out
.println(uuid
.toString() + " in " + duration
+ " ns, isTimeBasedRandom=" + isTimeBasedRandom(uuid
));
357 begin
= System
.nanoTime();
358 uuid
= timeUUIDwithRandomNode();
359 duration
= System
.nanoTime() - begin
;
360 System
.out
.println(uuid
.toString() + " in " + duration
+ " ns, isTimeBasedRandom=" + isTimeBasedRandom(uuid
));
362 begin
= System
.nanoTime();
363 uuid
= timeBasedUUID();
364 duration
= System
.nanoTime() - begin
;
365 System
.out
.println(uuid
.toString() + " in " + duration
+ " ns, isTimeBasedRandom=" + isTimeBasedRandom(uuid
));
367 begin
= System
.nanoTime();
368 uuid
= timeBasedRandomUUID();
369 duration
= System
.nanoTime() - begin
;
370 System
.out
.println(uuid
.toString() + " in " + duration
+ " ns, isTimeBasedRandom=" + isTimeBasedRandom(uuid
));
371 // System.out.println(toBinaryString(uuid, 8, ' '));
372 // System.out.println(toBinaryString(uuid, 16, '\n'));