org.assertj
assertj-core
diff --git a/json-java21/src/main/java/jdk/sandbox/java/util/json/UUIDGenerator.java b/json-java21/src/main/java/jdk/sandbox/java/util/json/UUIDGenerator.java
new file mode 100644
index 0000000..df44d11
--- /dev/null
+++ b/json-java21/src/main/java/jdk/sandbox/java/util/json/UUIDGenerator.java
@@ -0,0 +1,201 @@
+package jdk.sandbox.java.util.json;
+
+import java.nio.ByteBuffer;
+import java.security.SecureRandom;
+import java.util.UUID;
+import java.util.logging.Logger;
+
+/// Provides UUID generation utilities supporting both UUIDv7 and alternative generation modes.
+///
+/// This class supports two UUID generation strategies:
+/// 1. **UUIDv7** (default): Time-ordered UUIDs using Unix Epoch milliseconds (backported from Java 26)
+/// 2. **Unique-Then-Time**: Custom format with unique MSB and time-based LSB
+///
+/// The generation mode can be configured via the system property {@code jdk.sandbox.uuid.generator.mode}
+/// with values {@code "v7"} (default) or {@code "unique-then-time"}.
+///
+/// @since Backport from Java 26 (JDK-8334015)
+public final class UUIDGenerator {
+
+ private static final Logger LOGGER = Logger.getLogger(UUIDGenerator.class.getName());
+
+ /// System property key for configuring UUID generation mode
+ public static final String MODE_PROPERTY = "jdk.sandbox.uuid.generator.mode";
+
+ /// Mode value for UUIDv7 generation
+ public static final String MODE_V7 = "v7";
+
+ /// Mode value for unique-then-time generation
+ public static final String MODE_UNIQUE_THEN_TIME = "unique-then-time";
+
+ /// Enum representing the UUID generation mode
+ public enum Mode {
+ /// UUIDv7 mode using Unix Epoch timestamp
+ V7,
+ /// Unique-then-time mode with custom format
+ UNIQUE_THEN_TIME
+ }
+
+ /// Lazy initialization holder for SecureRandom
+ private static final class LazyRandom {
+ static final SecureRandom RANDOM = new SecureRandom();
+ }
+
+ private static final Mode DEFAULT_MODE = Mode.V7;
+ private static final Mode CONFIGURED_MODE;
+
+ static {
+ final String propertyValue = System.getProperty(MODE_PROPERTY);
+ Mode mode = DEFAULT_MODE;
+
+ if (propertyValue != null) {
+ final String normalized = propertyValue.trim().toLowerCase();
+ mode = switch (normalized) {
+ case MODE_V7 -> {
+ LOGGER.fine(() -> "UUID generator mode set to V7 via system property");
+ yield Mode.V7;
+ }
+ case MODE_UNIQUE_THEN_TIME -> {
+ LOGGER.fine(() -> "UUID generator mode set to UNIQUE_THEN_TIME via system property");
+ yield Mode.UNIQUE_THEN_TIME;
+ }
+ default -> {
+ LOGGER.warning(() -> "Invalid UUID generator mode: " + propertyValue +
+ ". Using default mode: " + DEFAULT_MODE);
+ yield DEFAULT_MODE;
+ }
+ };
+ } else {
+ LOGGER.fine(() -> "UUID generator mode not specified, using default: " + DEFAULT_MODE);
+ }
+
+ CONFIGURED_MODE = mode;
+ }
+
+ /// Private constructor to prevent instantiation
+ private UUIDGenerator() {
+ throw new AssertionError("UUIDGenerator cannot be instantiated");
+ }
+
+ /// Generates a UUID using the configured mode.
+ ///
+ /// The mode is determined by the system property {@code jdk.sandbox.uuid.generator.mode}.
+ /// If not specified, defaults to UUIDv7 mode.
+ ///
+ /// @return a {@code UUID} generated according to the configured mode
+ public static UUID generateUUID() {
+ return switch (CONFIGURED_MODE) {
+ case V7 -> ofEpochMillis(System.currentTimeMillis());
+ case UNIQUE_THEN_TIME -> uniqueThenTime(generateUniqueMsb());
+ };
+ }
+
+ /// Creates a type 7 UUID (UUIDv7) {@code UUID} from the given Unix Epoch timestamp.
+ ///
+ /// The returned {@code UUID} will have the given {@code timestamp} in
+ /// the first 6 bytes, followed by the version and variant bits representing {@code UUIDv7},
+ /// and the remaining bytes will contain random data from a cryptographically strong
+ /// pseudo-random number generator.
+ ///
+ /// @apiNote {@code UUIDv7} values are created by allocating a Unix timestamp in milliseconds
+ /// in the most significant 48 bits, allocating the required version (4 bits) and variant (2-bits)
+ /// and filling the remaining 74 bits with random bits. As such, this method rejects {@code timestamp}
+ /// values that do not fit into 48 bits.
+ ///
+ /// Monotonicity (each subsequent value being greater than the last) is a primary characteristic
+ /// of {@code UUIDv7} values. This is due to the {@code timestamp} value being part of the {@code UUID}.
+ /// Callers of this method that wish to generate monotonic {@code UUIDv7} values are expected to
+ /// ensure that the given {@code timestamp} value is monotonic.
+ ///
+ /// @param timestamp the number of milliseconds since midnight 1 Jan 1970 UTC,
+ /// leap seconds excluded.
+ ///
+ /// @return a {@code UUID} constructed using the given {@code timestamp}
+ ///
+ /// @throws IllegalArgumentException if the timestamp is negative or greater than {@code (1L << 48) - 1}
+ ///
+ /// @since Backport from Java 26 (JDK-8334015)
+ public static UUID ofEpochMillis(final long timestamp) {
+ if ((timestamp >> 48) != 0) {
+ throw new IllegalArgumentException("Supplied timestamp: " + timestamp + " does not fit within 48 bits");
+ }
+
+ final byte[] randomBytes = new byte[16];
+ LazyRandom.RANDOM.nextBytes(randomBytes);
+
+ // Embed the timestamp into the first 6 bytes
+ randomBytes[0] = (byte)(timestamp >> 40);
+ randomBytes[1] = (byte)(timestamp >> 32);
+ randomBytes[2] = (byte)(timestamp >> 24);
+ randomBytes[3] = (byte)(timestamp >> 16);
+ randomBytes[4] = (byte)(timestamp >> 8);
+ randomBytes[5] = (byte)(timestamp);
+
+ // Set version to 7
+ randomBytes[6] &= 0x0f;
+ randomBytes[6] |= 0x70;
+
+ // Set variant to IETF
+ randomBytes[8] &= 0x3f;
+ randomBytes[8] |= (byte) 0x80;
+
+ // Convert byte array to UUID using ByteBuffer
+ final ByteBuffer buffer = ByteBuffer.wrap(randomBytes);
+ final long msb = buffer.getLong();
+ final long lsb = buffer.getLong();
+ return new UUID(msb, lsb);
+ }
+
+ /// Creates a UUID with unique MSB and time-based LSB.
+ ///
+ /// Format:
+ /// ```
+ /// ┌──────────────────────────────────────────────────────────────────────────────┐
+ /// │ unique (64 bits) │ time+counter (44 bits) │ random (20 bits) │
+ /// └──────────────────────────────────────────────────────────────────────────────┘
+ /// ```
+ ///
+ /// The LSB contains:
+ /// - 44 most significant bits: time counter for ordering
+ /// - 20 least significant bits: random data
+ ///
+ /// @param uniqueMsb the unique 64-bit value for the MSB
+ /// @return a {@code UUID} with the specified MSB and time-ordered LSB
+ public static UUID uniqueThenTime(final long uniqueMsb) {
+ final int timeBits = 44;
+ final int randomBits = 20;
+ final int randomMask = (1 << randomBits) - 1;
+ final long timeCounter = timeCounterBits();
+ final long msb = uniqueMsb;
+ // Take the most significant 44 bits of timeCounter to preserve time ordering
+ final long timeComponent = timeCounter >> (64 - timeBits); // timeBits is 44
+ final long lsb = (timeComponent << randomBits) | (LazyRandom.RANDOM.nextInt() & randomMask);
+ return new UUID(msb, lsb);
+ }
+
+ /// Generates a time-based counter value using current time and nano precision.
+ ///
+ /// Combines milliseconds since epoch with nano adjustment for higher precision ordering.
+ ///
+ /// @return a 64-bit time counter value
+ private static long timeCounterBits() {
+ final long currentTimeMillis = System.currentTimeMillis();
+ final long nanoTime = System.nanoTime();
+ // Combine milliseconds with nano adjustment for better ordering
+ return (currentTimeMillis << 20) | (nanoTime & 0xFFFFF);
+ }
+
+ /// Generates a unique 64-bit MSB value using cryptographically strong random data.
+ ///
+ /// @return a unique 64-bit value
+ private static long generateUniqueMsb() {
+ return LazyRandom.RANDOM.nextLong();
+ }
+
+ /// Returns the currently configured UUID generation mode.
+ ///
+ /// @return the configured {@code Mode}
+ public static Mode getConfiguredMode() {
+ return CONFIGURED_MODE;
+ }
+}
diff --git a/json-java21/src/test/java/jdk/sandbox/java/util/json/UUIDGeneratorConfigTest.java b/json-java21/src/test/java/jdk/sandbox/java/util/json/UUIDGeneratorConfigTest.java
new file mode 100644
index 0000000..74b00d1
--- /dev/null
+++ b/json-java21/src/test/java/jdk/sandbox/java/util/json/UUIDGeneratorConfigTest.java
@@ -0,0 +1,66 @@
+package jdk.sandbox.java.util.json;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.DisplayName;
+
+import java.util.UUID;
+import java.util.logging.Logger;
+
+import static org.assertj.core.api.Assertions.*;
+
+/// Tests for {@link UUIDGenerator} system property configuration.
+///
+/// This test verifies that system properties can control the UUID generation mode.
+/// These tests run in a separate JVM via Maven Surefire configuration.
+class UUIDGeneratorConfigTest {
+
+ private static final Logger LOGGER = Logger.getLogger(UUIDGeneratorConfigTest.class.getName());
+
+ @Test
+ @DisplayName("Verify default mode is V7 when no system property is set")
+ void testDefaultModeIsV7() {
+ LOGGER.info("Executing testDefaultModeIsV7");
+ // This test assumes no system property was set at JVM startup
+ // In the default configuration, mode should be V7
+ final UUIDGenerator.Mode mode = UUIDGenerator.getConfiguredMode();
+ LOGGER.info(() -> "Configured mode: " + mode);
+
+ // Generate a UUID and verify it's a valid UUIDv7
+ final UUID uuid = UUIDGenerator.generateUUID();
+ assertThat(uuid).isNotNull();
+
+ // If mode is V7, the UUID should have version 7
+ if (mode == UUIDGenerator.Mode.V7) {
+ assertThat(uuid.version()).isEqualTo(7);
+ }
+ }
+
+ @Test
+ @DisplayName("Generate multiple UUIDs and verify consistency")
+ void testMultipleUUIDsWithConfiguredMode() {
+ LOGGER.info("Executing testMultipleUUIDsWithConfiguredMode");
+ final UUIDGenerator.Mode mode = UUIDGenerator.getConfiguredMode();
+ LOGGER.info(() -> "Configured mode: " + mode);
+
+ // Generate multiple UUIDs
+ final UUID uuid1 = UUIDGenerator.generateUUID();
+ final UUID uuid2 = UUIDGenerator.generateUUID();
+ final UUID uuid3 = UUIDGenerator.generateUUID();
+
+ assertThat(uuid1).isNotNull();
+ assertThat(uuid2).isNotNull();
+ assertThat(uuid3).isNotNull();
+
+ // All should be unique
+ assertThat(uuid1).isNotEqualTo(uuid2);
+ assertThat(uuid2).isNotEqualTo(uuid3);
+ assertThat(uuid1).isNotEqualTo(uuid3);
+
+ // If V7 mode, all should have version 7
+ if (mode == UUIDGenerator.Mode.V7) {
+ assertThat(uuid1.version()).isEqualTo(7);
+ assertThat(uuid2.version()).isEqualTo(7);
+ assertThat(uuid3.version()).isEqualTo(7);
+ }
+ }
+}
diff --git a/json-java21/src/test/java/jdk/sandbox/java/util/json/UUIDGeneratorTest.java b/json-java21/src/test/java/jdk/sandbox/java/util/json/UUIDGeneratorTest.java
new file mode 100644
index 0000000..aaeea8e
--- /dev/null
+++ b/json-java21/src/test/java/jdk/sandbox/java/util/json/UUIDGeneratorTest.java
@@ -0,0 +1,269 @@
+package jdk.sandbox.java.util.json;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+import java.util.logging.Logger;
+
+import static org.assertj.core.api.Assertions.*;
+
+/// Tests for {@link UUIDGenerator} covering UUIDv7 and unique-then-time generation modes.
+class UUIDGeneratorTest {
+
+ private static final Logger LOGGER = Logger.getLogger(UUIDGeneratorTest.class.getName());
+
+ @Test
+ @DisplayName("UUIDGenerator cannot be instantiated")
+ void testCannotInstantiate() {
+ LOGGER.info("Executing testCannotInstantiate");
+ assertThatThrownBy(() -> {
+ final var constructor = UUIDGenerator.class.getDeclaredConstructor();
+ constructor.setAccessible(true);
+ constructor.newInstance();
+ }).isInstanceOf(Exception.class)
+ .hasRootCauseInstanceOf(AssertionError.class)
+ .hasRootCauseMessage("UUIDGenerator cannot be instantiated");
+ }
+
+ @Test
+ @DisplayName("ofEpochMillis creates valid UUIDv7 with current timestamp")
+ void testOfEpochMillisWithCurrentTime() {
+ LOGGER.info("Executing testOfEpochMillisWithCurrentTime");
+ final long currentTime = System.currentTimeMillis();
+ final UUID uuid = UUIDGenerator.ofEpochMillis(currentTime);
+
+ assertThat(uuid).isNotNull();
+ assertThat(uuid.version()).isEqualTo(7);
+ assertThat(uuid.variant()).isEqualTo(2); // IETF variant
+ }
+
+ @Test
+ @DisplayName("ofEpochMillis creates valid UUIDv7 with zero timestamp")
+ void testOfEpochMillisWithZero() {
+ LOGGER.info("Executing testOfEpochMillisWithZero");
+ final UUID uuid = UUIDGenerator.ofEpochMillis(0L);
+
+ assertThat(uuid).isNotNull();
+ assertThat(uuid.version()).isEqualTo(7);
+ assertThat(uuid.variant()).isEqualTo(2);
+ }
+
+ @Test
+ @DisplayName("ofEpochMillis creates valid UUIDv7 with max valid timestamp")
+ void testOfEpochMillisWithMaxTimestamp() {
+ LOGGER.info("Executing testOfEpochMillisWithMaxTimestamp");
+ final long maxTimestamp = (1L << 48) - 1; // Max 48-bit value
+ final UUID uuid = UUIDGenerator.ofEpochMillis(maxTimestamp);
+
+ assertThat(uuid).isNotNull();
+ assertThat(uuid.version()).isEqualTo(7);
+ assertThat(uuid.variant()).isEqualTo(2);
+ }
+
+ @ParameterizedTest
+ @ValueSource(longs = {-1L, -100L, Long.MIN_VALUE})
+ @DisplayName("ofEpochMillis rejects negative timestamps")
+ void testOfEpochMillisRejectsNegativeTimestamps(final long timestamp) {
+ LOGGER.info("Executing testOfEpochMillisRejectsNegativeTimestamps with: " + timestamp);
+ assertThatThrownBy(() -> UUIDGenerator.ofEpochMillis(timestamp))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("does not fit within 48 bits");
+ }
+
+ @Test
+ @DisplayName("ofEpochMillis rejects timestamp exceeding 48 bits")
+ void testOfEpochMillisRejectsOversizedTimestamp() {
+ LOGGER.info("Executing testOfEpochMillisRejectsOversizedTimestamp");
+ final long oversizedTimestamp = (1L << 48); // Just over 48 bits
+ assertThatThrownBy(() -> UUIDGenerator.ofEpochMillis(oversizedTimestamp))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("does not fit within 48 bits");
+ }
+
+ @Test
+ @DisplayName("ofEpochMillis produces unique UUIDs with same timestamp")
+ void testOfEpochMillisUniquenessWithSameTimestamp() {
+ LOGGER.info("Executing testOfEpochMillisUniquenessWithSameTimestamp");
+ final long timestamp = System.currentTimeMillis();
+ final Set uuids = new HashSet<>();
+
+ for (int i = 0; i < 1000; i++) {
+ final UUID uuid = UUIDGenerator.ofEpochMillis(timestamp);
+ assertThat(uuids.add(uuid))
+ .as("UUID should be unique")
+ .isTrue();
+ }
+ }
+
+ @Test
+ @DisplayName("ofEpochMillis produces monotonic UUIDs with increasing timestamps")
+ void testOfEpochMillisMonotonicity() {
+ LOGGER.info("Executing testOfEpochMillisMonotonicity");
+ UUID previousUuid = null;
+
+ for (long timestamp = 1000L; timestamp < 2000L; timestamp += 10) {
+ final UUID uuid = UUIDGenerator.ofEpochMillis(timestamp);
+ if (previousUuid != null) {
+ assertThat(uuid.compareTo(previousUuid))
+ .as("UUID with later timestamp should be greater")
+ .isGreaterThan(0);
+ }
+ previousUuid = uuid;
+ }
+ }
+
+ @Test
+ @DisplayName("ofEpochMillis embeds timestamp correctly in first 48 bits")
+ void testOfEpochMillisTimestampEmbedding() {
+ LOGGER.info("Executing testOfEpochMillisTimestampEmbedding");
+ final long timestamp = 0x123456789ABCL; // Known 48-bit value
+ final UUID uuid = UUIDGenerator.ofEpochMillis(timestamp);
+
+ final long msb = uuid.getMostSignificantBits();
+ // Extract first 48 bits (before version/variant)
+ final long extractedTimestamp = (msb >>> 16) & 0xFFFF_FFFF_FFFFL;
+
+ assertThat(extractedTimestamp).isEqualTo(timestamp);
+ }
+
+ @Test
+ @DisplayName("uniqueThenTime creates valid UUID")
+ void testUniqueThenTime() {
+ LOGGER.info("Executing testUniqueThenTime");
+ final long uniqueMsb = 0x123456789ABCDEF0L;
+ final UUID uuid = UUIDGenerator.uniqueThenTime(uniqueMsb);
+
+ assertThat(uuid).isNotNull();
+ assertThat(uuid.getMostSignificantBits()).isEqualTo(uniqueMsb);
+ }
+
+ @Test
+ @DisplayName("uniqueThenTime produces unique UUIDs with same MSB")
+ void testUniqueThenTimeUniqueness() {
+ LOGGER.info("Executing testUniqueThenTimeUniqueness");
+ final long uniqueMsb = 0x123456789ABCDEF0L;
+ final Set uuids = new HashSet<>();
+
+ for (int i = 0; i < 1000; i++) {
+ final UUID uuid = UUIDGenerator.uniqueThenTime(uniqueMsb);
+ assertThat(uuids.add(uuid))
+ .as("UUID should be unique even with same MSB")
+ .isTrue();
+ }
+ }
+
+ @Test
+ @DisplayName("uniqueThenTime produces different UUIDs over time")
+ void testUniqueThenTimeTemporalDifference() throws InterruptedException {
+ LOGGER.info("Executing testUniqueThenTimeTemporalDifference");
+ final long uniqueMsb = 0x123456789ABCDEF0L;
+ final UUID uuid1 = UUIDGenerator.uniqueThenTime(uniqueMsb);
+
+ // Small delay to ensure time progression
+ Thread.sleep(2);
+
+ final UUID uuid2 = UUIDGenerator.uniqueThenTime(uniqueMsb);
+
+ assertThat(uuid1).isNotEqualTo(uuid2);
+ }
+
+ @Test
+ @DisplayName("generateUUID produces valid UUIDs")
+ void testGenerateUUID() {
+ LOGGER.info("Executing testGenerateUUID");
+ final Set uuids = new HashSet<>();
+
+ for (int i = 0; i < 100; i++) {
+ final UUID uuid = UUIDGenerator.generateUUID();
+ assertThat(uuid).isNotNull();
+ assertThat(uuids.add(uuid))
+ .as("Generated UUID should be unique")
+ .isTrue();
+ }
+ }
+
+ @Test
+ @DisplayName("getConfiguredMode returns valid mode")
+ void testGetConfiguredMode() {
+ LOGGER.info("Executing testGetConfiguredMode");
+ final UUIDGenerator.Mode mode = UUIDGenerator.getConfiguredMode();
+
+ assertThat(mode)
+ .isNotNull()
+ .isIn(UUIDGenerator.Mode.V7, UUIDGenerator.Mode.UNIQUE_THEN_TIME);
+ }
+
+ @Test
+ @DisplayName("Generated UUIDs have proper format")
+ void testUUIDFormat() {
+ LOGGER.info("Executing testUUIDFormat");
+ final UUID uuid = UUIDGenerator.generateUUID();
+ final String uuidString = uuid.toString();
+
+ // UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+ assertThat(uuidString)
+ .matches("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}");
+ }
+
+ @Test
+ @DisplayName("UUIDv7 generation is thread-safe")
+ void testV7ThreadSafety() throws InterruptedException {
+ LOGGER.info("Executing testV7ThreadSafety");
+ final Set uuids = new HashSet<>();
+ final int threadCount = 10;
+ final int uuidsPerThread = 100;
+ final Thread[] threads = new Thread[threadCount];
+
+ for (int i = 0; i < threadCount; i++) {
+ threads[i] = new Thread(() -> {
+ for (int j = 0; j < uuidsPerThread; j++) {
+ final UUID uuid = UUIDGenerator.ofEpochMillis(System.currentTimeMillis());
+ synchronized (uuids) {
+ uuids.add(uuid);
+ }
+ }
+ });
+ threads[i].start();
+ }
+
+ for (final Thread thread : threads) {
+ thread.join();
+ }
+
+ assertThat(uuids).hasSize(threadCount * uuidsPerThread);
+ }
+
+ @Test
+ @DisplayName("uniqueThenTime generation is thread-safe")
+ void testUniqueThenTimeThreadSafety() throws InterruptedException {
+ LOGGER.info("Executing testUniqueThenTimeThreadSafety");
+ final Set uuids = new HashSet<>();
+ final int threadCount = 10;
+ final int uuidsPerThread = 100;
+ final Thread[] threads = new Thread[threadCount];
+
+ for (int i = 0; i < threadCount; i++) {
+ final long uniqueMsb = i; // Different MSB per thread
+ threads[i] = new Thread(() -> {
+ for (int j = 0; j < uuidsPerThread; j++) {
+ final UUID uuid = UUIDGenerator.uniqueThenTime(uniqueMsb);
+ synchronized (uuids) {
+ uuids.add(uuid);
+ }
+ }
+ });
+ threads[i].start();
+ }
+
+ for (final Thread thread : threads) {
+ thread.join();
+ }
+
+ assertThat(uuids).hasSize(threadCount * uuidsPerThread);
+ }
+}
From f20954345ab5537233f84b365d88d151786a228c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 9 Nov 2025 07:27:27 +0000
Subject: [PATCH 3/3] Add UUIDGeneratorDemo showing usage examples
Co-authored-by: simbo1905 <322608+simbo1905@users.noreply.github.com>
---
.../jdk/sandbox/demo/UUIDGeneratorDemo.java | 86 +++++++++++++++++++
1 file changed, 86 insertions(+)
create mode 100644 json-java21/src/main/java/jdk/sandbox/demo/UUIDGeneratorDemo.java
diff --git a/json-java21/src/main/java/jdk/sandbox/demo/UUIDGeneratorDemo.java b/json-java21/src/main/java/jdk/sandbox/demo/UUIDGeneratorDemo.java
new file mode 100644
index 0000000..eea7a65
--- /dev/null
+++ b/json-java21/src/main/java/jdk/sandbox/demo/UUIDGeneratorDemo.java
@@ -0,0 +1,86 @@
+package jdk.sandbox.demo;
+
+import jdk.sandbox.java.util.json.UUIDGenerator;
+
+import java.util.UUID;
+
+/// Demonstrates usage of {@link UUIDGenerator} with both UUIDv7 and unique-then-time modes.
+///
+/// This demo shows:
+/// - Default UUIDv7 generation
+/// - Direct UUIDv7 creation with specific timestamps
+/// - Unique-then-time UUID generation
+/// - Configuration mode detection
+public final class UUIDGeneratorDemo {
+
+ public static void main(final String[] args) {
+ System.out.println("=== UUID Generator Demo ===\n");
+
+ // Show current configuration
+ final UUIDGenerator.Mode mode = UUIDGenerator.getConfiguredMode();
+ System.out.println("Configured mode: " + mode);
+ System.out.println("System property: " + System.getProperty(UUIDGenerator.MODE_PROPERTY, "(not set)"));
+ System.out.println();
+
+ // Generate UUIDs using the configured mode
+ System.out.println("--- Generating UUIDs with configured mode ---");
+ for (int i = 0; i < 5; i++) {
+ final UUID uuid = UUIDGenerator.generateUUID();
+ System.out.println("UUID " + (i + 1) + ": " + uuid);
+ if (mode == UUIDGenerator.Mode.V7) {
+ System.out.println(" Version: " + uuid.version() + ", Variant: " + uuid.variant());
+ }
+ }
+ System.out.println();
+
+ // Demonstrate UUIDv7 with specific timestamps
+ System.out.println("--- UUIDv7 with specific timestamps ---");
+ final long baseTime = System.currentTimeMillis();
+ for (int i = 0; i < 3; i++) {
+ final long timestamp = baseTime + (i * 1000); // 1 second apart
+ final UUID uuid = UUIDGenerator.ofEpochMillis(timestamp);
+ System.out.println("Timestamp: " + timestamp + " -> " + uuid);
+ System.out.println(" Version: " + uuid.version() + ", Variant: " + uuid.variant());
+ }
+ System.out.println();
+
+ // Demonstrate unique-then-time mode
+ System.out.println("--- Unique-then-time mode ---");
+ for (int i = 0; i < 3; i++) {
+ final long uniqueMsb = 0x1000000000000000L + i;
+ final UUID uuid = UUIDGenerator.uniqueThenTime(uniqueMsb);
+ System.out.println("Unique MSB: " + Long.toHexString(uniqueMsb) + " -> " + uuid);
+ }
+ System.out.println();
+
+ // Demonstrate monotonicity of UUIDv7
+ System.out.println("--- UUIDv7 Monotonicity (time-ordered) ---");
+ UUID previous = null;
+ for (int i = 0; i < 5; i++) {
+ final long timestamp = baseTime + (i * 100); // 100ms apart
+ final UUID current = UUIDGenerator.ofEpochMillis(timestamp);
+ if (previous != null) {
+ final int comparison = current.compareTo(previous);
+ System.out.println(current + " > " + previous + " ? " + (comparison > 0));
+ } else {
+ System.out.println(current + " (first)");
+ }
+ previous = current;
+ }
+ System.out.println();
+
+ // Show configuration examples
+ System.out.println("=== Configuration Examples ===");
+ System.out.println("To use UUIDv7 (default):");
+ System.out.println(" java -jar app.jar");
+ System.out.println(" or");
+ System.out.println(" java -D" + UUIDGenerator.MODE_PROPERTY + "=v7 -jar app.jar");
+ System.out.println();
+ System.out.println("To use unique-then-time mode:");
+ System.out.println(" java -D" + UUIDGenerator.MODE_PROPERTY + "=unique-then-time -jar app.jar");
+ System.out.println();
+ System.out.println("On Android, set in Application.onCreate():");
+ System.out.println(" System.setProperty(\"" + UUIDGenerator.MODE_PROPERTY + "\", \"v7\");");
+ System.out.println(" Note: Must be set before first UUIDGenerator access");
+ }
+}