1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.waarp.common.guid;
22
23 import com.fasterxml.jackson.annotation.JsonGetter;
24 import com.fasterxml.jackson.annotation.JsonIgnore;
25 import com.fasterxml.jackson.annotation.JsonSetter;
26 import com.fasterxml.jackson.annotation.JsonTypeInfo;
27 import org.waarp.common.exception.InvalidArgumentException;
28 import org.waarp.common.utility.BaseXx;
29 import org.waarp.common.utility.SingletonUtils;
30
31 import java.util.Arrays;
32 import java.util.concurrent.atomic.AtomicInteger;
33
34 @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
35 public class GUID implements Comparable<GUID> {
36
37
38
39 public static final String ARK = "ark:/";
40
41 private static final String ATTEMPTED_TO_PARSE_MALFORMED_ARK_GUID =
42 "Attempted to parse malformed ARK GUID: ";
43
44
45
46 static final int KEYSIZE = 21;
47 static final int KEYB64SIZE = 28;
48 static final int KEYB32SIZE = 34;
49 static final int KEYB16SIZE = KEYSIZE * 2;
50 static final int HEADER_POS = 0;
51 static final int HEADER_SIZE = 1;
52 static final int TENANT_POS = 1;
53 static final int TENANT_SIZE = 4;
54 static final int PLATFORM_POS = 5;
55 static final int PLATFORM_SIZE = 4;
56 static final int PID_POS = 9;
57 static final int PID_SIZE = 3;
58 static final int TIME_POS = 12;
59 static final int TIME_SIZE = 6;
60 static final int COUNTER_POS = 18;
61 static final int COUNTER_SIZE = 3;
62
63
64
65 private static final int SIZE_COUNTER = COUNTER_SIZE * 8;
66
67
68
69 private static final int MIN_COUNTER = 0;
70
71
72
73 private static final int MAX_COUNTER = (1 << SIZE_COUNTER) - 1;
74
75
76
77
78 static final int VERSION = 1 & 0xFF;
79
80 static final int BYTE_SIZE = 8;
81
82
83
84 private static final AtomicInteger COUNTER = new AtomicInteger(MIN_COUNTER);
85
86
87
88 @JsonIgnore
89 private final byte[] bguid;
90
91 static final synchronized int getNewCounter() {
92 if (COUNTER.compareAndSet(MAX_COUNTER, MIN_COUNTER)) {
93 return MAX_COUNTER;
94 } else {
95 return COUNTER.getAndIncrement();
96 }
97 }
98
99
100
101
102 public static int getKeySize() {
103 return KEYSIZE;
104 }
105
106
107
108
109
110
111
112 GUID(final int size) {
113 bguid = new byte[size];
114 }
115
116
117
118
119
120
121
122
123 public GUID(final byte[] bytes) throws InvalidArgumentException {
124 this(KEYSIZE);
125 setBytes(bytes, KEYSIZE);
126 if (getVersion() != VERSION) {
127 throw new InvalidArgumentException(
128 "Version is incorrect: " + getVersion());
129 }
130 }
131
132
133
134
135
136
137
138
139 public GUID(final String idsource) throws InvalidArgumentException {
140 this(KEYSIZE);
141 setString(idsource);
142 if (getVersion() != VERSION) {
143 throw new InvalidArgumentException(
144 "Version is incorrect: " + getVersion());
145 }
146 }
147
148
149
150
151
152
153
154
155
156
157
158 @JsonIgnore
159 GUID setBytes(final byte[] bytes, final int size)
160 throws InvalidArgumentException {
161 if (bytes == null) {
162 throw new InvalidArgumentException("Empty argument");
163 }
164 if (bytes.length != size) {
165 throw new InvalidArgumentException(
166 "Attempted to parse malformed GUID: (" + bytes.length + ") " +
167 Arrays.toString(bytes));
168 }
169 System.arraycopy(bytes, 0, bguid, 0, size);
170 return this;
171 }
172
173
174
175
176
177 public GUID() {
178 this(0, JvmProcessId.macAddressAsInt() & 0x7FFFFFFF);
179 }
180
181
182
183
184
185
186
187
188
189
190
191 public GUID(final int tenantId, final int platformId) {
192 this(KEYSIZE);
193 if (tenantId < 0 || tenantId > 0x3FFFFFFF) {
194 throw new IllegalArgumentException(
195 "DomainId must be between 0 and 2^30-1: " + tenantId);
196 }
197 if (platformId < 0 || platformId > 0x7FFFFFFF) {
198 throw new IllegalArgumentException(
199 "PlatformId must be between 0 and 2^31-1: " + platformId);
200 }
201
202
203 final long time = System.currentTimeMillis();
204 final int count = getNewCounter();
205
206 bguid[HEADER_POS] = (byte) VERSION;
207
208
209 int value = tenantId;
210 bguid[TENANT_POS + 3] = (byte) (value & 0xFF);
211 value >>>= BYTE_SIZE;
212 bguid[TENANT_POS + 2] = (byte) (value & 0xFF);
213 value >>>= BYTE_SIZE;
214 bguid[TENANT_POS + 1] = (byte) (value & 0xFF);
215 value >>>= BYTE_SIZE;
216 bguid[TENANT_POS] = (byte) (value & 0x3F);
217
218
219 value = platformId;
220 bguid[PLATFORM_POS + 3] = (byte) (value & 0xFF);
221 value >>>= BYTE_SIZE;
222 bguid[PLATFORM_POS + 2] = (byte) (value & 0xFF);
223 value >>>= BYTE_SIZE;
224 bguid[PLATFORM_POS + 1] = (byte) (value & 0xFF);
225 value >>>= BYTE_SIZE;
226 bguid[PLATFORM_POS] = (byte) (value & 0x7F);
227
228
229 value = JvmProcessId.JVMPID;
230 bguid[PID_POS + 2] = (byte) (value & 0xFF);
231 value >>>= BYTE_SIZE;
232 bguid[PID_POS + 1] = (byte) (value & 0xFF);
233 value >>>= BYTE_SIZE;
234 bguid[PID_POS] = (byte) (value & 0xFF);
235
236
237
238 long lvalue = time;
239 bguid[TIME_POS + 5] = (byte) (lvalue & 0xFF);
240 lvalue >>>= BYTE_SIZE;
241 bguid[TIME_POS + 4] = (byte) (lvalue & 0xFF);
242 lvalue >>>= BYTE_SIZE;
243 bguid[TIME_POS + 3] = (byte) (lvalue & 0xFF);
244 lvalue >>>= BYTE_SIZE;
245 bguid[TIME_POS + 2] = (byte) (lvalue & 0xFF);
246 lvalue >>>= BYTE_SIZE;
247 bguid[TIME_POS + 1] = (byte) (lvalue & 0xFF);
248 lvalue >>>= BYTE_SIZE;
249 bguid[TIME_POS] = (byte) (lvalue & 0xFF);
250
251
252 value = count;
253 bguid[COUNTER_POS + 2] = (byte) (value & 0xFF);
254 value >>>= BYTE_SIZE;
255 bguid[COUNTER_POS + 1] = (byte) (value & 0xFF);
256 value >>>= BYTE_SIZE;
257 bguid[COUNTER_POS] = (byte) (value & 0xFF);
258
259 }
260
261
262
263
264 @JsonIgnore
265 public final String toBase32() {
266 return BaseXx.getBase32(bguid);
267 }
268
269
270
271
272 @JsonIgnore
273 public final String toBase64() {
274 return BaseXx.getBase64UrlWithoutPadding(bguid);
275 }
276
277
278
279
280 @JsonIgnore
281 public final String toHex() {
282 return BaseXx.getBase16(bguid);
283 }
284
285
286
287
288 @JsonIgnore
289 public final String toArk() {
290 return new StringBuilder(ARK).append(getTenantId()).append('/')
291 .append(toArkName()).toString();
292 }
293
294
295
296
297 @JsonGetter("id")
298 public final String getId() {
299 return toString();
300 }
301
302
303
304
305
306
307
308
309
310
311 @JsonSetter("id")
312 GUID setString(final String idsource) throws InvalidArgumentException {
313 if (idsource == null) {
314 throw new InvalidArgumentException("Empty argument");
315 }
316 final String id = idsource.trim();
317 if (idsource.startsWith(ARK)) {
318 String ids = idsource;
319 ids = ids.substring(ARK.length());
320 final int separator = ids.indexOf('/');
321 if (separator <= 0) {
322 throw new InvalidArgumentException(
323 ATTEMPTED_TO_PARSE_MALFORMED_ARK_GUID + id);
324 }
325 int tenantId;
326 try {
327 tenantId = Integer.parseInt(ids.substring(0, separator));
328 } catch (final NumberFormatException e) {
329 throw new InvalidArgumentException(
330 ATTEMPTED_TO_PARSE_MALFORMED_ARK_GUID + id);
331 }
332
333 ids = ids.substring(separator + 1);
334 final byte[] base32 = BaseXx.getFromBase32(ids);
335 if (base32.length != KEYSIZE - TENANT_SIZE) {
336 throw new InvalidArgumentException(
337 ATTEMPTED_TO_PARSE_MALFORMED_ARK_GUID + id);
338 }
339 System.arraycopy(base32, 0, bguid, HEADER_POS, HEADER_SIZE);
340
341 bguid[TENANT_POS + 3] = (byte) (tenantId & 0xFF);
342 tenantId >>>= BYTE_SIZE;
343 bguid[TENANT_POS + 2] = (byte) (tenantId & 0xFF);
344 tenantId >>>= BYTE_SIZE;
345 bguid[TENANT_POS + 1] = (byte) (tenantId & 0xFF);
346 tenantId >>>= BYTE_SIZE;
347 bguid[TENANT_POS] = (byte) (tenantId & 0x3F);
348
349 System.arraycopy(base32, HEADER_SIZE, bguid, PLATFORM_POS,
350 PLATFORM_SIZE + PID_SIZE + TIME_SIZE + COUNTER_SIZE);
351 return this;
352 }
353 final int len = id.length();
354 try {
355 if (len == KEYB16SIZE) {
356
357 System.arraycopy(BaseXx.getFromBase16(idsource), 0, bguid, 0, KEYSIZE);
358 } else if (len == KEYB32SIZE) {
359
360 System.arraycopy(BaseXx.getFromBase32(idsource), 0, bguid, 0, KEYSIZE);
361 } else if (len == KEYB64SIZE) {
362
363 System.arraycopy(BaseXx.getFromBase64UrlWithoutPadding(idsource), 0,
364 bguid, 0, KEYSIZE);
365 } else {
366 throw new InvalidArgumentException(
367 "Attempted to parse malformed GUID: (" + len + ") " + id);
368 }
369 } catch (final IllegalArgumentException e) {
370 throw new InvalidArgumentException(
371 "Attempted to parse malformed GUID: " + id, e);
372 }
373 return this;
374 }
375
376
377
378
379
380
381 @JsonIgnore
382 public final int getVersion() {
383 return bguid[HEADER_POS] & 0xFF;
384 }
385
386
387
388
389 @JsonIgnore
390 public final int getTenantId() {
391 return (bguid[TENANT_POS] & 0x3F) << BYTE_SIZE * 3 |
392 (bguid[TENANT_POS + 1] & 0xFF) << BYTE_SIZE * 2 |
393 (bguid[TENANT_POS + 2] & 0xFF) << BYTE_SIZE |
394 bguid[TENANT_POS + 3] & 0xFF;
395 }
396
397
398
399
400
401
402 @JsonIgnore
403 public final int getPlatformId() {
404 return (bguid[PLATFORM_POS] & 0x7F) << BYTE_SIZE * 3 |
405 (bguid[PLATFORM_POS + 1] & 0xFF) << BYTE_SIZE * 2 |
406 (bguid[PLATFORM_POS + 2] & 0xFF) << BYTE_SIZE |
407 bguid[PLATFORM_POS + 3] & 0xFF;
408 }
409
410
411
412
413
414
415 @JsonIgnore
416 public final byte[] getMacFragment() {
417 if (getVersion() != VERSION) {
418 return SingletonUtils.getSingletonByteArray();
419 }
420 final byte[] x = new byte[6];
421 x[0] = 0;
422 x[1] = 0;
423 x[2] = (byte) (bguid[PLATFORM_POS] & 0x7F);
424 x[3] = bguid[PLATFORM_POS + 1];
425 x[4] = bguid[PLATFORM_POS + 2];
426 x[5] = bguid[PLATFORM_POS + 3];
427 return x;
428 }
429
430
431
432
433
434
435
436 @JsonIgnore
437 public final int getProcessId() {
438 if (getVersion() != VERSION) {
439 return -1;
440 }
441 return (bguid[PID_POS] & 0xFF) << BYTE_SIZE * 2 |
442 (bguid[PID_POS + 1] & 0xFF) << BYTE_SIZE | bguid[PID_POS + 2] & 0xFF;
443 }
444
445
446
447
448
449
450
451 @JsonIgnore
452 public final long getTimestamp() {
453 if (getVersion() != VERSION) {
454 return -1;
455 }
456 long time = 0;
457 for (int i = 0; i < TIME_SIZE; i++) {
458 time <<= BYTE_SIZE;
459 time |= bguid[TIME_POS + i] & 0xFF;
460 }
461 return time;
462 }
463
464
465
466
467 @JsonIgnore
468 public final int getCounter() {
469 return (bguid[COUNTER_POS] & 0xFF) << BYTE_SIZE * 2 |
470 (bguid[COUNTER_POS + 1] & 0xFF) << BYTE_SIZE |
471 bguid[COUNTER_POS + 2] & 0xFF;
472 }
473
474
475
476
477 public final String toArkName() {
478 final byte[] temp = new byte[KEYSIZE - TENANT_SIZE];
479 System.arraycopy(bguid, HEADER_POS, temp, 0, HEADER_SIZE);
480 System.arraycopy(bguid, PLATFORM_POS, temp, HEADER_SIZE,
481 PLATFORM_SIZE + PID_SIZE + TIME_SIZE + COUNTER_SIZE);
482 return BaseXx.getBase32(temp);
483 }
484
485
486 @Override
487 public String toString() {
488 return toBase32();
489 }
490
491
492
493
494
495
496 @JsonIgnore
497 public final byte[] getBytes() {
498 return Arrays.copyOf(bguid, bguid.length);
499 }
500
501 @Override
502 @JsonIgnore
503 public final int hashCode() {
504 return Arrays.hashCode(bguid);
505 }
506
507 @Override
508 public final boolean equals(final Object o) {
509 if (!(o instanceof GUID)) {
510 return false;
511 }
512 return this == o || Arrays.equals(bguid, ((GUID) o).bguid);
513 }
514
515 @Override
516 public final int compareTo(final GUID guid) {
517 final int id = getTenantId();
518 final int id2 = guid.getTenantId();
519 if (id != id2) {
520 return id < id2? -1 : 1;
521 }
522 final long ts = getTimestamp();
523 final long ts2 = guid.getTimestamp();
524 if (ts == ts2) {
525 final int ct = getCounter();
526 final int ct2 = guid.getCounter();
527 if (ct == ct2) {
528
529 return Arrays.equals(this.bguid, guid.getBytes())? 0 : -1;
530 }
531
532 return ct < ct2? -1 : 1;
533 }
534
535 return ts < ts2? -1 : 1;
536 }
537
538 }