Add support for RandomGenerator in Java 17 #19

This commit is contained in:
Fabio Lima 2022-07-09 14:43:36 -03:00
parent 3b6a135fd3
commit f80eb4029c
8 changed files with 508 additions and 66 deletions

View File

@ -6,6 +6,10 @@ All notable changes to this project will be documented in this file.
Nothing unreleased.
## [5.0.0] - 2022-07-09
Add support for RandomGenerator in Java 17. #19
## [4.2.1] - 2022-04-21
Handle clock drift. #18
@ -286,7 +290,8 @@ Project created as an alternative Java implementation of [ULID spec](https://git
- Added `LICENSE`
- Added test cases
[unreleased]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-4.2.1...HEAD
[unreleased]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-5.0.0...HEAD
[5.0.0]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-4.2.1...ulid-creator-5.0.0
[4.2.1]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-4.2.0...ulid-creator-4.2.1
[4.2.0]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-4.1.2...ulid-creator-4.2.0
[4.1.2]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-4.1.1...ulid-creator-4.1.2

View File

@ -39,7 +39,7 @@ Add these lines to your `pom.xml`.
<dependency>
<groupId>com.github.f4b6a3</groupId>
<artifactId>ulid-creator</artifactId>
<version>4.2.1</version>
<version>5.0.0</version>
</dependency>
```
See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator).
@ -198,12 +198,38 @@ Ulid ulid = factory.create();
---
A `UlidFactory` with `ThreadLocalRandom` inside of a `Supplier<byte[]>`:
A `UlidFactory` with `SplittableRandom`:
```java
// use a random function that returns a long value
SplittableRandom random = new SplittableRandom();
UlidFactory factory = UlidFactory.newInstance(() -> random.nextLong());
// use the factory
Ulid ulid = factory.create();
```
---
A `UlidFactory` with `RandomGenerator` (JDK 17+):
```java
// use a random function that returns a long value
RandomGenerator random = RandomGenerator.getDefault();
UlidFactory factory = UlidFactory.newInstance(() -> random.nextLong());
// use the factory
Ulid ulid = factory.create();
```
---
A `UlidFactory` with `ThreadLocalRandom`:
```java
// use a random supplier that returns an array of 10 bytes
UlidFactory factory = UlidFactory.newInstance(() -> {
final byte[] bytes = new byte[Ulid.RANDOM_BYTES];
UlidFactory factory = UlidFactory.newInstance((length) -> {
final byte[] bytes = new byte[length];
ThreadLocalRandom.current().nextBytes(bytes);
return bytes;
});

View File

@ -27,8 +27,9 @@ package com.github.f4b6a3.ulid;
import java.security.SecureRandom;
import java.time.Clock;
import java.util.Random;
import java.util.function.IntFunction;
import java.util.function.LongFunction;
import java.util.function.Supplier;
import java.util.function.LongSupplier;
/**
* Factory that generates ULIDs.
@ -47,7 +48,7 @@ public final class UlidFactory {
private final LongFunction<Ulid> ulidFunction;
public UlidFactory() {
this(new UlidFunction(getRandomSupplier(null)));
this(new UlidFunction());
}
private UlidFactory(LongFunction<Ulid> ulidFunction) {
@ -67,7 +68,7 @@ public final class UlidFactory {
* @return {@link UlidFactory}
*/
public static UlidFactory newInstance() {
return newInstance(getRandomSupplier(null));
return new UlidFactory(new UlidFunction());
}
/**
@ -77,19 +78,31 @@ public final class UlidFactory {
* @return {@link UlidFactory}
*/
public static UlidFactory newInstance(Random random) {
return newInstance(getRandomSupplier(random));
return new UlidFactory(new UlidFunction(random));
}
/**
* Returns a new factory.
*
* The given random supplier must return an array of 10 bytes.
* The given random function must return a long value.
*
* @param randomSupplier a random supplier that returns 10 bytes
* @param randomFunction a random function that returns a long value
* @return {@link UlidFactory}
*/
public static UlidFactory newInstance(Supplier<byte[]> randomSupplier) {
return new UlidFactory(new UlidFunction(randomSupplier));
public static UlidFactory newInstance(LongSupplier randomFunction) {
return new UlidFactory(new UlidFunction(randomFunction));
}
/**
* Returns a new factory.
*
* The given random function must return a byte array.
*
* @param randomFunction a random function that returns a byte array
* @return {@link UlidFactory}
*/
public static UlidFactory newInstance(IntFunction<byte[]> randomFunction) {
return new UlidFactory(new UlidFunction(randomFunction));
}
/**
@ -98,7 +111,7 @@ public final class UlidFactory {
* @return {@link UlidFactory}
*/
public static UlidFactory newMonotonicInstance() {
return newMonotonicInstance(getRandomSupplier(null));
return new UlidFactory(new MonotonicFunction());
}
/**
@ -108,32 +121,57 @@ public final class UlidFactory {
* @return {@link UlidFactory}
*/
public static UlidFactory newMonotonicInstance(Random random) {
return newMonotonicInstance(getRandomSupplier(random));
return new UlidFactory(new MonotonicFunction(random));
}
/**
* Returns a new monotonic factory.
*
* The given random supplier must return an array of 10 bytes.
* The given random function must return a long value.
*
* @param randomSupplier a random supplier that returns 10 bytes
* @param randomFunction a random function that returns a long value
* @return {@link UlidFactory}
*/
public static UlidFactory newMonotonicInstance(Supplier<byte[]> randomSupplier) {
return new UlidFactory(new MonotonicFunction(randomSupplier));
public static UlidFactory newMonotonicInstance(LongSupplier randomFunction) {
return new UlidFactory(new MonotonicFunction(randomFunction));
}
/**
* Returns a new monotonic factory.
*
* The given random supplier must return an array of 10 bytes.
* The given random function must return a byte array.
*
* @param randomSupplier a random supplier that returns 10 bytes
* @param randomFunction a random function that returns a byte array
* @return {@link UlidFactory}
*/
public static UlidFactory newMonotonicInstance(IntFunction<byte[]> randomFunction) {
return new UlidFactory(new MonotonicFunction(randomFunction));
}
/**
* Returns a new monotonic factory.
*
* The given random function must return a long value.
*
* @param randomFunction a random function that returns a long value
* @param clock a custom clock instance for tests
* @return {@link UlidFactory}
*/
protected static UlidFactory newMonotonicInstance(Supplier<byte[]> randomSupplier, Clock clock) {
return new UlidFactory(new MonotonicFunction(randomSupplier), clock);
protected static UlidFactory newMonotonicInstance(LongSupplier randomFunction, Clock clock) {
return new UlidFactory(new MonotonicFunction(randomFunction), clock);
}
/**
* Returns a new monotonic factory.
*
* The given random function must return a byte array.
*
* @param randomFunction a random function that returns a byte array
* @param clock a custom clock instance for tests
* @return {@link UlidFactory}
*/
protected static UlidFactory newMonotonicInstance(IntFunction<byte[]> randomFunction, Clock clock) {
return new UlidFactory(new MonotonicFunction(randomFunction), clock);
}
/**
@ -141,8 +179,8 @@ public final class UlidFactory {
*
* @return a ULID
*/
public Ulid create() {
return create(clock.millis());
public synchronized Ulid create() {
return this.ulidFunction.apply(clock.millis());
}
/**
@ -153,7 +191,7 @@ public final class UlidFactory {
* @param time a given time
* @return a ULID
*/
public Ulid create(final long time) {
public synchronized Ulid create(final long time) {
return this.ulidFunction.apply(time);
}
@ -162,16 +200,33 @@ public final class UlidFactory {
*/
protected static final class UlidFunction implements LongFunction<Ulid> {
// it must return an array of 10 bytes
private Supplier<byte[]> randomSupplier;
private final IRandom random;
public UlidFunction(Supplier<byte[]> randomSupplier) {
this.randomSupplier = randomSupplier;
public UlidFunction() {
this.random = new ByteRandom();
}
public UlidFunction(Random random) {
this.random = IRandom.newInstance(random);
}
public UlidFunction(IntFunction<byte[]> randomFunction) {
this.random = new ByteRandom(randomFunction);
}
public UlidFunction(LongSupplier randomFunction) {
this.random = new LongRandom(randomFunction);
}
@Override
public Ulid apply(final long time) {
return new Ulid(time, this.randomSupplier.get());
if (this.random instanceof ByteRandom) {
return new Ulid(time, this.random.nextBytes(Ulid.RANDOM_BYTES));
} else {
final long msb = (time << 16) | (this.random.nextLong() & 0xffffL);
final long lsb = this.random.nextLong();
return new Ulid(msb, lsb);
}
}
}
@ -183,20 +238,35 @@ public final class UlidFactory {
private long lastTime;
private Ulid lastUlid;
private final IRandom random;
// Used to preserve monotonicity when the system clock is
// adjusted by NTP after a small clock drift or when the
// system clock jumps back by 1 second due to leap second.
protected static final int CLOCK_DRIFT_TOLERANCE = 10_000;
// it must return an array of 10 bytes
private Supplier<byte[]> randomSupplier;
public MonotonicFunction() {
this(new ByteRandom());
}
public MonotonicFunction(Supplier<byte[]> randomSupplier) {
this.randomSupplier = randomSupplier;
public MonotonicFunction(Random random) {
this(IRandom.newInstance(random));
}
public MonotonicFunction(IntFunction<byte[]> randomFunction) {
this(new ByteRandom(randomFunction));
}
public MonotonicFunction(LongSupplier randomFunction) {
this(new LongRandom(randomFunction));
}
private MonotonicFunction(IRandom random) {
this.random = random;
// initialize internal state
this.lastTime = Clock.systemUTC().millis();
this.lastUlid = new Ulid(lastTime, randomSupplier.get());
this.lastUlid = new Ulid(lastTime, random.nextBytes(Ulid.RANDOM_BYTES));
}
@Override
@ -209,26 +279,122 @@ public final class UlidFactory {
this.lastUlid = lastUlid.increment();
} else {
this.lastTime = time;
this.lastUlid = new Ulid(time, this.randomSupplier.get());
if (this.random instanceof ByteRandom) {
this.lastUlid = new Ulid(time, this.random.nextBytes(Ulid.RANDOM_BYTES));
} else {
final long msb = (time << 16) | (this.random.nextLong() & 0xffffL);
final long lsb = this.random.nextLong();
this.lastUlid = new Ulid(msb, lsb);
}
}
return new Ulid(this.lastUlid);
}
}
/**
* It instantiates a supplier that returns an array of 10 bytes.
*
* @param random a {@link Random} generator
* @return a random supplier that returns 10 bytes
*/
protected static Supplier<byte[]> getRandomSupplier(Random random) {
Random entropy = random != null ? random : new SecureRandom();
return () -> {
byte[] payload = new byte[Ulid.RANDOM_BYTES];
entropy.nextBytes(payload);
return payload;
};
protected static interface IRandom {
public long nextLong();
public byte[] nextBytes(int length);
static IRandom newInstance(Random random) {
if (random == null) {
return new ByteRandom();
} else {
if (random instanceof SecureRandom) {
return new ByteRandom(random);
} else {
return new LongRandom(random);
}
}
}
}
protected static class LongRandom implements IRandom {
private final LongSupplier randomFunction;
public LongRandom() {
this(newRandomFunction(null));
}
public LongRandom(Random random) {
this(newRandomFunction(random));
}
public LongRandom(LongSupplier randomFunction) {
this.randomFunction = randomFunction != null ? randomFunction : newRandomFunction(null);
}
@Override
public long nextLong() {
return randomFunction.getAsLong();
}
@Override
public byte[] nextBytes(int length) {
int shift = 0;
long random = 0;
final byte[] bytes = new byte[length];
for (int i = 0; i < length; i++) {
if (shift < Byte.SIZE) {
shift = Long.SIZE;
random = randomFunction.getAsLong();
}
shift -= Byte.SIZE; // 56, 48, 42...
bytes[i] = (byte) (random >>> shift);
}
return bytes;
}
protected static LongSupplier newRandomFunction(Random random) {
final Random entropy = random != null ? random : new SecureRandom();
return entropy::nextLong;
}
}
protected static class ByteRandom implements IRandom {
private final IntFunction<byte[]> randomFunction;
public ByteRandom() {
this(newRandomFunction(null));
}
public ByteRandom(Random random) {
this(newRandomFunction(random));
}
public ByteRandom(IntFunction<byte[]> randomFunction) {
this.randomFunction = randomFunction != null ? randomFunction : newRandomFunction(null);
}
@Override
public long nextLong() {
long number = 0;
byte[] bytes = this.randomFunction.apply(Long.BYTES);
for (int i = 0; i < Long.BYTES; i++) {
number = (number << 8) | (bytes[i] & 0xff);
}
return number;
}
@Override
public byte[] nextBytes(int length) {
return this.randomFunction.apply(length);
}
protected static IntFunction<byte[]> newRandomFunction(Random random) {
final Random entropy = random != null ? random : new SecureRandom();
return (final int length) -> {
final byte[] bytes = new byte[length];
entropy.nextBytes(bytes);
return bytes;
};
}
}
}

View File

@ -2,13 +2,16 @@ package com.github.f4b6a3.ulid;
import org.junit.Test;
import com.github.f4b6a3.ulid.Ulid;
import com.github.f4b6a3.ulid.UlidCreator;
import com.github.f4b6a3.ulid.UlidFactory;
import static org.junit.Assert.*;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Random;
import java.util.SplittableRandom;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.IntFunction;
import java.util.function.LongSupplier;
public class UlidFactoryDefaultfTest extends UlidFactoryTest {
@ -59,4 +62,188 @@ public class UlidFactoryDefaultfTest extends UlidFactoryTest {
assertEquals(time, ulid.getTime());
}
}
@Test
public void testDefault() {
UlidFactory factory = new UlidFactory();
assertNotNull(factory.create());
}
@Test
public void testWithRandom() {
{
Random random = new Random();
UlidFactory factory = UlidFactory.newInstance(random);
assertNotNull(factory.create());
}
{
SecureRandom random = new SecureRandom();
UlidFactory factory = UlidFactory.newInstance(random);
assertNotNull(factory.create());
}
}
@Test
public void testWithRandomNull() {
UlidFactory factory = UlidFactory.newInstance((Random) null);
assertNotNull(factory.create());
}
@Test
public void testWithRandomFunction() {
{
SplittableRandom random = new SplittableRandom();
LongSupplier function = () -> random.nextLong();
UlidFactory factory = UlidFactory.newInstance(function);
assertNotNull(factory.create());
}
{
IntFunction<byte[]> function = (length) -> {
byte[] bytes = new byte[length];
ThreadLocalRandom.current().nextBytes(bytes);
return bytes;
};
UlidFactory factory = UlidFactory.newInstance(function);
assertNotNull(factory.create());
}
}
@Test
public void testWithRandomFunctionNull() {
{
UlidFactory factory = UlidFactory.newInstance((LongSupplier) null);
assertNotNull(factory.create());
}
{
UlidFactory factory = UlidFactory.newInstance((IntFunction<byte[]>) null);
assertNotNull(factory.create());
}
}
@Test
public void testByteRandomNextLong() {
for (int i = 0; i < 10; i++) {
byte[] bytes = new byte[Long.BYTES];
(new Random()).nextBytes(bytes);
long number = ByteBuffer.wrap(bytes).getLong();
UlidFactory.IRandom random = new UlidFactory.ByteRandom((x) -> bytes);
assertEquals(number, random.nextLong());
}
for (int i = 0; i < 10; i++) {
int longs = 10;
int size = Long.BYTES * longs;
byte[] bytes = new byte[size];
(new Random()).nextBytes(bytes);
ByteBuffer buffer1 = ByteBuffer.wrap(bytes);
ByteBuffer buffer2 = ByteBuffer.wrap(bytes);
UlidFactory.IRandom random = new UlidFactory.ByteRandom((x) -> {
byte[] octects = new byte[x];
buffer1.get(octects);
return octects;
});
for (int j = 0; j < longs; j++) {
assertEquals(buffer2.getLong(), random.nextLong());
}
}
}
@Test
public void testByteRandomNextBytes() {
for (int i = 0; i < 10; i++) {
byte[] bytes = new byte[Long.BYTES];
(new Random()).nextBytes(bytes);
UlidFactory.IRandom random = new UlidFactory.ByteRandom((x) -> bytes);
assertEquals(Arrays.toString(bytes), Arrays.toString(random.nextBytes(Long.BYTES)));
}
for (int i = 0; i < 10; i++) {
int ints = 10;
int size = Long.BYTES * ints;
byte[] bytes = new byte[size];
(new Random()).nextBytes(bytes);
ByteBuffer buffer1 = ByteBuffer.wrap(bytes);
ByteBuffer buffer2 = ByteBuffer.wrap(bytes);
UlidFactory.IRandom random = new UlidFactory.ByteRandom((x) -> {
byte[] octects = new byte[x];
buffer1.get(octects);
return octects;
});
for (int j = 0; j < ints; j++) {
byte[] octects = new byte[Long.BYTES];
buffer2.get(octects);
assertEquals(Arrays.toString(octects), Arrays.toString(random.nextBytes(Long.BYTES)));
}
}
}
@Test
public void testLogRandomNextLong() {
for (int i = 0; i < 10; i++) {
byte[] bytes = new byte[Long.BYTES];
(new Random()).nextBytes(bytes);
long number = ByteBuffer.wrap(bytes).getLong();
UlidFactory.IRandom random = new UlidFactory.LongRandom(() -> number);
assertEquals(number, random.nextLong());
}
for (int i = 0; i < 10; i++) {
int ints = 10;
int size = Long.BYTES * ints;
byte[] bytes = new byte[size];
(new Random()).nextBytes(bytes);
ByteBuffer buffer1 = ByteBuffer.wrap(bytes);
ByteBuffer buffer2 = ByteBuffer.wrap(bytes);
UlidFactory.IRandom random = new UlidFactory.LongRandom(() -> buffer1.getLong());
for (int j = 0; j < ints; j++) {
assertEquals(buffer2.getLong(), random.nextLong());
}
}
}
@Test
public void testLogRandomNextBytes() {
for (int i = 0; i < 10; i++) {
byte[] bytes = new byte[Long.BYTES];
(new Random()).nextBytes(bytes);
long number = ByteBuffer.wrap(bytes).getLong();
UlidFactory.IRandom random = new UlidFactory.LongRandom(() -> number);
assertEquals(Arrays.toString(bytes), Arrays.toString(random.nextBytes(Long.BYTES)));
}
for (int i = 0; i < 10; i++) {
int ints = 10;
int size = Long.BYTES * ints;
byte[] bytes = new byte[size];
(new Random()).nextBytes(bytes);
ByteBuffer buffer1 = ByteBuffer.wrap(bytes);
ByteBuffer buffer2 = ByteBuffer.wrap(bytes);
UlidFactory.IRandom random = new UlidFactory.LongRandom(() -> buffer1.getLong());
for (int j = 0; j < ints; j++) {
byte[] octects = new byte[Long.BYTES];
buffer2.get(octects);
assertEquals(Arrays.toString(octects), Arrays.toString(random.nextBytes(Long.BYTES)));
}
}
}
}

View File

@ -2,10 +2,6 @@ package com.github.f4b6a3.ulid;
import org.junit.Test;
import com.github.f4b6a3.ulid.Ulid;
import com.github.f4b6a3.ulid.UlidCreator;
import com.github.f4b6a3.ulid.UlidFactory;
import static org.junit.Assert.*;
import java.time.Clock;
@ -13,7 +9,10 @@ import java.time.Instant;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.Random;
import java.util.function.Supplier;
import java.util.SplittableRandom;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.IntFunction;
import java.util.function.LongSupplier;
public class UlidFactoryMonotonicTest extends UlidFactoryTest {
@ -66,7 +65,7 @@ public class UlidFactoryMonotonicTest extends UlidFactoryTest {
}
};
Supplier<byte[]> randomSupplier = UlidFactory.getRandomSupplier(new Random());
IntFunction<byte[]> randomSupplier = UlidFactory.ByteRandom.newRandomFunction(new Random());
UlidFactory factory = UlidFactory.newMonotonicInstance(randomSupplier, clock);
long ms1 = factory.create().getTime(); // time
@ -115,7 +114,7 @@ public class UlidFactoryMonotonicTest extends UlidFactoryTest {
}
};
Supplier<byte[]> randomSupplier = UlidFactory.getRandomSupplier(new Random());
IntFunction<byte[]> randomSupplier = UlidFactory.ByteRandom.newRandomFunction(new Random());
UlidFactory factory = UlidFactory.newMonotonicInstance(randomSupplier, clock);
long ms1 = factory.create().getTime(); // second
@ -163,4 +162,63 @@ public class UlidFactoryMonotonicTest extends UlidFactoryTest {
assertEquals(time, ulid.getTime());
}
}
@Test
public void testWithRandom() {
Random random = new Random();
UlidFactory factory = UlidFactory.newMonotonicInstance(random);
assertNotNull(factory.create());
}
@Test
public void testWithRandomNull() {
UlidFactory factory = UlidFactory.newMonotonicInstance((Random) null);
assertNotNull(factory.create());
}
@Test
public void testWithRandomFunction() {
{
SplittableRandom random = new SplittableRandom();
LongSupplier function = () -> random.nextLong();
UlidFactory factory = UlidFactory.newMonotonicInstance(function);
assertNotNull(factory.create());
}
{
SplittableRandom random = new SplittableRandom();
LongSupplier function = () -> random.nextLong();
UlidFactory factory = UlidFactory.newMonotonicInstance(function, Clock.systemDefaultZone());
assertNotNull(factory.create());
}
{
IntFunction<byte[]> function = (length) -> {
byte[] bytes = new byte[length];
ThreadLocalRandom.current().nextBytes(bytes);
return bytes;
};
UlidFactory factory = UlidFactory.newMonotonicInstance(function);
assertNotNull(factory.create());
}
{
IntFunction<byte[]> function = (length) -> {
byte[] bytes = new byte[length];
ThreadLocalRandom.current().nextBytes(bytes);
return bytes;
};
UlidFactory factory = UlidFactory.newMonotonicInstance(function, Clock.systemDefaultZone());
assertNotNull(factory.create());
}
}
@Test
public void testWithRandomFunctionNull() {
{
UlidFactory factory = UlidFactory.newMonotonicInstance((LongSupplier) null);
assertNotNull(factory.create());
}
{
UlidFactory factory = UlidFactory.newMonotonicInstance((IntFunction<byte[]>) null);
assertNotNull(factory.create());
}
}
}

View File

@ -9,8 +9,6 @@ import java.util.Random;
import java.util.Set;
import java.util.UUID;
import com.github.f4b6a3.ulid.UlidFactory;
public abstract class UlidFactoryTest {
protected static final int DEFAULT_LOOP_MAX = 10_000;

View File

@ -15,8 +15,6 @@ import java.util.UUID;
import org.junit.Test;
import com.github.f4b6a3.ulid.Ulid;
public class UlidTest {
private static final int DEFAULT_LOOP_MAX = 1_000;

View File

@ -1,8 +1,12 @@
package com.github.f4b6a3.ulid;
package com.github.f4b6a3.ulid.uniq;
import java.util.HashSet;
import java.util.Random;
import com.github.f4b6a3.ulid.TestSuite;
import com.github.f4b6a3.ulid.Ulid;
import com.github.f4b6a3.ulid.UlidFactory;
/**
*
* This test starts many threads that keep requesting thousands of ULIDs to a