From 295cac58f9a3127a9b9768c21d15eba9dc2fc81f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Nov 2025 07:18:40 +0000 Subject: [PATCH 1/3] Initial plan From 328e11c06e78b3542a5cd4d3176434e7c0d70d3c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Nov 2025 07:25:38 +0000 Subject: [PATCH 2/3] Add UUIDGenerator with UUIDv7 support and configurable modes Co-authored-by: simbo1905 <322608+simbo1905@users.noreply.github.com> --- json-java21/pom.xml | 5 + .../sandbox/java/util/json/UUIDGenerator.java | 201 +++++++++++++ .../util/json/UUIDGeneratorConfigTest.java | 66 +++++ .../java/util/json/UUIDGeneratorTest.java | 269 ++++++++++++++++++ 4 files changed, 541 insertions(+) create mode 100644 json-java21/src/main/java/jdk/sandbox/java/util/json/UUIDGenerator.java create mode 100644 json-java21/src/test/java/jdk/sandbox/java/util/json/UUIDGeneratorConfigTest.java create mode 100644 json-java21/src/test/java/jdk/sandbox/java/util/json/UUIDGeneratorTest.java diff --git a/json-java21/pom.xml b/json-java21/pom.xml index 70e602e..4b659a0 100644 --- a/json-java21/pom.xml +++ b/json-java21/pom.xml @@ -34,6 +34,11 @@ junit-jupiter-engine test + + org.junit.jupiter + junit-jupiter-params + test + 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"); + } +}