Skip to content

Commit cd9bf67

Browse files
authored
Add alternate version to UUIDv7 generator that uses random values on every call (not just for different timestamp) (#94)
1 parent 191f37f commit cd9bf67

File tree

12 files changed

+289
-61
lines changed

12 files changed

+289
-61
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ UUID uuid = Generators.nameBasedgenerator().generate("string to hash"); // Versi
6767
// With JUG 4.1+: support for https://github.com/uuid6/uuid6-ietf-draft versions 6 and 7:
6868
UUID uuid = Generators.timeBasedReorderedGenerator().generate(); // Version 6
6969
UUID uuid = Generators.timeBasedEpochGenerator().generate(); // Version 7
70+
UUID uuid = Generators.timeBasedEpochRandomGenerator().generate(); // Version 7 with per-call random values
7071
```
7172

7273
If you want customize generators, you may also just want to hold on to generator instance:

release-notes/VERSION

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ Releases
1313
(contributed by @magdel)
1414
#85: Fix `LazyRandom` for native code generation tools
1515
(contributed by @Maia-Everett)
16+
#94: Add alternate version to UUIDv7 generator that uses random values on every
17+
call (not just for different timestamp)
18+
(contributed by @magdel)
1619

1720
4.3.0 (12-Sep-2023)
1821

src/main/java/com/fasterxml/uuid/Generators.java

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.fasterxml.uuid.impl.NameBasedGenerator;
2424
import com.fasterxml.uuid.impl.RandomBasedGenerator;
2525
import com.fasterxml.uuid.impl.TimeBasedEpochGenerator;
26+
import com.fasterxml.uuid.impl.TimeBasedEpochRandomGenerator;
2627
import com.fasterxml.uuid.impl.TimeBasedReorderedGenerator;
2728
import com.fasterxml.uuid.impl.TimeBasedGenerator;
2829

@@ -135,6 +136,10 @@ public static TimeBasedEpochGenerator timeBasedEpochGenerator()
135136
* Factory method for constructing UUID generator that generates UUID using
136137
* version 7 (Unix Epoch time+random based), using specified {@link Random}
137138
* number generator.
139+
*<p>
140+
* NOTE: calls within same millisecond produce very similar values; this may be
141+
* unsafe in some environments.
142+
*<p>
138143
* No additional external synchronization is used.
139144
*/
140145
public static TimeBasedEpochGenerator timeBasedEpochGenerator(Random random)
@@ -145,9 +150,12 @@ public static TimeBasedEpochGenerator timeBasedEpochGenerator(Random random)
145150
/**
146151
* Factory method for constructing UUID generator that generates UUID using
147152
* version 7 (Unix Epoch time+random based), using specified {@link Random}
148-
* number generato.
149-
* Timestamp to use is accessed using specified {@link UUIDClock}
150-
*
153+
* number generator.
154+
* Timestamp to use is accessed using specified {@link UUIDClock}.
155+
*<p>
156+
* NOTE: calls within same millisecond produce very similar values; this may be
157+
* unsafe in some environments.
158+
*<p>
151159
* No additional external synchronization is used.
152160
*
153161
* @since 4.3
@@ -158,6 +166,42 @@ public static TimeBasedEpochGenerator timeBasedEpochGenerator(Random random,
158166
return new TimeBasedEpochGenerator(random, clock);
159167
}
160168

169+
/**
170+
* Factory method for constructing UUID generator that generates UUID using
171+
* version 7 (Unix Epoch time+random based), using specified {@link Random}
172+
* number generator.
173+
*<p>
174+
* Calls within same millisecond use additional per-call randomness to try to create
175+
* more distinct values, compared to {@link #timeBasedEpochGenerator(Random)}
176+
*<p>
177+
* No additional external synchronization is used.
178+
*
179+
* @since 5.0
180+
*/
181+
public static TimeBasedEpochRandomGenerator timeBasedEpochRandomGenerator(Random random)
182+
{
183+
return new TimeBasedEpochRandomGenerator(random);
184+
}
185+
186+
/**
187+
* Factory method for constructing UUID generator that generates UUID using
188+
* version 7 (Unix Epoch time+random based), using specified {@link Random}
189+
* number generator.
190+
* Timestamp to use is accessed using specified {@link UUIDClock}
191+
*<p>
192+
* Calls within same millisecond use additional per-call randomness to try to create
193+
* more distinct values, compared to {@link #timeBasedEpochGenerator(Random)}
194+
*<p>
195+
* No additional external synchronization is used.
196+
*
197+
* @since 5.0
198+
*/
199+
public static TimeBasedEpochRandomGenerator timeBasedEpochRandomGenerator(Random random,
200+
UUIDClock clock)
201+
{
202+
return new TimeBasedEpochRandomGenerator(random, clock);
203+
}
204+
161205
// // Time+location-based generation
162206

163207
/**

src/main/java/com/fasterxml/uuid/Jug.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public class Jug
3333
TYPES.put("name-based", "n");
3434
TYPES.put("reordered-time-based", "o"); // Version 6
3535
TYPES.put("epoch-time-based", "e"); // Version 7
36+
TYPES.put("random-epoch-time-based", "m"); // Version 7 but more random
3637
}
3738

3839
protected final static HashMap<String,String> OPTIONS = new HashMap<String,String>();
@@ -266,6 +267,16 @@ public static void main(String[] args)
266267
noArgGenerator = Generators.timeBasedEpochGenerator(r);
267268
}
268269
break;
270+
case 'm': // random-epoch-time-based
271+
usesRnd = true;
272+
{
273+
SecureRandom r = new SecureRandom();
274+
if (verbose) {
275+
System.out.print("(using secure random generator, info = '"+r.getProvider().getInfo()+"')");
276+
}
277+
noArgGenerator = Generators.timeBasedEpochRandomGenerator(r);
278+
}
279+
break;
269280
case 'n': // name-based
270281
if (nameSpace == null) {
271282
System.err.println("--name-space (-s) - argument missing when using method that requires it, exiting.");

src/main/java/com/fasterxml/uuid/UUIDGenerator.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,32 @@ protected UUIDGenerator() { }
4646
* generator instance will produce.
4747
*/
4848
public abstract UUIDType getType();
49+
50+
/*
51+
/**********************************************************
52+
/* Helper methods for implementations
53+
/**********************************************************
54+
*/
55+
56+
protected final static long _toLong(byte[] buffer, int offset)
57+
{
58+
long l1 = _toInt(buffer, offset);
59+
long l2 = _toInt(buffer, offset+4);
60+
long l = (l1 << 32) + ((l2 << 32) >>> 32);
61+
return l;
62+
}
63+
64+
protected final static long _toInt(byte[] buffer, int offset)
65+
{
66+
return (buffer[offset] << 24)
67+
+ ((buffer[++offset] & 0xFF) << 16)
68+
+ ((buffer[++offset] & 0xFF) << 8)
69+
+ (buffer[++offset] & 0xFF);
70+
}
71+
72+
protected final static long _toShort(byte[] buffer, int offset)
73+
{
74+
return ((buffer[offset] & 0xFF) << 8)
75+
+ (buffer[++offset] & 0xFF);
76+
}
4977
}

src/main/java/com/fasterxml/uuid/impl/NameBasedGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public NameBasedGenerator(UUID namespace, MessageDigest digester, UUIDType type)
116116
@Override
117117
public UUID generate(String name)
118118
{
119-
// !!! TODO: 14-Oct-2010, tatu: can repurpose faster UTF-8 encoding from Jackson
119+
// !!! TODO: 14-Oct-2010, tatu: could re-purpose faster UTF-8 encoding from Jackson
120120
return generate(name.getBytes(_utf8));
121121
}
122122

src/main/java/com/fasterxml/uuid/impl/RandomBasedGenerator.java

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -92,26 +92,4 @@ public UUID generate()
9292
}
9393
return UUIDUtil.constructUUID(UUIDType.RANDOM_BASED, r1, r2);
9494
}
95-
96-
/*
97-
/**********************************************************************
98-
/* Internal methods
99-
/**********************************************************************
100-
*/
101-
102-
protected final static long _toLong(byte[] buffer, int offset)
103-
{
104-
long l1 = _toInt(buffer, offset);
105-
long l2 = _toInt(buffer, offset+4);
106-
long l = (l1 << 32) + ((l2 << 32) >>> 32);
107-
return l;
108-
}
109-
110-
private final static long _toInt(byte[] buffer, int offset)
111-
{
112-
return (buffer[offset] << 24)
113-
+ ((buffer[++offset] & 0xFF) << 16)
114-
+ ((buffer[++offset] & 0xFF) << 8)
115-
+ (buffer[++offset] & 0xFF);
116-
}
11795
}

src/main/java/com/fasterxml/uuid/impl/TimeBasedEpochGenerator.java

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -144,32 +144,4 @@ public UUID construct(long rawTimestamp)
144144
lock.unlock();
145145
}
146146
}
147-
148-
/*
149-
/**********************************************************************
150-
/* Internal methods
151-
/**********************************************************************
152-
*/
153-
154-
protected final static long _toLong(byte[] buffer, int offset)
155-
{
156-
long l1 = _toInt(buffer, offset);
157-
long l2 = _toInt(buffer, offset+4);
158-
long l = (l1 << 32) + ((l2 << 32) >>> 32);
159-
return l;
160-
}
161-
162-
private final static long _toInt(byte[] buffer, int offset)
163-
{
164-
return (buffer[offset] << 24)
165-
+ ((buffer[++offset] & 0xFF) << 16)
166-
+ ((buffer[++offset] & 0xFF) << 8)
167-
+ (buffer[++offset] & 0xFF);
168-
}
169-
170-
private final static long _toShort(byte[] buffer, int offset)
171-
{
172-
return ((buffer[offset] & 0xFF) << 8)
173-
+ (buffer[++offset] & 0xFF);
174-
}
175147
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package com.fasterxml.uuid.impl;
2+
3+
import com.fasterxml.uuid.NoArgGenerator;
4+
import com.fasterxml.uuid.UUIDClock;
5+
import com.fasterxml.uuid.UUIDType;
6+
7+
import java.security.SecureRandom;
8+
import java.util.Random;
9+
import java.util.UUID;
10+
import java.util.concurrent.locks.Lock;
11+
import java.util.concurrent.locks.ReentrantLock;
12+
13+
/**
14+
* Implementation of UUID generator that uses time/location based generation
15+
* method field from the Unix Epoch timestamp source - the number of
16+
* milliseconds seconds since midnight 1 Jan 1970 UTC, leap seconds excluded.
17+
* This is usually referred to as "Version 7".
18+
* In addition to that random part is regenerated for every new UUID.
19+
* This removes possibilities to have almost similar UUID, when calls
20+
* to generate are made within same millisecond.
21+
* <p>
22+
* As all JUG provided implementations, this generator is fully thread-safe.
23+
* Additionally it can also be made externally synchronized with other instances
24+
* (even ones running on other JVMs); to do this, use
25+
* {@link com.fasterxml.uuid.ext.FileBasedTimestampSynchronizer} (or
26+
* equivalent).
27+
*
28+
* @since 5.0
29+
*/
30+
public class TimeBasedEpochRandomGenerator extends NoArgGenerator
31+
{
32+
private static final int ENTROPY_BYTE_LENGTH = 10;
33+
34+
/*
35+
/**********************************************************************
36+
/* Configuration
37+
/**********************************************************************
38+
*/
39+
40+
/**
41+
* Random number generator that this generator uses.
42+
*/
43+
protected final Random _random;
44+
45+
/**
46+
* Underlying {@link UUIDClock} used for accessing current time, to use for
47+
* generation.
48+
*/
49+
protected final UUIDClock _clock;
50+
51+
private final byte[] _lastEntropy = new byte[ENTROPY_BYTE_LENGTH];
52+
private final Lock lock = new ReentrantLock();
53+
54+
/*
55+
/**********************************************************************
56+
/* Construction
57+
/**********************************************************************
58+
*/
59+
60+
/**
61+
* @param rnd Random number generator to use for generating UUIDs; if null,
62+
* shared default generator is used. Note that it is strongly recommend to
63+
* use a <b>good</b> (pseudo) random number generator; for example, JDK's
64+
* {@link SecureRandom}.
65+
*/
66+
public TimeBasedEpochRandomGenerator(Random rnd) {
67+
this(rnd, UUIDClock.systemTimeClock());
68+
}
69+
70+
/**
71+
* @param rnd Random number generator to use for generating UUIDs; if null,
72+
* shared default generator is used. Note that it is strongly recommend to
73+
* use a <b>good</b> (pseudo) random number generator; for example, JDK's
74+
* {@link SecureRandom}.
75+
* @param clock clock Object used for accessing current time to use for generation
76+
*/
77+
public TimeBasedEpochRandomGenerator(Random rnd, UUIDClock clock)
78+
{
79+
if (rnd == null) {
80+
rnd = LazyRandom.sharedSecureRandom();
81+
}
82+
_random = rnd;
83+
_clock = clock;
84+
}
85+
86+
/*
87+
/**********************************************************************
88+
/* Access to config
89+
/**********************************************************************
90+
*/
91+
92+
@Override
93+
public UUIDType getType() { return UUIDType.TIME_BASED_EPOCH; }
94+
95+
/*
96+
/**********************************************************************
97+
/* UUID generation
98+
/**********************************************************************
99+
*/
100+
101+
@Override
102+
public UUID generate()
103+
{
104+
return construct(_clock.currentTimeMillis());
105+
}
106+
107+
/**
108+
* Method that will construct actual {@link UUID} instance for given
109+
* unix epoch timestamp: called by {@link #generate()} but may alternatively be
110+
* called directly to construct an instance with known timestamp.
111+
* NOTE: calling this method directly produces somewhat distinct UUIDs as
112+
* "entropy" value is still generated as necessary to avoid producing same
113+
* {@link UUID} even if same timestamp is being passed.
114+
*
115+
* @param rawTimestamp unix epoch millis
116+
*
117+
* @return unix epoch time based UUID
118+
*/
119+
public UUID construct(long rawTimestamp)
120+
{
121+
lock.lock();
122+
try {
123+
_random.nextBytes(_lastEntropy);
124+
return UUIDUtil.constructUUID(UUIDType.TIME_BASED_EPOCH, (rawTimestamp << 16) | _toShort(_lastEntropy, 0), _toLong(_lastEntropy, 2));
125+
} finally {
126+
lock.unlock();
127+
}
128+
}
129+
}

src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ private final static void _checkUUIDByteArray(byte[] bytes, int offset)
362362
*
363363
* @return timestamp in milliseconds (since Epoch), or 0 if type does not support timestamps
364364
*
365-
* @since 5.0.0
365+
* @since 5.0
366366
*/
367367
public static long extractTimestamp(UUID uuid)
368368
{

src/test/java/com/fasterxml/uuid/UUIDComparatorTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import com.fasterxml.uuid.impl.TimeBasedEpochGenerator;
2424

25+
import com.fasterxml.uuid.impl.TimeBasedEpochRandomGenerator;
2526
import junit.framework.TestCase;
2627

2728
public class UUIDComparatorTest

0 commit comments

Comments
 (0)