Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions json-java21/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
Expand Down
86 changes: 86 additions & 0 deletions json-java21/src/main/java/jdk/sandbox/demo/UUIDGeneratorDemo.java
Original file line number Diff line number Diff line change
@@ -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");
}
}
Original file line number Diff line number Diff line change
@@ -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.
/// <p>
/// 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;
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Loading