[chore] regular maintenance

This commit is contained in:
Fabio Lima 2023-07-23 14:10:34 -03:00
parent 9f84f74257
commit 7e7e3c496c
2 changed files with 347 additions and 342 deletions

View File

@ -221,8 +221,8 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
* bits of the random component are all set to ZERO. * bits of the random component are all set to ZERO.
* <p> * <p>
* For example, the minimum ULID for 2022-02-22 22:22:22.222 is * For example, the minimum ULID for 2022-02-22 22:22:22.222 is
* `{@code new Ulid(0x018781ebb25e0000L, 0x0000000000000000L)}`, where * `{@code new Ulid(0x017f2387460e0000L, 0x0000000000000000L)}`, where
* `{@code 0x018781ebb25e}` is the timestamp in hexadecimal. * `{@code 0x017f2387460e}` is the timestamp in hexadecimal.
* <p> * <p>
* It can be useful to find all records before or after a specific timestamp in * It can be useful to find all records before or after a specific timestamp in
* a table without a `{@code created_at}` field. * a table without a `{@code created_at}` field.
@ -242,8 +242,8 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
* bits or the random component are all set to ONE. * bits or the random component are all set to ONE.
* <p> * <p>
* For example, the maximum ULID for 2022-02-22 22:22:22.222 is * For example, the maximum ULID for 2022-02-22 22:22:22.222 is
* `{@code new Ulid(0x018781ebb25effffL, 0xffffffffffffffffL)}`, where * `{@code new Ulid(0x017f2387460effffL, 0xffffffffffffffffL)}`, where
* `{@code 0x018781ebb25e}` is the timestamp in hexadecimal. * `{@code 0x017f2387460e}` is the timestamp in hexadecimal.
* <p> * <p>
* It can be useful to find all records before or after a specific timestamp in * It can be useful to find all records before or after a specific timestamp in
* a table without a `{@code created_at}` field. * a table without a `{@code created_at}` field.

View File

