diff --git a/README.md b/README.md index 6ce06a4..e84d56b 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ UUID uuid = Generators.nameBasedgenerator().generate("string to hash"); // Versi // With JUG 4.1+: support for https://github.com/uuid6/uuid6-ietf-draft versions 6 and 7: UUID uuid = Generators.timeBasedReorderedGenerator().generate(); // Version 6 UUID uuid = Generators.timeBasedEpochGenerator().generate(); // Version 7 +UUID uuid = Generators.timeBasedEpochRandomGenerator().generate(); // Version 7 with per-call random values ``` If you want customize generators, you may also just want to hold on to generator instance: diff --git a/release-notes/VERSION b/release-notes/VERSION index f621b53..d7db8cd 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -13,6 +13,9 @@ Releases (contributed by @magdel) #85: Fix `LazyRandom` for native code generation tools (contributed by @Maia-Everett) +#94: Add alternate version to UUIDv7 generator that uses random values on every + call (not just for different timestamp) + (contributed by @magdel) 4.3.0 (12-Sep-2023) diff --git a/src/main/java/com/fasterxml/uuid/Generators.java b/src/main/java/com/fasterxml/uuid/Generators.java index 1c4fe6b..9a17506 100644 --- a/src/main/java/com/fasterxml/uuid/Generators.java +++ b/src/main/java/com/fasterxml/uuid/Generators.java @@ -23,6 +23,7 @@ import com.fasterxml.uuid.impl.NameBasedGenerator; import com.fasterxml.uuid.impl.RandomBasedGenerator; import com.fasterxml.uuid.impl.TimeBasedEpochGenerator; +import com.fasterxml.uuid.impl.TimeBasedEpochRandomGenerator; import com.fasterxml.uuid.impl.TimeBasedReorderedGenerator; import com.fasterxml.uuid.impl.TimeBasedGenerator; @@ -135,6 +136,10 @@ public static TimeBasedEpochGenerator timeBasedEpochGenerator() * Factory method for constructing UUID generator that generates UUID using * version 7 (Unix Epoch time+random based), using specified {@link Random} * number generator. + *
+ * NOTE: calls within same millisecond produce very similar values; this may be + * unsafe in some environments. + *
* No additional external synchronization is used. */ public static TimeBasedEpochGenerator timeBasedEpochGenerator(Random random) @@ -145,9 +150,12 @@ public static TimeBasedEpochGenerator timeBasedEpochGenerator(Random random) /** * Factory method for constructing UUID generator that generates UUID using * version 7 (Unix Epoch time+random based), using specified {@link Random} - * number generato. - * Timestamp to use is accessed using specified {@link UUIDClock} - * + * number generator. + * Timestamp to use is accessed using specified {@link UUIDClock}. + *
+ * NOTE: calls within same millisecond produce very similar values; this may be + * unsafe in some environments. + *
* No additional external synchronization is used. * * @since 4.3 @@ -158,6 +166,42 @@ public static TimeBasedEpochGenerator timeBasedEpochGenerator(Random random, return new TimeBasedEpochGenerator(random, clock); } + /** + * Factory method for constructing UUID generator that generates UUID using + * version 7 (Unix Epoch time+random based), using specified {@link Random} + * number generator. + *
+ * Calls within same millisecond use additional per-call randomness to try to create + * more distinct values, compared to {@link #timeBasedEpochGenerator(Random)} + *
+ * No additional external synchronization is used. + * + * @since 5.0 + */ + public static TimeBasedEpochRandomGenerator timeBasedEpochRandomGenerator(Random random) + { + return new TimeBasedEpochRandomGenerator(random); + } + + /** + * Factory method for constructing UUID generator that generates UUID using + * version 7 (Unix Epoch time+random based), using specified {@link Random} + * number generator. + * Timestamp to use is accessed using specified {@link UUIDClock} + *
+ * Calls within same millisecond use additional per-call randomness to try to create + * more distinct values, compared to {@link #timeBasedEpochGenerator(Random)} + *
+ * No additional external synchronization is used.
+ *
+ * @since 5.0
+ */
+ public static TimeBasedEpochRandomGenerator timeBasedEpochRandomGenerator(Random random,
+ UUIDClock clock)
+ {
+ return new TimeBasedEpochRandomGenerator(random, clock);
+ }
+
// // Time+location-based generation
/**
diff --git a/src/main/java/com/fasterxml/uuid/Jug.java b/src/main/java/com/fasterxml/uuid/Jug.java
index 08aac47..3b9a71f 100644
--- a/src/main/java/com/fasterxml/uuid/Jug.java
+++ b/src/main/java/com/fasterxml/uuid/Jug.java
@@ -33,6 +33,7 @@ public class Jug
TYPES.put("name-based", "n");
TYPES.put("reordered-time-based", "o"); // Version 6
TYPES.put("epoch-time-based", "e"); // Version 7
+ TYPES.put("random-epoch-time-based", "m"); // Version 7 but more random
}
protected final static HashMap
+ * As all JUG provided implementations, this generator is fully thread-safe.
+ * Additionally it can also be made externally synchronized with other instances
+ * (even ones running on other JVMs); to do this, use
+ * {@link com.fasterxml.uuid.ext.FileBasedTimestampSynchronizer} (or
+ * equivalent).
+ *
+ * @since 5.0
+ */
+public class TimeBasedEpochRandomGenerator extends NoArgGenerator
+{
+ private static final int ENTROPY_BYTE_LENGTH = 10;
+
+ /*
+ /**********************************************************************
+ /* Configuration
+ /**********************************************************************
+ */
+
+ /**
+ * Random number generator that this generator uses.
+ */
+ protected final Random _random;
+
+ /**
+ * Underlying {@link UUIDClock} used for accessing current time, to use for
+ * generation.
+ */
+ protected final UUIDClock _clock;
+
+ private final byte[] _lastEntropy = new byte[ENTROPY_BYTE_LENGTH];
+ private final Lock lock = new ReentrantLock();
+
+ /*
+ /**********************************************************************
+ /* Construction
+ /**********************************************************************
+ */
+
+ /**
+ * @param rnd Random number generator to use for generating UUIDs; if null,
+ * shared default generator is used. Note that it is strongly recommend to
+ * use a good (pseudo) random number generator; for example, JDK's
+ * {@link SecureRandom}.
+ */
+ public TimeBasedEpochRandomGenerator(Random rnd) {
+ this(rnd, UUIDClock.systemTimeClock());
+ }
+
+ /**
+ * @param rnd Random number generator to use for generating UUIDs; if null,
+ * shared default generator is used. Note that it is strongly recommend to
+ * use a good (pseudo) random number generator; for example, JDK's
+ * {@link SecureRandom}.
+ * @param clock clock Object used for accessing current time to use for generation
+ */
+ public TimeBasedEpochRandomGenerator(Random rnd, UUIDClock clock)
+ {
+ if (rnd == null) {
+ rnd = LazyRandom.sharedSecureRandom();
+ }
+ _random = rnd;
+ _clock = clock;
+ }
+
+ /*
+ /**********************************************************************
+ /* Access to config
+ /**********************************************************************
+ */
+
+ @Override
+ public UUIDType getType() { return UUIDType.TIME_BASED_EPOCH; }
+
+ /*
+ /**********************************************************************
+ /* UUID generation
+ /**********************************************************************
+ */
+
+ @Override
+ public UUID generate()
+ {
+ return construct(_clock.currentTimeMillis());
+ }
+
+ /**
+ * Method that will construct actual {@link UUID} instance for given
+ * unix epoch timestamp: called by {@link #generate()} but may alternatively be
+ * called directly to construct an instance with known timestamp.
+ * NOTE: calling this method directly produces somewhat distinct UUIDs as
+ * "entropy" value is still generated as necessary to avoid producing same
+ * {@link UUID} even if same timestamp is being passed.
+ *
+ * @param rawTimestamp unix epoch millis
+ *
+ * @return unix epoch time based UUID
+ */
+ public UUID construct(long rawTimestamp)
+ {
+ lock.lock();
+ try {
+ _random.nextBytes(_lastEntropy);
+ return UUIDUtil.constructUUID(UUIDType.TIME_BASED_EPOCH, (rawTimestamp << 16) | _toShort(_lastEntropy, 0), _toLong(_lastEntropy, 2));
+ } finally {
+ lock.unlock();
+ }
+ }
+}
diff --git a/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java b/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java
index 60ce671..fffea93 100644
--- a/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java
+++ b/src/main/java/com/fasterxml/uuid/impl/UUIDUtil.java
@@ -362,7 +362,7 @@ private final static void _checkUUIDByteArray(byte[] bytes, int offset)
*
* @return timestamp in milliseconds (since Epoch), or 0 if type does not support timestamps
*
- * @since 5.0.0
+ * @since 5.0
*/
public static long extractTimestamp(UUID uuid)
{
diff --git a/src/test/java/com/fasterxml/uuid/UUIDComparatorTest.java b/src/test/java/com/fasterxml/uuid/UUIDComparatorTest.java
index 6103a66..3d79ec9 100644
--- a/src/test/java/com/fasterxml/uuid/UUIDComparatorTest.java
+++ b/src/test/java/com/fasterxml/uuid/UUIDComparatorTest.java
@@ -22,6 +22,7 @@
import com.fasterxml.uuid.impl.TimeBasedEpochGenerator;
+import com.fasterxml.uuid.impl.TimeBasedEpochRandomGenerator;
import junit.framework.TestCase;
public class UUIDComparatorTest
diff --git a/src/test/java/com/fasterxml/uuid/UUIDGeneratorTest.java b/src/test/java/com/fasterxml/uuid/UUIDGeneratorTest.java
index 59407ff..788c68b 100644
--- a/src/test/java/com/fasterxml/uuid/UUIDGeneratorTest.java
+++ b/src/test/java/com/fasterxml/uuid/UUIDGeneratorTest.java
@@ -32,6 +32,7 @@
import com.fasterxml.uuid.impl.NameBasedGenerator;
import com.fasterxml.uuid.impl.RandomBasedGenerator;
import com.fasterxml.uuid.impl.TimeBasedEpochGenerator;
+import com.fasterxml.uuid.impl.TimeBasedEpochRandomGenerator;
import com.fasterxml.uuid.impl.TimeBasedReorderedGenerator;
import com.fasterxml.uuid.impl.TimeBasedGenerator;
@@ -119,7 +120,8 @@ public void testGenerateRandomBasedUUID()
// we need a instance to use
RandomBasedGenerator uuid_gen = Generators.randomBasedGenerator();
-
+ assertEquals(uuid_gen.getType(), UUIDType.RANDOM_BASED);
+
// for the random UUID generator, we will generate a bunch of
// random UUIDs
UUID uuid_array[] = new UUID[SIZE_OF_TEST_ARRAY];
@@ -153,7 +155,8 @@ public void testGenerateTimeBasedUUID()
// we need a instance to use
TimeBasedGenerator uuid_gen = Generators.timeBasedGenerator();
-
+ assertEquals(uuid_gen.getType(), UUIDType.TIME_BASED);
+
// first check that given a number of calls to generateTimeBasedUUID,
// all returned UUIDs order after the last returned UUID
// we'll check this by generating the UUIDs into one array and sorting
@@ -202,7 +205,8 @@ public void testGenerateTimeBasedUUIDWithEthernetAddress()
// we need a instance to use
TimeBasedGenerator uuid_gen = Generators.timeBasedGenerator(ethernet_address);
-
+ assertEquals(uuid_gen.getType(), UUIDType.TIME_BASED);
+
// check that given a number of calls to generateTimeBasedUUID,
// all returned UUIDs order after the last returned UUID
// we'll check this by generating the UUIDs into one array and sorting
@@ -261,6 +265,7 @@ public void testGenerateTimeBasedEpochUUID() throws Exception
// we need a instance to use
TimeBasedEpochGenerator uuid_gen = Generators.timeBasedEpochGenerator(entropy);
+ assertEquals(uuid_gen.getType(), UUIDType.TIME_BASED_EPOCH);
// first check that given a number of calls to generateTimeBasedEpochUUID,
// all returned UUIDs order after the last returned UUID
@@ -299,6 +304,58 @@ public void testGenerateTimeBasedEpochUUID() throws Exception
checkUUIDArrayForCorrectCreationTimeEpoch(uuid_array, start_time, end_time);
}
+ /**
+ * Test of generateTimeBasedEpochRandomUUID() method,
+ * of class com.fasterxml.uuid.UUIDGenerator.
+ */
+ public void testGenerateTimeBasedEpochRandomUUID() throws Exception
+ {
+ // this test will attempt to check for reasonable behavior of the
+ // generateTimeBasedRandomUUID method
+
+ Random entropy = new Random(0x666);
+
+ // we need a instance to use
+ TimeBasedEpochRandomGenerator uuid_gen = Generators.timeBasedEpochRandomGenerator(entropy);
+
+ assertEquals(uuid_gen.getType(), UUIDType.TIME_BASED_EPOCH);
+ // first check that given a number of calls to generateTimeBasedEpochUUID,
+ // all returned UUIDs order after the last returned UUID
+ // we'll check this by generating the UUIDs into one array and sorting
+ // then in another and checking the order of the two match
+ // change the number in the array statement if you want more or less
+ // UUIDs to be generated and tested
+ UUID uuid_array[] = new UUID[SIZE_OF_TEST_ARRAY];
+
+ // before we generate all the uuids, lets get the start time
+ long start_time = System.currentTimeMillis();
+ Thread.sleep(2); // Clean start time
+
+ // now create the array of uuids
+ for (int i = 0; i < uuid_array.length; i++) {
+ uuid_array[i] = uuid_gen.generate();
+ }
+
+ // now capture the end time
+ long end_time = System.currentTimeMillis();
+ Thread.sleep(2); // Clean end time
+
+ // check that none of the UUIDs are null
+ checkUUIDArrayForNonNullUUIDs(uuid_array);
+
+ // check that all the uuids were correct variant and version (type-1)
+ checkUUIDArrayForCorrectVariantAndVersion(uuid_array, UUIDType.TIME_BASED_EPOCH);
+
+ // check that all uuids were unique
+ // NOTE: technically, this test 'could' fail, but statistically
+ // speaking it should be extremely unlikely unless the implementation
+ // of (Secure)Random is bad
+ checkUUIDArrayForUniqueness(uuid_array);
+
+ // check that all uuids have timestamps between the start and end time
+ checkUUIDArrayForCorrectCreationTimeEpoch(uuid_array, start_time, end_time);
+ }
+
// [#70]: allow use of custom UUIDClock
public void testGenerateTimeBasedEpochUUIDWithFixedClock() throws Exception
{
@@ -342,7 +399,8 @@ public void testGenerateNameBasedUUIDNameSpaceAndName()
// we need a instance to use
NameBasedGenerator uuid_gen = Generators.nameBasedGenerator(NameBasedGenerator.NAMESPACE_URL);
-
+ assertEquals(uuid_gen.getType(), UUIDType.NAME_BASED_SHA1);
+
UUID uuid_array[] = new UUID[SIZE_OF_TEST_ARRAY];
// now create the array of uuids
@@ -426,6 +484,7 @@ public void testGenerateNameBasedUUIDNameSpaceNameAndMessageDigest()
// generateNameBasedUUID method
NameBasedGenerator uuid_gen = Generators.nameBasedGenerator(NameBasedGenerator.NAMESPACE_URL, MESSAGE_DIGEST);
+ assertEquals(uuid_gen.getType(), UUIDType.NAME_BASED_MD5);
UUID uuid_array[] = new UUID[SIZE_OF_TEST_ARRAY];
// now create the array of uuids
@@ -502,7 +561,8 @@ public void testGenerateTimeBasedReorderedUUID()
// we need a instance to use
TimeBasedReorderedGenerator uuid_gen = Generators.timeBasedReorderedGenerator();
-
+ assertEquals(uuid_gen.getType(), UUIDType.TIME_BASED_REORDERED);
+
// first check that given a number of calls to generateTimeBasedReorderedUUID,
// all returned UUIDs order after the last returned UUID
// we'll check this by generating the UUIDs into one array and sorting
@@ -551,7 +611,8 @@ public void testGenerateTimeBasedReorderedUUIDWithEthernetAddress()
// we need a instance to use
TimeBasedReorderedGenerator uuid_gen = Generators.timeBasedReorderedGenerator(ethernet_address);
-
+ assertEquals(uuid_gen.getType(), UUIDType.TIME_BASED_REORDERED);
+
// check that given a number of calls to generateTimeBasedReorderedUUID,
// all returned UUIDs order after the last returned UUID
// we'll check this by generating the UUIDs into one array and sorting