]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.util/src/org/argeo/util/UuidUtils.java
Improve minimal web app.
[lgpl/argeo-commons.git] / org.argeo.util / src / org / argeo / util / UuidUtils.java
1 package org.argeo.util;
2
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;
15
16 /**
17 * Utilities to simplify and extends usage of {@link UUID}. Only the RFC 4122
18 * variant (also known as Leach–Salz variant) is supported.
19 */
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);
24
25 private final static long MOST_SIG_VERSION1 = (1l << 12);
26 private final static long LEAST_SIG_RFC4122_VARIANT = (1l << 63);
27
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;
33 static {
34 RANDOM = new SecureRandom();
35 CLOCK_SEQUENCE = new AtomicInteger(RANDOM.nextInt(16384));
36 HARDWARE_ADDRESS = getHardwareAddress();
37
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;
41 }
42
43 private static byte[] getHardwareAddress() {
44 InetAddress localHost;
45 try {
46 localHost = InetAddress.getLocalHost();
47 try {
48 NetworkInterface nic = NetworkInterface.getByInetAddress(localHost);
49 return nic.getHardwareAddress();
50 } catch (SocketException e) {
51 return null;
52 }
53 } catch (UnknownHostException e) {
54 return null;
55 }
56
57 }
58
59 public static UUID timeUUIDwithRandomNode() {
60 long timestamp = START_TIMESTAMP + System.nanoTime() / 100;
61 return timeUUID(timestamp, RANDOM);
62 }
63
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);
70 }
71
72 public static UUID timeUUID() {
73 long timestamp = START_TIMESTAMP + System.nanoTime() / 100;
74 return timeUUID(timestamp);
75 }
76
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);
82 }
83
84 public static UUID timeUUID(long timestamp, NetworkInterface nic) {
85 byte[] node;
86 try {
87 node = nic.getHardwareAddress();
88 } catch (SocketException e) {
89 throw new IllegalStateException("Cannot get hardware address", e);
90 }
91 long clockSequence = CLOCK_SEQUENCE.incrementAndGet();
92 return timeUUID(timestamp, clockSequence, node);
93 }
94
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);
100 }
101
102 public static UUID timeUUID(long timestamp, long clockSequence, byte[] node) {
103 assert node.length >= 6;
104
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
109
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));
121 // }
122 UUID uuid = new UUID(mostSig, leastSig);
123
124 // tests
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;
130 return uuid;
131 }
132
133 @Deprecated
134 public static UUID timeBasedUUID() {
135 return timeBasedUUID(LocalDateTime.now(ZoneOffset.UTC));
136 }
137
138 @Deprecated
139 public static UUID timeBasedRandomUUID() {
140 return timeBasedRandomUUID(LocalDateTime.now(ZoneOffset.UTC), RANDOM);
141 }
142
143 @Deprecated
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));
148 }
149
150 @Deprecated
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);
155 }
156
157 @Deprecated
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);
162 // set random marker
163 node.set(0, true);
164 return timeBasedUUID(time, node);
165 }
166
167 @Deprecated
168 public static UUID timeBasedUUID(LocalDateTime time, BitSet node) {
169 // most significant
170 Duration duration = Duration.between(GREGORIAN_START, time);
171
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;
176
177 int clockSequence;
178 synchronized (CLOCK_SEQUENCE) {
179 clockSequence = CLOCK_SEQUENCE.incrementAndGet();
180 if (clockSequence > 16384)
181 CLOCK_SEQUENCE.set(0);
182 }
183 BitSet clockSequenceBits = BitSet.valueOf(new long[] { clockSequence });
184
185 // Build the UUID, bit by bit
186 // see https://tools.ietf.org/html/rfc4122#section-4.2.2
187 // time
188 BitSet time_low = new BitSet(32);
189 BitSet time_mid = new BitSet(16);
190 BitSet time_hi_and_version = new BitSet(16);
191
192 for (int i = 0; i < 60; i++) {
193 if (i < 32)
194 time_low.set(i, timeBits.get(i));
195 else if (i < 48)
196 time_mid.set(i - 32, timeBits.get(i));
197 else
198 time_hi_and_version.set(i - 48, timeBits.get(i));
199 }
200 // version
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);
205
206 // clock sequence
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));
211 }
212 for (int i = 8; i < 14; i++) {
213 clk_seq_hi_res.set(i - 8, clockSequenceBits.get(i));
214 }
215 // variant
216 clk_seq_hi_res.set(6, false);
217 clk_seq_hi_res.set(7, true);
218
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);
224
225 BitSet uuidBits = new BitSet(128);
226 for (int i = 0; i < 128; i++) {
227 if (i < 48)
228 uuidBits.set(i, node.get(i));
229 else if (i < 56)
230 uuidBits.set(i, clk_seq_low.get(i - 48));
231 else if (i < 64)
232 uuidBits.set(i, clk_seq_hi_res.get(i - 56));
233 else if (i < 80)
234 uuidBits.set(i, time_hi_and_version.get(i - 64));
235 else if (i < 96)
236 uuidBits.set(i, time_mid.get(i - 80));
237 else
238 uuidBits.set(i, time_low.get(i - 96));
239 }
240
241 long[] uuidLongs = uuidBits.toLongArray();
242 assert uuidLongs.length == 2;
243 UUID uuid = new UUID(uuidLongs[1], uuidLongs[0]);
244
245 // tests
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;
251 return uuid;
252 }
253
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));
261 }
262 return sb.toString();
263 }
264
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;
270 return binaryString;
271 }
272
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++)
278 sb.append('0');
279 sb.append(str);
280 return sb.toString();
281 } else
282 return str;
283 }
284
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)
292 sb.append('-');
293 sb.append(compact.charAt(i));
294 }
295 String std = sb.toString();
296 assert std.length() == 36;
297 assert UUID.fromString(std).toString().equals(std);
298 return std;
299 }
300
301 public static UUID compactToUuid(String compact) {
302 return UUID.fromString(compactToStd(compact));
303 }
304
305 public static boolean isRandom(UUID uuid) {
306 return uuid.version() == 4;
307 }
308
309 public static boolean isTimeBased(UUID uuid) {
310 return uuid.version() == 1;
311 }
312
313 public static boolean isTimeBasedRandom(UUID uuid) {
314 if (uuid.version() == 1) {
315 BitSet node = BitSet.valueOf(new long[] { uuid.node() });
316 return node.get(0);
317 } else
318 return false;
319 }
320
321 public static boolean isNameBased(UUID uuid) {
322 return uuid.version() == 3 || uuid.version() == 5;
323 }
324
325 /** Singleton. */
326 private UuidUtils() {
327 }
328
329 public final static void main(String[] args) throws Exception {
330 UUID uuid;
331
332 // uuid = compactToUuid("996b1f5122de4b2f94e49168d32f22d1");
333 // System.out.println(uuid.toString() + ", isRandom=" + isRandom(uuid));
334
335 // warm up before measuring perf
336 for (int i = 0; i < 10; i++) {
337 UUID.randomUUID();
338 timeUUID();
339 timeUUIDwithRandomNode();
340 timeBasedRandomUUID();
341 timeBasedUUID();
342 }
343
344 long begin;
345 long duration;
346
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));
351
352 begin = System.nanoTime();
353 uuid = timeUUID();
354 duration = System.nanoTime() - begin;
355 System.out.println(uuid.toString() + " in " + duration + " ns, isTimeBasedRandom=" + isTimeBasedRandom(uuid));
356
357 begin = System.nanoTime();
358 uuid = timeUUIDwithRandomNode();
359 duration = System.nanoTime() - begin;
360 System.out.println(uuid.toString() + " in " + duration + " ns, isTimeBasedRandom=" + isTimeBasedRandom(uuid));
361
362 begin = System.nanoTime();
363 uuid = timeBasedUUID();
364 duration = System.nanoTime() - begin;
365 System.out.println(uuid.toString() + " in " + duration + " ns, isTimeBasedRandom=" + isTimeBasedRandom(uuid));
366
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'));
373 }
374 }