@ -1,18 +1,18 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2020-2023 Fabio Lima * Copyright (c) 2020-2023 Fabio Lima
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights * in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is * copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions: * furnished to do so, subject to the following conditions:
* *
* The above copyright notice and this permission notice shall be included in all * The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software. * copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -49,401 +49,406 @@ import java.util.function.LongSupplier;
*/ */
public final class UlidFactory { public final class UlidFactory {
private final LongSupplier timeMillisNow; // 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, (LongSupplier) null); this(ulidFunction, (LongSupplier) null);
} }
private UlidFactory(LongFunction<Ulid> ulidFunction, Clock clock) { private UlidFactory(LongFunction<Ulid> ulidFunction, Clock clock) {
this(ulidFunction, clock != null ? clock::millis : null); this(ulidFunction, clock != null ? clock::millis : null);
} }
private UlidFactory(LongFunction<Ulid> ulidFunction, LongSupplier timeMillisNow) { private UlidFactory(LongFunction<Ulid> ulidFunction, LongSupplier timeMillisNow) {
this.ulidFunction = ulidFunction; this.ulidFunction = ulidFunction;
this.timeMillisNow = timeMillisNow != null ? timeMillisNow : Clock.systemUTC()::millis; this.timeMillisNow = timeMillisNow != null ? timeMillisNow : Clock.systemUTC()::millis;
} }
/** /**
* Returns a new factory. * Returns a new factory.
* <p> * <p>
* It is equivalent to {@code new UlidFactory()}. * It is equivalent to {@code new UlidFactory()}.
* *
* @return {@link UlidFactory} * @return {@link UlidFactory}
*/ */
public static UlidFactory newInstance() { public static UlidFactory newInstance() {
return new UlidFactory(new UlidFunction(IRandom.newInstance())); return new UlidFactory(new UlidFunction(IRandom.newInstance()));
} }
/** /**
* Returns a new factory. * Returns a new factory.
* *
* @param random a {@link Random} generator * @param random a {@link Random} generator
* @return {@link UlidFactory} * @return {@link UlidFactory}
*/ */
public static UlidFactory newInstance(Random random) { public static UlidFactory newInstance(Random random) {
return new UlidFactory(new UlidFunction(IRandom.newInstance(random))); return new UlidFactory(new UlidFunction(IRandom.newInstance(random)));
} }
/** /**
* Returns a new factory. * Returns a new 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
* @return {@link UlidFactory} * @return {@link UlidFactory}
*/ */
public static UlidFactory newInstance(LongSupplier 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 factory. * Returns a new 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
* @return {@link UlidFactory} * @return {@link UlidFactory}
*/ */
public static UlidFactory newInstance(IntFunction<byte[]> randomFunction) { public static UlidFactory newInstance(IntFunction<byte[]> 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 monotonic factory.
* *
* @return {@link UlidFactory} * @return {@link UlidFactory}
*/ */
public static UlidFactory newMonotonicInstance() { public static UlidFactory newMonotonicInstance() {
return new UlidFactory(new MonotonicFunction(IRandom.newInstance())); return new UlidFactory(new MonotonicFunction(IRandom.newInstance()));
} }
/** /**
* Returns a new monotonic factory. * Returns a new monotonic factory.
* *
* @param random a {@link Random} generator * @param random a {@link Random} generator
* @return {@link UlidFactory} * @return {@link UlidFactory}
*/ */
public static UlidFactory newMonotonicInstance(Random random) { public static UlidFactory newMonotonicInstance(Random random) {
return new UlidFactory(new MonotonicFunction(IRandom.newInstance(random))); return new UlidFactory(new MonotonicFunction(IRandom.newInstance(random)));
} }
/** /**
* Returns a new monotonic factory. * Returns a new monotonic factory.
* *
* @param random a {@link Random} generator * @param random a {@link Random} generator
* @param timeMillisNow a function that returns the current time as milliseconds from epoch * @param timeMillisNow a function that returns the current time as milliseconds
* @return {@link UlidFactory} * from epoch
*/ * @return {@link UlidFactory}
public static UlidFactory newMonotonicInstance(Random random, LongSupplier timeMillisNow) { */
return new UlidFactory(new MonotonicFunction(IRandom.newInstance(random), timeMillisNow), timeMillisNow); public static UlidFactory newMonotonicInstance(Random random, LongSupplier timeMillisNow) {
} return new UlidFactory(new MonotonicFunction(IRandom.newInstance(random), timeMillisNow), timeMillisNow);
}
/** /**
* 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
* @return {@link UlidFactory} * @return {@link UlidFactory}
*/ */
public static UlidFactory newMonotonicInstance(LongSupplier randomFunction) { public static UlidFactory newMonotonicInstance(LongSupplier randomFunction) {
return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction))); 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 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
* @return {@link UlidFactory} * @return {@link UlidFactory}
*/ */
public static UlidFactory newMonotonicInstance(IntFunction<byte[]> randomFunction) { public static UlidFactory newMonotonicInstance(IntFunction<byte[]> randomFunction) {
return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction))); 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 timeMillisNow a function that returns the current time as milliseconds from epoch * @param timeMillisNow a function that returns the current time as
* @return {@link UlidFactory} * milliseconds from epoch
*/ * @return {@link UlidFactory}
public static UlidFactory newMonotonicInstance(LongSupplier randomFunction, LongSupplier timeMillisNow) { */
return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction), timeMillisNow), timeMillisNow); public static UlidFactory newMonotonicInstance(LongSupplier randomFunction, LongSupplier timeMillisNow) {
} return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction), timeMillisNow),
timeMillisNow);
}
/** /**
* 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 * @param clock a custom clock instance for tests
* @return {@link UlidFactory} * @return {@link UlidFactory}
*/ */
static UlidFactory newMonotonicInstance(LongSupplier randomFunction, Clock clock) { static UlidFactory newMonotonicInstance(LongSupplier randomFunction, Clock clock) {
return UlidFactory.newMonotonicInstance(randomFunction, clock::millis); return UlidFactory.newMonotonicInstance(randomFunction, clock::millis);
} }
/** /**
* 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 timeMillisNow a function that returns the current time as milliseconds from epoch * @param timeMillisNow a function that returns the current time as
* @return {@link UlidFactory} * milliseconds from epoch
*/ * @return {@link UlidFactory}
public static UlidFactory newMonotonicInstance(IntFunction<byte[]> randomFunction, LongSupplier timeMillisNow) { */
return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction), timeMillisNow), timeMillisNow); public static UlidFactory newMonotonicInstance(IntFunction<byte[]> randomFunction, LongSupplier timeMillisNow) {
} return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction), timeMillisNow),
timeMillisNow);
}
/** /**
* 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 * @param clock a custom clock instance for tests
* @return {@link UlidFactory} * @return {@link UlidFactory}
*/ */
static UlidFactory newMonotonicInstance(IntFunction<byte[]> randomFunction, Clock clock) { static UlidFactory newMonotonicInstance(IntFunction<byte[]> randomFunction, Clock clock) {
return UlidFactory.newMonotonicInstance(randomFunction, clock::millis); return UlidFactory.newMonotonicInstance(randomFunction, clock::millis);
} }
// ****************************** // ******************************
// Public methods // Public methods
// ****************************** // ******************************
/** /**
* Returns a UUID. * Returns a UUID.
* *
* @return a ULID * @return a ULID
*/ */
public synchronized Ulid create() { public synchronized Ulid create() {
return this.ulidFunction.apply(timeMillisNow.getAsLong()); return this.ulidFunction.apply(timeMillisNow.getAsLong());
} }
/** /**
* Returns a UUID with a specific time. * Returns a UUID with a specific time.
* *
* @param time a number of milliseconds since 1970-01-01 (Unix epoch). * @param time a number of milliseconds since 1970-01-01 (Unix epoch).
* @return a ULID * @return a ULID
*/ */
public synchronized Ulid create(final long time) { public synchronized Ulid create(final long time) {
return this.ulidFunction.apply(time); return this.ulidFunction.apply(time);
} }
// ****************************** // ******************************
// Package-private inner classes // Package-private inner classes
// ****************************** // ******************************
/** /**
* Function that creates ULIDs. * Function that creates ULIDs.
*/ */
static final class UlidFunction implements LongFunction<Ulid> { static final class UlidFunction implements LongFunction<Ulid> {
private final IRandom random; private final IRandom random;
public UlidFunction(IRandom random) { public UlidFunction(IRandom random) {
this.random = random; this.random = random;
} }
@Override @Override
public Ulid apply(final long time) { public Ulid apply(final long time) {
if (this.random instanceof ByteRandom) { if (this.random instanceof ByteRandom) {
return new Ulid(time, this.random.nextBytes(Ulid.RANDOM_BYTES)); return new Ulid(time, this.random.nextBytes(Ulid.RANDOM_BYTES));
} else { } else {
final long msb = (time << 16) | (this.random.nextLong() & 0xffffL); final long msb = (time << 16) | (this.random.nextLong() & 0xffffL);
final long lsb = this.random.nextLong(); final long lsb = this.random.nextLong();
return new Ulid(msb, lsb); return new Ulid(msb, lsb);
} }
} }
} }
/** /**
* Function that creates Monotonic ULIDs. * Function that creates Monotonic ULIDs.
*/ */
static final class MonotonicFunction implements LongFunction<Ulid> { static final class MonotonicFunction implements LongFunction<Ulid> {
private Ulid lastUlid; private Ulid lastUlid;
private final IRandom random; private final IRandom random;
// Used to preserve monotonicity when the system clock is // Used to preserve monotonicity when the system clock is
// adjusted by NTP after a small clock drift or when the // adjusted by NTP after a small clock drift or when the
// system clock jumps back by 1 second due to leap second. // system clock jumps back by 1 second due to leap second.
protected static final int CLOCK_DRIFT_TOLERANCE = 10_000; protected static final int CLOCK_DRIFT_TOLERANCE = 10_000;
public MonotonicFunction(IRandom random) { public MonotonicFunction(IRandom random) {
this(random, Clock.systemUTC()); this(random, Clock.systemUTC());
} }
public MonotonicFunction(IRandom random, Clock clock) { public MonotonicFunction(IRandom random, Clock clock) {
this(random, clock::millis); this(random, clock::millis);
} }
public MonotonicFunction(IRandom random, LongSupplier timeMillisNow) { public MonotonicFunction(IRandom random, LongSupplier timeMillisNow) {
this.random = random; this.random = random;
// initialize internal state // initialize internal state
this.lastUlid = new Ulid(timeMillisNow.getAsLong(), this.random.nextBytes(Ulid.RANDOM_BYTES)); this.lastUlid = new Ulid(timeMillisNow.getAsLong(), this.random.nextBytes(Ulid.RANDOM_BYTES));
} }
@Override @Override
public synchronized Ulid apply(final long time) { public synchronized Ulid apply(final long time) {
final long lastTime = lastUlid.getTime(); final long lastTime = lastUlid.getTime();
// Check if the current time is the same as the previous time or has moved // Check if the current time is the same as the previous time or has moved
// backwards after a small system clock adjustment or after a leap second. // backwards after a small system clock adjustment or after a leap second.
// Drift tolerance = (previous_time - 10s) < current_time <= previous_time // Drift tolerance = (previous_time - 10s) < current_time <= previous_time
if ((time > lastTime - CLOCK_DRIFT_TOLERANCE) && (time <= lastTime)) { if ((time > lastTime - CLOCK_DRIFT_TOLERANCE) && (time <= lastTime)) {
this.lastUlid = this.lastUlid.increment(); this.lastUlid = this.lastUlid.increment();
} else { } else {
if (this.random instanceof ByteRandom) { if (this.random instanceof ByteRandom) {
this.lastUlid = new Ulid(time, this.random.nextBytes(Ulid.RANDOM_BYTES)); this.lastUlid = new Ulid(time, this.random.nextBytes(Ulid.RANDOM_BYTES));
} else { } else {
final long msb = (time << 16) | (this.random.nextLong() & 0xffffL); final long msb = (time << 16) | (this.random.nextLong() & 0xffffL);
final long lsb = this.random.nextLong(); final long lsb = this.random.nextLong();
this.lastUlid = new Ulid(msb, lsb); this.lastUlid = new Ulid(msb, lsb);
} }
} }
return new Ulid(this.lastUlid); return new Ulid(this.lastUlid);
} }
} }
static interface IRandom { static interface IRandom {
public long nextLong(); public long nextLong();
public byte[] nextBytes(int length); public byte[] nextBytes(int length);
static IRandom newInstance() { static IRandom newInstance() {
return new ByteRandom(); return new ByteRandom();
} }
static IRandom newInstance(Random random) { static IRandom newInstance(Random random) {
if (random == null) { if (random == null) {
return new ByteRandom(); return new ByteRandom();
} else { } else {
if (random instanceof SecureRandom) { if (random instanceof SecureRandom) {
return new ByteRandom(random); return new ByteRandom(random);
} else { } else {
return new LongRandom(random); return new LongRandom(random);
} }
} }
} }
static IRandom newInstance(LongSupplier randomFunction) { static IRandom newInstance(LongSupplier randomFunction) {
return new LongRandom(randomFunction); return new LongRandom(randomFunction);
} }
static IRandom newInstance(IntFunction<byte[]> randomFunction) { static IRandom newInstance(IntFunction<byte[]> randomFunction) {
return new ByteRandom(randomFunction); return new ByteRandom(randomFunction);
} }
} }
static class LongRandom implements IRandom { static class LongRandom implements IRandom {
private final LongSupplier randomFunction; private final LongSupplier randomFunction;
public LongRandom() { public LongRandom() {
this(newRandomFunction(null)); this(newRandomFunction(null));
} }
public LongRandom(Random random) { public LongRandom(Random random) {
this(newRandomFunction(random)); this(newRandomFunction(random));
} }
public LongRandom(LongSupplier randomFunction) { public LongRandom(LongSupplier randomFunction) {
this.randomFunction = randomFunction != null ? randomFunction : newRandomFunction(null); this.randomFunction = randomFunction != null ? randomFunction : newRandomFunction(null);
} }
@Override @Override
public long nextLong() { public long nextLong() {
return randomFunction.getAsLong(); return randomFunction.getAsLong();
} }
@Override @Override
public byte[] nextBytes(int length) { public byte[] nextBytes(int length) {
int shift = 0; int shift = 0;
long random = 0; long random = 0;
final byte[] bytes = new byte[length]; final byte[] bytes = new byte[length];
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
if (shift < Byte.SIZE) { if (shift < Byte.SIZE) {
shift = Long.SIZE; shift = Long.SIZE;
random = randomFunction.getAsLong(); random = randomFunction.getAsLong();
} }
shift -= Byte.SIZE; // 56, 48, 40... shift -= Byte.SIZE; // 56, 48, 40...
bytes[i] = (byte) (random >>> shift); bytes[i] = (byte) (random >>> shift);
} }
return bytes; return bytes;
} }
static LongSupplier newRandomFunction(Random random) { static LongSupplier newRandomFunction(Random random) {
final Random entropy = random != null ? random : new SecureRandom(); final Random entropy = random != null ? random : new SecureRandom();
return entropy::nextLong; return entropy::nextLong;
} }
} }
static class ByteRandom implements IRandom { static class ByteRandom implements IRandom {
private final IntFunction<byte[]> randomFunction; private final IntFunction<byte[]> randomFunction;
public ByteRandom() { public ByteRandom() {
this(newRandomFunction(null)); this(newRandomFunction(null));
} }
public ByteRandom(Random random) { public ByteRandom(Random random) {
this(newRandomFunction(random)); this(newRandomFunction(random));
} }
public ByteRandom(IntFunction<byte[]> randomFunction) { public ByteRandom(IntFunction<byte[]> randomFunction) {
this.randomFunction = randomFunction != null ? randomFunction : newRandomFunction(null); this.randomFunction = randomFunction != null ? randomFunction : newRandomFunction(null);
} }
@Override @Override
public long nextLong() { public long nextLong() {
long number = 0; long number = 0;
byte[] bytes = this.randomFunction.apply(Long.BYTES); byte[] bytes = this.randomFunction.apply(Long.BYTES);
for (int i = 0; i < Long.BYTES; i++) { for (int i = 0; i < Long.BYTES; i++) {
number = (number << 8) | (bytes[i] & 0xff); number = (number << 8) | (bytes[i] & 0xff);
} }
return number; return number;
} }
@Override @Override
public byte[] nextBytes(int length) { public byte[] nextBytes(int length) {
return this.randomFunction.apply(length); return this.randomFunction.apply(length);
} }
static IntFunction<byte[]> newRandomFunction(Random random) { static IntFunction<byte[]> newRandomFunction(Random random) {
final Random entropy = random != null ? random : new SecureRandom(); final Random entropy = random != null ? random : new SecureRandom();
return (final int length) -> { return (final int length) -> {
final byte[] bytes = new byte[length]; final byte[] bytes = new byte[length];
entropy.nextBytes(bytes); entropy.nextBytes(bytes);
return bytes; return bytes;
}; };
} }
} }
} }