Allow UlidFactory.java to accept a LongSupplier as a time source

Using a `java.time.Clock` to get the current time as milliseconds from epoch is inconvenient, as `java.time.Clock` is an abstract class requiring 3 methods to be implemented, which makes interoperability with other JVM languages e.g. Kotlin hard.

By exposing overloaded factory methods that accept a LongSupplier, this problem disappears without breaking backward compatibility.
This commit is contained in:
Michele Sollecito 2023-07-18 09:56:35 +01:00 committed by GitHub
parent a5006743ed
commit 3ecd6c84a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 343 additions and 298 deletions

View File

@ -49,356 +49,401 @@ import java.util.function.LongSupplier;
*/ */
public final class UlidFactory { public final class UlidFactory {
private final Clock clock; // for tests private final LongSupplier timeMillisNow; // for tests
private final LongFunction<Ulid> ulidFunction; private final LongFunction<Ulid> ulidFunction;
// ****************************** // ******************************
// Constructors // Constructors
// ****************************** // ******************************
/** /**
* Default constructor. * Default constructor.
*/ */
public UlidFactory() { public UlidFactory() {
this(new UlidFunction(IRandom.newInstance())); this(new UlidFunction(IRandom.newInstance()));
} }
private UlidFactory(LongFunction<Ulid> ulidFunction) { private UlidFactory(LongFunction<Ulid> ulidFunction) {
this(ulidFunction, null); this(ulidFunction, (LongSupplier) null);
} }
private UlidFactory(LongFunction<Ulid> ulidFunction, Clock clock) { private UlidFactory(LongFunction<Ulid> ulidFunction, Clock clock) {
this.ulidFunction = ulidFunction; this(ulidFunction, clock != null ? clock::millis : null);
this.clock = clock != null ? clock : Clock.systemUTC(); }
}
/** private UlidFactory(LongFunction<Ulid> ulidFunction, LongSupplier timeMillisNow) {
* Returns a new factory. this.ulidFunction = ulidFunction;
* <p> this.timeMillisNow = timeMillisNow != null ? timeMillisNow : Clock.systemUTC()::millis;
* It is equivalent to {@code new UlidFactory()}. }
*
* @return {@link UlidFactory}
*/
public static UlidFactory newInstance() {
return new UlidFactory(new UlidFunction(IRandom.newInstance()));
}
/** /**
* Returns a new factory. * Returns a new factory.
* * <p>
* @param random a {@link Random} generator * It is equivalent to {@code new UlidFactory()}.
* @return {@link UlidFactory} *
*/ * @return {@link UlidFactory}
public static UlidFactory newInstance(Random random) { */
return new UlidFactory(new UlidFunction(IRandom.newInstance(random))); public static UlidFactory newInstance() {
} return new UlidFactory(new UlidFunction(IRandom.newInstance()));
}
/** /**
* Returns a new factory. * Returns a new factory.
* <p> *
* The given random function must return a long value. * @param random a {@link Random} generator
* * @return {@link UlidFactory}
* @param randomFunction a random function that returns a long value */
* @return {@link UlidFactory} public static UlidFactory newInstance(Random random) {
*/ return new UlidFactory(new UlidFunction(IRandom.newInstance(random)));
public static UlidFactory newInstance(LongSupplier randomFunction) { }
return new UlidFactory(new UlidFunction(IRandom.newInstance(randomFunction)));
}
/** /**
* Returns a new factory. * Returns a new factory.
* <p> * <p>
* The given random function must return a byte array. * The given random function must return a long value.
* *
* @param randomFunction a random function that returns a byte array * @param randomFunction a random function that returns a long value
* @return {@link UlidFactory} * @return {@link UlidFactory}
*/ */
public static UlidFactory newInstance(IntFunction<byte[]> randomFunction) { public static UlidFactory newInstance(LongSupplier randomFunction) {
return new UlidFactory(new UlidFunction(IRandom.newInstance(randomFunction))); return new UlidFactory(new UlidFunction(IRandom.newInstance(randomFunction)));
} }
/** /**
* Returns a new monotonic factory. * Returns a new factory.
* * <p>
* @return {@link UlidFactory} * The given random function must return a byte array.
*/ *
public static UlidFactory newMonotonicInstance() { * @param randomFunction a random function that returns a byte array
return new UlidFactory(new MonotonicFunction(IRandom.newInstance())); * @return {@link UlidFactory}
} */
public static UlidFactory newInstance(IntFunction<byte[]> randomFunction) {
return new UlidFactory(new UlidFunction(IRandom.newInstance(randomFunction)));
}
/** /**
* Returns a new monotonic factory. * Returns a new monotonic factory.
* *
* @param random a {@link Random} generator * @return {@link UlidFactory}
* @return {@link UlidFactory} */
*/ public static UlidFactory newMonotonicInstance() {
public static UlidFactory newMonotonicInstance(Random random) { return new UlidFactory(new MonotonicFunction(IRandom.newInstance()));
return new UlidFactory(new MonotonicFunction(IRandom.newInstance(random))); }
}
/** /**
* Returns a new monotonic factory. * Returns a new monotonic factory.
* <p> *
* The given random function must return a long value. * @param random a {@link Random} generator
* * @return {@link UlidFactory}
* @param randomFunction a random function that returns a long value */
* @return {@link UlidFactory} public static UlidFactory newMonotonicInstance(Random random) {
*/ return new UlidFactory(new MonotonicFunction(IRandom.newInstance(random)));
public static UlidFactory newMonotonicInstance(LongSupplier randomFunction) { }
return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction)));
}
/** /**
* Returns a new monotonic factory. * Returns a new monotonic factory.
* <p> *
* The given random function must return a byte array. * @param random a {@link Random} generator
* * @param timeMillisNow a function that returns the current time as milliseconds from epoch
* @param randomFunction a random function that returns a byte array * @return {@link UlidFactory}
* @return {@link UlidFactory} */
*/ public static UlidFactory newMonotonicInstance(Random random, LongSupplier timeMillisNow) {
public static UlidFactory newMonotonicInstance(IntFunction<byte[]> randomFunction) { return new UlidFactory(new MonotonicFunction(IRandom.newInstance(random), timeMillisNow), timeMillisNow);
return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction))); }
}
/** /**
* Returns a new monotonic factory. * Returns a new monotonic factory.
* <p> * <p>
* The given random function must return a long value. * The given random function must return a long value.
* *
* @param randomFunction a random function that returns a long value * @param randomFunction a random function that returns a long value
* @param clock a custom clock instance for tests * @return {@link UlidFactory}
* @return {@link UlidFactory} */
*/ public static UlidFactory newMonotonicInstance(LongSupplier randomFunction) {
static UlidFactory newMonotonicInstance(LongSupplier randomFunction, Clock clock) { return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction)));
return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction), clock), clock); }
}
/** /**
* Returns a new monotonic factory. * Returns a new monotonic factory.
* <p> * <p>
* The given random function must return a byte array. * The given random function must return a byte array.
* *
* @param randomFunction a random function that returns a byte array * @param randomFunction a random function that returns a byte array
* @param clock a custom clock instance for tests * @return {@link UlidFactory}
* @return {@link UlidFactory} */
*/ public static UlidFactory newMonotonicInstance(IntFunction<byte[]> randomFunction) {
static UlidFactory newMonotonicInstance(IntFunction<byte[]> randomFunction, Clock clock) { return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction)));
return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction), clock), clock); }
}
// ****************************** /**
// Public methods * Returns a new monotonic factory.
// ****************************** * <p>
* The given random function must return a long value.
*
* @param randomFunction a random function that returns a long value
* @param timeMillisNow a function that returns the current time as milliseconds from epoch
* @return {@link UlidFactory}
*/
public static UlidFactory newMonotonicInstance(LongSupplier randomFunction, LongSupplier timeMillisNow) {
return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction), timeMillisNow), timeMillisNow);
}
/** /**
* Returns a UUID. * Returns a new monotonic factory.
* * <p>
* @return a ULID * The given random function must return a long value.
*/ *
public synchronized Ulid create() { * @param randomFunction a random function that returns a long value
return this.ulidFunction.apply(clock.millis()); * @param clock a custom clock instance for tests
} * @return {@link UlidFactory}
*/
static UlidFactory newMonotonicInstance(LongSupplier randomFunction, Clock clock) {
return UlidFactory.newMonotonicInstance(randomFunction, clock::millis);
}
/** /**
* Returns a UUID with a specific time. * Returns a new monotonic factory.
* * <p>
* @param time a number of milliseconds since 1970-01-01 (Unix epoch). * The given random function must return a byte array.
* @return a ULID *
*/ * @param randomFunction a random function that returns a byte array
public synchronized Ulid create(final long time) { * @param timeMillisNow a function that returns the current time as milliseconds from epoch
return this.ulidFunction.apply(time); * @return {@link UlidFactory}
} */
public static UlidFactory newMonotonicInstance(IntFunction<byte[]> randomFunction, LongSupplier timeMillisNow) {
return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction), timeMillisNow), timeMillisNow);
}
// ****************************** /**
// Package-private inner classes * Returns a new monotonic factory.
// ****************************** * <p>
* 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}
*/
static UlidFactory newMonotonicInstance(IntFunction<byte[]> randomFunction, Clock clock) {
return UlidFactory.newMonotonicInstance(randomFunction, clock::millis);
}
/** // ******************************
* Function that creates ULIDs. // Public methods
*/ // ******************************
static final class UlidFunction implements LongFunction<Ulid> {
private final IRandom random; /**
* Returns a UUID.
*
* @return a ULID
*/
public synchronized Ulid create() {
return this.ulidFunction.apply(timeMillisNow.getAsLong());
}
public UlidFunction(IRandom random) { /**
this.random = random; * Returns a UUID with a specific time.
} *
* @param time a number of milliseconds since 1970-01-01 (Unix epoch).
* @return a ULID
*/
public synchronized Ulid create(final long time) {
return this.ulidFunction.apply(time);
}
@Override // ******************************
public Ulid apply(final long time) { // Package-private inner classes
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);
}
}
}
/** /**
* Function that creates Monotonic ULIDs. * Function that creates ULIDs.
*/ */
static final class MonotonicFunction implements LongFunction<Ulid> { static final class UlidFunction implements LongFunction<Ulid> {
private Ulid lastUlid; private final IRandom random;
private final IRandom random; public UlidFunction(IRandom random) {
this.random = random;
}
// Used to preserve monotonicity when the system clock is @Override
// adjusted by NTP after a small clock drift or when the public Ulid apply(final long time) {
// system clock jumps back by 1 second due to leap second. if (this.random instanceof ByteRandom) {
protected static final int CLOCK_DRIFT_TOLERANCE = 10_000; 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);
}
}
}
public MonotonicFunction(IRandom random) { /**
this(random, Clock.systemUTC()); * Function that creates Monotonic ULIDs.
} */
static final class MonotonicFunction implements LongFunction<Ulid> {
public MonotonicFunction(IRandom random, Clock clock) { private Ulid lastUlid;
this.random = random;
// initialize internal state
this.lastUlid = new Ulid(clock.millis(), this.random.nextBytes(Ulid.RANDOM_BYTES));
}
@Override private final IRandom random;
public synchronized Ulid apply(final long time) {
final long lastTime = lastUlid.getTime(); // 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;
// Check if the current time is the same as the previous time or has moved public MonotonicFunction(IRandom random) {
// backwards after a small system clock adjustment or after a leap second. this(random, Clock.systemUTC());
// Drift tolerance = (previous_time - 10s) < current_time <= previous_time }
if ((time > lastTime - CLOCK_DRIFT_TOLERANCE) && (time <= lastTime)) {
this.lastUlid = this.lastUlid.increment();
} else {
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); public MonotonicFunction(IRandom random, Clock clock) {
} this(random, clock::millis);
} }
static interface IRandom { public MonotonicFunction(IRandom random, LongSupplier timeMillisNow) {
this.random = random;
// initialize internal state
this.lastUlid = new Ulid(timeMillisNow.getAsLong(), this.random.nextBytes(Ulid.RANDOM_BYTES));
}
public long nextLong(); @Override
public synchronized Ulid apply(final long time) {
public byte[] nextBytes(int length); final long lastTime = lastUlid.getTime();
static IRandom newInstance() { // Check if the current time is the same as the previous time or has moved
return new ByteRandom(); // backwards after a small system clock adjustment or after a leap second.
} // Drift tolerance = (previous_time - 10s) < current_time <= previous_time
if ((time > lastTime - CLOCK_DRIFT_TOLERANCE) && (time <= lastTime)) {
this.lastUlid = this.lastUlid.increment();
} else {
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);
}
}
static IRandom newInstance(Random random) { return new Ulid(this.lastUlid);
if (random == null) { }
return new ByteRandom(); }
} else {
if (random instanceof SecureRandom) {
return new ByteRandom(random);
} else {
return new LongRandom(random);
}
}
}
static IRandom newInstance(LongSupplier randomFunction) { static interface IRandom {
return new LongRandom(randomFunction);
}
static IRandom newInstance(IntFunction<byte[]> randomFunction) { public long nextLong();
return new ByteRandom(randomFunction);
}
}
static class LongRandom implements IRandom { public byte[] nextBytes(int length);
private final LongSupplier randomFunction; static IRandom newInstance() {
return new ByteRandom();
}
public LongRandom() { static IRandom newInstance(Random random) {
this(newRandomFunction(null)); if (random == null) {
} return new ByteRandom();
} else {
if (random instanceof SecureRandom) {
return new ByteRandom(random);
} else {
return new LongRandom(random);
}
}
}
public LongRandom(Random random) { static IRandom newInstance(LongSupplier randomFunction) {
this(newRandomFunction(random)); return new LongRandom(randomFunction);
} }
public LongRandom(LongSupplier randomFunction) { static IRandom newInstance(IntFunction<byte[]> randomFunction) {
this.randomFunction = randomFunction != null ? randomFunction : newRandomFunction(null); return new ByteRandom(randomFunction);
} }
}
@Override static class LongRandom implements IRandom {
public long nextLong() {
return randomFunction.getAsLong();
}
@Override private final LongSupplier randomFunction;
public byte[] nextBytes(int length) {
int shift = 0; public LongRandom() {
long random = 0; this(newRandomFunction(null));
final byte[] bytes = new byte[length]; }
for (int i = 0; i < length; i++) { public LongRandom(Random random) {
if (shift < Byte.SIZE) { this(newRandomFunction(random));
shift = Long.SIZE; }
random = randomFunction.getAsLong();
}
shift -= Byte.SIZE; // 56, 48, 40...
bytes[i] = (byte) (random >>> shift);
}
return bytes; public LongRandom(LongSupplier randomFunction) {
} this.randomFunction = randomFunction != null ? randomFunction : newRandomFunction(null);
}
static LongSupplier newRandomFunction(Random random) { @Override
final Random entropy = random != null ? random : new SecureRandom(); public long nextLong() {
return entropy::nextLong; return randomFunction.getAsLong();
} }
}
static class ByteRandom implements IRandom { @Override
public byte[] nextBytes(int length) {
private final IntFunction<byte[]> randomFunction; int shift = 0;
long random = 0;
final byte[] bytes = new byte[length];
public ByteRandom() { for (int i = 0; i < length; i++) {
this(newRandomFunction(null)); if (shift < Byte.SIZE) {
} shift = Long.SIZE;
random = randomFunction.getAsLong();
}
shift -= Byte.SIZE; // 56, 48, 40...
bytes[i] = (byte) (random >>> shift);
}
public ByteRandom(Random random) { return bytes;
this(newRandomFunction(random)); }
}
public ByteRandom(IntFunction<byte[]> randomFunction) { static LongSupplier newRandomFunction(Random random) {
this.randomFunction = randomFunction != null ? randomFunction : newRandomFunction(null); final Random entropy = random != null ? random : new SecureRandom();
} return entropy::nextLong;
}
}
@Override static class ByteRandom implements IRandom {
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 private final IntFunction<byte[]> randomFunction;
public byte[] nextBytes(int length) {
return this.randomFunction.apply(length);
}
static IntFunction<byte[]> newRandomFunction(Random random) { public ByteRandom() {
final Random entropy = random != null ? random : new SecureRandom(); this(newRandomFunction(null));
return (final int length) -> { }
final byte[] bytes = new byte[length];
entropy.nextBytes(bytes); public ByteRandom(Random random) {
return bytes; 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);
}
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;
};
}
}
} }