diff --git a/design/perfin-logo.svg b/design/perfin-logo.svg
index 0e953e2..a4d5e94 100644
--- a/design/perfin-logo.svg
+++ b/design/perfin-logo.svg
@@ -9,9 +9,9 @@
id="svg1"
inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)"
sodipodi:docname="perfin-logo.svg"
- inkscape:export-filename="../src/main/resources/images/perfin-logo_64.png"
- inkscape:export-xdpi="96"
- inkscape:export-ydpi="96"
+ inkscape:export-filename="perfin-logo_256.png"
+ inkscape:export-xdpi="384"
+ inkscape:export-ydpi="384"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
@@ -26,9 +26,9 @@
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
inkscape:document-units="px"
- inkscape:zoom="4"
- inkscape:cx="-0.99999999"
- inkscape:cy="38.25"
+ inkscape:zoom="8"
+ inkscape:cx="25.125"
+ inkscape:cy="33.375"
inkscape:window-width="1920"
inkscape:window-height="1025"
inkscape:window-x="1080"
diff --git a/design/perfin-logo_256.png b/design/perfin-logo_256.png
new file mode 100644
index 0000000..3a97420
Binary files /dev/null and b/design/perfin-logo_256.png differ
diff --git a/pom.xml b/pom.xml
index dc999e1..8f35203 100644
--- a/pom.xml
+++ b/pom.xml
@@ -49,11 +49,6 @@
+ * ULID is a 128-bit value that has two components: + *
+ * ULID has 128-bit compatibility with {@link UUID}. Like a UUID, a ULID can + * also be stored as a 16-byte array. + *
+ * Instances of this class are immutable.
+ *
+ * @see ULID Specification
+ */
+public final class Ulid implements Serializable, Comparable
+ * Useful to make copies of ULIDs.
+ *
+ * @param ulid a ULID
+ */
+ public Ulid(Ulid ulid) {
+ this.msb = ulid.msb;
+ this.lsb = ulid.lsb;
+ }
+
+ /**
+ * Creates a new ULID.
+ *
+ * If you want to make a copy of a {@link UUID}, use {@link Ulid#from(UUID)}
+ * instead.
+ *
+ * @param mostSignificantBits the first 8 bytes as a long value
+ * @param leastSignificantBits the last 8 bytes as a long value
+ */
+ public Ulid(long mostSignificantBits, long leastSignificantBits) {
+ this.msb = mostSignificantBits;
+ this.lsb = leastSignificantBits;
+ }
+
+ /**
+ * Creates a new ULID.
+ *
+ * The time parameter is the number of milliseconds since 1970-01-01, also known
+ * as Unix epoch. It must be a positive number not larger than 2^48-1.
+ *
+ * The random parameter must be an arbitrary array of 10 bytes.
+ *
+ * Note: ULIDs cannot be composed of dates before 1970-01-01, as their embedded
+ * timestamp is internally treated as an unsigned integer, i.e., it can only
+ * represent the set of natural numbers including zero, up to 2^48-1.
+ *
+ * @param time the number of milliseconds since 1970-01-01
+ * @param random an array of 10 bytes
+ * @throws IllegalArgumentException if time is negative or larger than 2^48-1
+ * @throws IllegalArgumentException if random is null or its length is not 10
+ */
+ public Ulid(long time, byte[] random) {
+
+ // The time component has 48 bits.
+ if ((time & 0xffff000000000000L) != 0) {
+ // ULID specification:
+ // "Any attempt to decode or encode a ULID larger than this (time > 2^48-1)
+ // should be rejected by all implementations, to prevent overflow bugs."
+ throw new IllegalArgumentException("Invalid time value"); // overflow or negative time!
+ }
+ // The random component has 80 bits (10 bytes).
+ if (random == null || random.length != RANDOM_BYTES) {
+ throw new IllegalArgumentException("Invalid random bytes"); // null or wrong length!
+ }
+
+ long long0 = 0;
+ long long1 = 0;
+
+ long0 |= time << 16;
+ long0 |= (long) (random[0x0] & 0xff) << 8;
+ long0 |= (long) (random[0x1] & 0xff);
+
+ long1 |= (long) (random[0x2] & 0xff) << 56;
+ long1 |= (long) (random[0x3] & 0xff) << 48;
+ long1 |= (long) (random[0x4] & 0xff) << 40;
+ long1 |= (long) (random[0x5] & 0xff) << 32;
+ long1 |= (long) (random[0x6] & 0xff) << 24;
+ long1 |= (long) (random[0x7] & 0xff) << 16;
+ long1 |= (long) (random[0x8] & 0xff) << 8;
+ long1 |= (long) (random[0x9] & 0xff);
+
+ this.msb = long0;
+ this.lsb = long1;
+ }
+
+ /**
+ * Returns a fast new ULID.
+ *
+ * This static method is a quick alternative to {@link UlidCreator#getUlid()}.
+ *
+ * It employs {@link ThreadLocalRandom} which works very well, although not
+ * cryptographically strong. It can be useful, for example, for logging.
+ *
+ * Security-sensitive applications that require a cryptographically secure
+ * pseudo-random generator should use {@link UlidCreator#getUlid()}.
+ *
+ * @return a ULID
+ * @see {@link ThreadLocalRandom}
+ * @since 5.1.0
+ */
+ public static Ulid fast() {
+ final long time = System.currentTimeMillis();
+ ThreadLocalRandom random = ThreadLocalRandom.current();
+ return new Ulid((time << 16) | (random.nextLong() & 0xffffL), random.nextLong());
+ }
+
+ /**
+ * Returns the minimum ULID for a given time.
+ *
+ * The 48 bits of the time component are filled with the given time and the 80
+ * bits of the random component are all set to ZERO.
+ *
+ * For example, the minimum ULID for 2022-02-22 22:22:22.222 is
+ * `{@code new Ulid(0x017f2387460e0000L, 0x0000000000000000L)}`, where
+ * `{@code 0x017f2387460e}` is the timestamp in hexadecimal.
+ *
+ * It can be useful to find all records before or after a specific timestamp in
+ * a table without a `{@code created_at}` field.
+ *
+ * @param time the number of milliseconds since 1970-01-01
+ * @return a ULID
+ * @since 5.2.0
+ */
+ public static Ulid min(long time) {
+ return new Ulid((time << 16) | 0x0000L, 0x0000000000000000L);
+ }
+
+ /**
+ * Returns the maximum ULID for a given time.
+ *
+ * The 48 bits of the time component are filled with the given time and the 80
+ * bits or the random component are all set to ONE.
+ *
+ * For example, the maximum ULID for 2022-02-22 22:22:22.222 is
+ * `{@code new Ulid(0x017f2387460effffL, 0xffffffffffffffffL)}`, where
+ * `{@code 0x017f2387460e}` is the timestamp in hexadecimal.
+ *
+ * It can be useful to find all records before or after a specific timestamp in
+ * a table without a `{@code created_at}` field.
+ *
+ * @param time the number of milliseconds since 1970-01-01
+ * @return a ULID
+ * @since 5.2.0
+ */
+ public static Ulid max(long time) {
+ return new Ulid((time << 16) | 0xffffL, 0xffffffffffffffffL);
+ }
+
+ /**
+ * Converts a UUID into a ULID.
+ *
+ * @param uuid a UUID
+ * @return a ULID
+ */
+ public static Ulid from(UUID uuid) {
+ return new Ulid(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits());
+ }
+
+ /**
+ * Converts a byte array into a ULID.
+ *
+ * @param bytes an array of 16 bytes
+ * @return a ULID
+ * @throws IllegalArgumentException if bytes are null or its length is not 16
+ */
+ public static Ulid from(byte[] bytes) {
+
+ if (bytes == null || bytes.length != ULID_BYTES) {
+ throw new IllegalArgumentException("Invalid ULID bytes"); // null or wrong length!
+ }
+
+ long msb = 0;
+ long lsb = 0;
+
+ msb |= (bytes[0x0] & 0xffL) << 56;
+ msb |= (bytes[0x1] & 0xffL) << 48;
+ msb |= (bytes[0x2] & 0xffL) << 40;
+ msb |= (bytes[0x3] & 0xffL) << 32;
+ msb |= (bytes[0x4] & 0xffL) << 24;
+ msb |= (bytes[0x5] & 0xffL) << 16;
+ msb |= (bytes[0x6] & 0xffL) << 8;
+ msb |= (bytes[0x7] & 0xffL);
+
+ lsb |= (bytes[0x8] & 0xffL) << 56;
+ lsb |= (bytes[0x9] & 0xffL) << 48;
+ lsb |= (bytes[0xa] & 0xffL) << 40;
+ lsb |= (bytes[0xb] & 0xffL) << 32;
+ lsb |= (bytes[0xc] & 0xffL) << 24;
+ lsb |= (bytes[0xd] & 0xffL) << 16;
+ lsb |= (bytes[0xe] & 0xffL) << 8;
+ lsb |= (bytes[0xf] & 0xffL);
+
+ return new Ulid(msb, lsb);
+ }
+
+ /**
+ * Converts a canonical string into a ULID.
+ *
+ * The input string must be 26 characters long and must contain only characters
+ * from Crockford's base 32 alphabet.
+ *
+ * The first character of the input string must be between 0 and 7.
+ *
+ * @param string a canonical string
+ * @return a ULID
+ * @throws IllegalArgumentException if the input string is invalid
+ * @see Crockford's Base 32
+ */
+ public static Ulid from(String string) {
+
+ final char[] chars = toCharArray(string);
+
+ long time = 0;
+ long random0 = 0;
+ long random1 = 0;
+
+ time |= (long) ALPHABET_VALUES[chars[0x00]] << 45;
+ time |= (long) ALPHABET_VALUES[chars[0x01]] << 40;
+ time |= (long) ALPHABET_VALUES[chars[0x02]] << 35;
+ time |= (long) ALPHABET_VALUES[chars[0x03]] << 30;
+ time |= (long) ALPHABET_VALUES[chars[0x04]] << 25;
+ time |= (long) ALPHABET_VALUES[chars[0x05]] << 20;
+ time |= (long) ALPHABET_VALUES[chars[0x06]] << 15;
+ time |= (long) ALPHABET_VALUES[chars[0x07]] << 10;
+ time |= (long) ALPHABET_VALUES[chars[0x08]] << 5;
+ time |= (long) ALPHABET_VALUES[chars[0x09]];
+
+ random0 |= (long) ALPHABET_VALUES[chars[0x0a]] << 35;
+ random0 |= (long) ALPHABET_VALUES[chars[0x0b]] << 30;
+ random0 |= (long) ALPHABET_VALUES[chars[0x0c]] << 25;
+ random0 |= (long) ALPHABET_VALUES[chars[0x0d]] << 20;
+ random0 |= (long) ALPHABET_VALUES[chars[0x0e]] << 15;
+ random0 |= (long) ALPHABET_VALUES[chars[0x0f]] << 10;
+ random0 |= (long) ALPHABET_VALUES[chars[0x10]] << 5;
+ random0 |= (long) ALPHABET_VALUES[chars[0x11]];
+
+ random1 |= (long) ALPHABET_VALUES[chars[0x12]] << 35;
+ random1 |= (long) ALPHABET_VALUES[chars[0x13]] << 30;
+ random1 |= (long) ALPHABET_VALUES[chars[0x14]] << 25;
+ random1 |= (long) ALPHABET_VALUES[chars[0x15]] << 20;
+ random1 |= (long) ALPHABET_VALUES[chars[0x16]] << 15;
+ random1 |= (long) ALPHABET_VALUES[chars[0x17]] << 10;
+ random1 |= (long) ALPHABET_VALUES[chars[0x18]] << 5;
+ random1 |= (long) ALPHABET_VALUES[chars[0x19]];
+
+ final long msb = (time << 16) | (random0 >>> 24);
+ final long lsb = (random0 << 40) | (random1 & 0xffffffffffL);
+
+ return new Ulid(msb, lsb);
+ }
+
+ /**
+ * Convert the ULID into a UUID.
+ *
+ * A ULID has 128-bit compatibility with a {@link UUID}.
+ *
+ * If you need an RFC-4122 UUIDv4 do this: {@code Ulid.toRfc4122().toUuid()}.
+ *
+ * @return a UUID.
+ */
+ public UUID toUuid() {
+ return new UUID(this.msb, this.lsb);
+ }
+
+ /**
+ * Convert the ULID into a byte array.
+ *
+ * @return a byte array.
+ */
+ public byte[] toBytes() {
+
+ final byte[] bytes = new byte[ULID_BYTES];
+
+ bytes[0x0] = (byte) (msb >>> 56);
+ bytes[0x1] = (byte) (msb >>> 48);
+ bytes[0x2] = (byte) (msb >>> 40);
+ bytes[0x3] = (byte) (msb >>> 32);
+ bytes[0x4] = (byte) (msb >>> 24);
+ bytes[0x5] = (byte) (msb >>> 16);
+ bytes[0x6] = (byte) (msb >>> 8);
+ bytes[0x7] = (byte) (msb);
+
+ bytes[0x8] = (byte) (lsb >>> 56);
+ bytes[0x9] = (byte) (lsb >>> 48);
+ bytes[0xa] = (byte) (lsb >>> 40);
+ bytes[0xb] = (byte) (lsb >>> 32);
+ bytes[0xc] = (byte) (lsb >>> 24);
+ bytes[0xd] = (byte) (lsb >>> 16);
+ bytes[0xe] = (byte) (lsb >>> 8);
+ bytes[0xf] = (byte) (lsb);
+
+ return bytes;
+ }
+
+ /**
+ * Converts the ULID into a canonical string in upper case.
+ *
+ * The output string is 26 characters long and contains only characters from
+ * Crockford's Base 32 alphabet.
+ *
+ * For lower case string, use the shorthand {@code Ulid#toLowerCase()}, instead
+ * of {@code Ulid#toString()#toLowerCase()}.
+ *
+ * @return a ULID string
+ * @see Crockford's Base 32
+ */
+ @Override
+ public String toString() {
+ return toString(ALPHABET_UPPERCASE);
+ }
+
+ /**
+ * Converts the ULID into a canonical string in lower case.
+ *
+ * The output string is 26 characters long and contains only characters from
+ * Crockford's Base 32 alphabet.
+ *
+ * It is a shorthand at least twice as fast as
+ * {@code Ulid.toString().toLowerCase()}.
+ *
+ * @return a string
+ * @see Crockford's Base 32
+ */
+ public String toLowerCase() {
+ return toString(ALPHABET_LOWERCASE);
+ }
+
+ /**
+ * Converts the ULID into another ULID that is compatible with UUIDv4.
+ *
+ * The bytes of the returned ULID are compliant with the RFC-4122 version 4.
+ *
+ * If you need a RFC-4122 UUIDv4 do this: {@code Ulid.toRfc4122().toUuid()}.
+ *
+ * Note: If you use this method, you can not get the original ULID, since
+ * it changes 6 bits of it to generate a UUIDv4.
+ *
+ * @return a ULID
+ * @see RFC-4122
+ */
+ public Ulid toRfc4122() {
+
+ // set the 4 most significant bits of the 7th byte to 0, 1, 0 and 0
+ final long msb4 = (this.msb & 0xffffffffffff0fffL) | 0x0000000000004000L; // RFC-4122 version 4
+ // set the 2 most significant bits of the 9th byte to 1 and 0
+ final long lsb4 = (this.lsb & 0x3fffffffffffffffL) | 0x8000000000000000L; // RFC-4122 variant 2
+
+ return new Ulid(msb4, lsb4);
+ }
+
+ /**
+ * Returns the instant of creation.
+ *
+ * The instant of creation is extracted from the time component.
+ *
+ * @return the {@link Instant} of creation
+ */
+ public Instant getInstant() {
+ return Instant.ofEpochMilli(this.getTime());
+ }
+
+ /**
+ * Returns the instant of creation.
+ *
+ * The instant of creation is extracted from the time component.
+ *
+ * @param string a canonical string
+ * @return the {@link Instant} of creation
+ * @throws IllegalArgumentException if the input string is invalid
+ */
+ public static Instant getInstant(String string) {
+ return Instant.ofEpochMilli(getTime(string));
+ }
+
+ /**
+ * Returns the time component as a number.
+ *
+ * The time component is a number between 0 and 2^48-1. It is equivalent to the
+ * count of milliseconds since 1970-01-01 (Unix epoch).
+ *
+ * @return a number of milliseconds
+ */
+ public long getTime() {
+ return this.msb >>> 16;
+ }
+
+ /**
+ * Returns the time component as a number.
+ *
+ * The time component is a number between 0 and 2^48-1. It is equivalent to the
+ * count of milliseconds since 1970-01-01 (Unix epoch).
+ *
+ * @param string a canonical string
+ * @return a number of milliseconds
+ * @throws IllegalArgumentException if the input string is invalid
+ */
+ public static long getTime(String string) {
+
+ final char[] chars = toCharArray(string);
+
+ long time = 0;
+
+ time |= (long) ALPHABET_VALUES[chars[0x00]] << 45;
+ time |= (long) ALPHABET_VALUES[chars[0x01]] << 40;
+ time |= (long) ALPHABET_VALUES[chars[0x02]] << 35;
+ time |= (long) ALPHABET_VALUES[chars[0x03]] << 30;
+ time |= (long) ALPHABET_VALUES[chars[0x04]] << 25;
+ time |= (long) ALPHABET_VALUES[chars[0x05]] << 20;
+ time |= (long) ALPHABET_VALUES[chars[0x06]] << 15;
+ time |= (long) ALPHABET_VALUES[chars[0x07]] << 10;
+ time |= (long) ALPHABET_VALUES[chars[0x08]] << 5;
+ time |= (long) ALPHABET_VALUES[chars[0x09]];
+
+ return time;
+ }
+
+ /**
+ * Returns the random component as a byte array.
+ *
+ * The random component is an array of 10 bytes (80 bits).
+ *
+ * @return a byte array
+ */
+ public byte[] getRandom() {
+
+ final byte[] bytes = new byte[RANDOM_BYTES];
+
+ bytes[0x0] = (byte) (msb >>> 8);
+ bytes[0x1] = (byte) (msb);
+
+ bytes[0x2] = (byte) (lsb >>> 56);
+ bytes[0x3] = (byte) (lsb >>> 48);
+ bytes[0x4] = (byte) (lsb >>> 40);
+ bytes[0x5] = (byte) (lsb >>> 32);
+ bytes[0x6] = (byte) (lsb >>> 24);
+ bytes[0x7] = (byte) (lsb >>> 16);
+ bytes[0x8] = (byte) (lsb >>> 8);
+ bytes[0x9] = (byte) (lsb);
+
+ return bytes;
+ }
+
+ /**
+ * Returns the random component as a byte array.
+ *
+ * The random component is an array of 10 bytes (80 bits).
+ *
+ * @param string a canonical string
+ * @return a byte array
+ * @throws IllegalArgumentException if the input string is invalid
+ */
+ public static byte[] getRandom(String string) {
+
+ final char[] chars = toCharArray(string);
+
+ long random0 = 0;
+ long random1 = 0;
+
+ random0 |= (long) ALPHABET_VALUES[chars[0x0a]] << 35;
+ random0 |= (long) ALPHABET_VALUES[chars[0x0b]] << 30;
+ random0 |= (long) ALPHABET_VALUES[chars[0x0c]] << 25;
+ random0 |= (long) ALPHABET_VALUES[chars[0x0d]] << 20;
+ random0 |= (long) ALPHABET_VALUES[chars[0x0e]] << 15;
+ random0 |= (long) ALPHABET_VALUES[chars[0x0f]] << 10;
+ random0 |= (long) ALPHABET_VALUES[chars[0x10]] << 5;
+ random0 |= (long) ALPHABET_VALUES[chars[0x11]];
+
+ random1 |= (long) ALPHABET_VALUES[chars[0x12]] << 35;
+ random1 |= (long) ALPHABET_VALUES[chars[0x13]] << 30;
+ random1 |= (long) ALPHABET_VALUES[chars[0x14]] << 25;
+ random1 |= (long) ALPHABET_VALUES[chars[0x15]] << 20;
+ random1 |= (long) ALPHABET_VALUES[chars[0x16]] << 15;
+ random1 |= (long) ALPHABET_VALUES[chars[0x17]] << 10;
+ random1 |= (long) ALPHABET_VALUES[chars[0x18]] << 5;
+ random1 |= (long) ALPHABET_VALUES[chars[0x19]];
+
+ final byte[] bytes = new byte[RANDOM_BYTES];
+
+ bytes[0x0] = (byte) (random0 >>> 32);
+ bytes[0x1] = (byte) (random0 >>> 24);
+ bytes[0x2] = (byte) (random0 >>> 16);
+ bytes[0x3] = (byte) (random0 >>> 8);
+ bytes[0x4] = (byte) (random0);
+
+ bytes[0x5] = (byte) (random1 >>> 32);
+ bytes[0x6] = (byte) (random1 >>> 24);
+ bytes[0x7] = (byte) (random1 >>> 16);
+ bytes[0x8] = (byte) (random1 >>> 8);
+ bytes[0x9] = (byte) (random1);
+
+ return bytes;
+ }
+
+ /**
+ * Returns the most significant bits as a number.
+ *
+ * @return a number.
+ */
+ public long getMostSignificantBits() {
+ return this.msb;
+ }
+
+ /**
+ * Returns the least significant bits as a number.
+ *
+ * @return a number.
+ */
+ public long getLeastSignificantBits() {
+ return this.lsb;
+ }
+
+ /**
+ * Returns a new ULID by incrementing the random component of the current ULID.
+ *
+ * Since the random component contains 80 bits:
+ *
+ * Due to (1) and (2), it does not throw the error message recommended by the
+ * specification. When an overflow occurs in the random 80 bits, the time
+ * component is simply incremented to maintain monotonicity.
+ *
+ * @return a ULID
+ */
+ public Ulid increment() {
+
+ long newMsb = this.msb;
+ long newLsb = this.lsb + 1; // increment the LEAST significant bits
+
+ if (newLsb == INCREMENT_OVERFLOW) {
+ newMsb += 1; // increment the MOST significant bits
+ }
+
+ return new Ulid(newMsb, newLsb);
+ }
+
+ /**
+ * Checks if the input string is valid.
+ *
+ * The input string must be 26 characters long and must contain only characters
+ * from Crockford's base 32 alphabet.
+ *
+ * The first character of the input string must be between 0 and 7.
+ *
+ * @param string a canonical string
+ * @return true if the input string is valid
+ * @see Crockford's Base 32
+ */
+ public static boolean isValid(String string) {
+ return string != null && isValidCharArray(string.toCharArray());
+ }
+
+ /**
+ * Returns a hash code value for the ULID.
+ */
+ @Override
+ public int hashCode() {
+ final long bits = msb ^ lsb;
+ return (int) (bits ^ (bits >>> 32));
+ }
+
+ /**
+ * Checks if some other ULID is equal to this one.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other == null)
+ return false;
+ if (other.getClass() != Ulid.class)
+ return false;
+ Ulid that = (Ulid) other;
+ if (lsb != that.lsb)
+ return false;
+ else return msb == that.msb;
+ }
+
+ /**
+ * Compares two ULIDs as unsigned 128-bit integers.
+ *
+ * The first of two ULIDs is greater than the second if the most significant
+ * byte in which they differ is greater for the first ULID.
+ *
+ * @param that a ULID to be compared with
+ * @return -1, 0 or 1 as {@code this} is less than, equal to, or greater than
+ * {@code that}
+ */
+ @Override
+ public int compareTo(Ulid that) {
+
+ // used to compare as UNSIGNED longs
+ final long min = 0x8000000000000000L;
+
+ final long a = this.msb + min;
+ final long b = that.msb + min;
+
+ if (a > b)
+ return 1;
+ else if (a < b)
+ return -1;
+
+ final long c = this.lsb + min;
+ final long d = that.lsb + min;
+
+ if (c > d)
+ return 1;
+ else if (c < d)
+ return -1;
+
+ return 0;
+ }
+
+ String toString(char[] alphabet) {
+
+ final char[] chars = new char[ULID_CHARS];
+
+ long time = this.msb >>> 16;
+ long random0 = ((this.msb & 0xffffL) << 24) | (this.lsb >>> 40);
+ long random1 = (this.lsb & 0xffffffffffL);
+
+ chars[0x00] = alphabet[(int) (time >>> 45 & 0b11111)];
+ chars[0x01] = alphabet[(int) (time >>> 40 & 0b11111)];
+ chars[0x02] = alphabet[(int) (time >>> 35 & 0b11111)];
+ chars[0x03] = alphabet[(int) (time >>> 30 & 0b11111)];
+ chars[0x04] = alphabet[(int) (time >>> 25 & 0b11111)];
+ chars[0x05] = alphabet[(int) (time >>> 20 & 0b11111)];
+ chars[0x06] = alphabet[(int) (time >>> 15 & 0b11111)];
+ chars[0x07] = alphabet[(int) (time >>> 10 & 0b11111)];
+ chars[0x08] = alphabet[(int) (time >>> 5 & 0b11111)];
+ chars[0x09] = alphabet[(int) (time & 0b11111)];
+
+ chars[0x0a] = alphabet[(int) (random0 >>> 35 & 0b11111)];
+ chars[0x0b] = alphabet[(int) (random0 >>> 30 & 0b11111)];
+ chars[0x0c] = alphabet[(int) (random0 >>> 25 & 0b11111)];
+ chars[0x0d] = alphabet[(int) (random0 >>> 20 & 0b11111)];
+ chars[0x0e] = alphabet[(int) (random0 >>> 15 & 0b11111)];
+ chars[0x0f] = alphabet[(int) (random0 >>> 10 & 0b11111)];
+ chars[0x10] = alphabet[(int) (random0 >>> 5 & 0b11111)];
+ chars[0x11] = alphabet[(int) (random0 & 0b11111)];
+
+ chars[0x12] = alphabet[(int) (random1 >>> 35 & 0b11111)];
+ chars[0x13] = alphabet[(int) (random1 >>> 30 & 0b11111)];
+ chars[0x14] = alphabet[(int) (random1 >>> 25 & 0b11111)];
+ chars[0x15] = alphabet[(int) (random1 >>> 20 & 0b11111)];
+ chars[0x16] = alphabet[(int) (random1 >>> 15 & 0b11111)];
+ chars[0x17] = alphabet[(int) (random1 >>> 10 & 0b11111)];
+ chars[0x18] = alphabet[(int) (random1 >>> 5 & 0b11111)];
+ chars[0x19] = alphabet[(int) (random1 & 0b11111)];
+
+ return new String(chars);
+ }
+
+ static char[] toCharArray(String string) {
+ char[] chars = string == null ? null : string.toCharArray();
+ if (!isValidCharArray(chars)) {
+ throw new IllegalArgumentException(String.format("Invalid ULID: \"%s\"", string));
+ }
+ return chars;
+ }
+
+ /*
+ * Checks if the string is a valid ULID.
+ *
+ * A valid ULID string is a sequence of 26 characters from Crockford's Base 32
+ * alphabet.
+ *
+ * The first character of the input string must be between 0 and 7.
+ */
+ static boolean isValidCharArray(final char[] chars) {
+
+ if (chars == null || chars.length != ULID_CHARS) {
+ return false; // null or wrong size!
+ }
+
+ // The time component has 48 bits.
+ // The base32 encoded time component has 50 bits.
+ // The time component cannot be greater than than 2^48-1.
+ // So the 2 first bits of the base32 decoded time component must be ZERO.
+ // As a consequence, the 1st char of the input string must be between 0 and 7.
+ if ((ALPHABET_VALUES[chars[0]] & 0b11000) != 0) {
+ // ULID specification:
+ // "Any attempt to decode or encode a ULID larger than this (time > 2^48-1)
+ // should be rejected by all implementations, to prevent overflow bugs."
+ return false; // time overflow!
+ }
+
+ for (char aChar : chars) {
+ if (ALPHABET_VALUES[aChar] == -1) {
+ return false; // invalid character!
+ }
+ }
+
+ return true; // It seems to be OK.
+ }
+}
diff --git a/src/main/java/com/andrewlalis/perfin/data/ulid/UlidCreator.java b/src/main/java/com/andrewlalis/perfin/data/ulid/UlidCreator.java
new file mode 100644
index 0000000..4c5760a
--- /dev/null
+++ b/src/main/java/com/andrewlalis/perfin/data/ulid/UlidCreator.java
@@ -0,0 +1,167 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2020-2023 Fabio Lima
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package com.andrewlalis.perfin.data.ulid;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+/**
+ * A class that generates ULIDs.
+ *
+ * Both types of ULID can be easily created by this generator, i.e. monotonic
+ * and non-monotonic.
+ *
+ * In addition, a "non-standard" hash-based ULID can also be generated, in which
+ * the random component is replaced with the first 10 bytes of an SHA-256 hash.
+ */
+public final class UlidCreator {
+
+ private UlidCreator() {
+ }
+
+ /**
+ * Returns a ULID.
+ *
+ * The random component is reset for each new ULID generated.
+ *
+ * @return a ULID
+ */
+ public static Ulid getUlid() {
+ return UlidFactoryHolder.INSTANCE.create();
+ }
+
+ /**
+ * Returns a ULID.
+ *
+ * The random component is reset for each new ULID generated.
+ *
+ * @param time the current time in milliseconds, measured from the UNIX epoch of
+ * 1970-01-01T00:00Z (UTC)
+ * @return a ULID
+ */
+ public static Ulid getUlid(final long time) {
+ return UlidFactoryHolder.INSTANCE.create(time);
+ }
+
+ /**
+ * Returns a Monotonic ULID.
+ *
+ * The random component is incremented for each new ULID generated in the same
+ * millisecond.
+ *
+ * @return a ULID
+ */
+ public static Ulid getMonotonicUlid() {
+ return MonotonicFactoryHolder.INSTANCE.create();
+ }
+
+ /**
+ * Returns a Monotonic ULID.
+ *
+ * The random component is incremented for each new ULID generated in the same
+ * millisecond.
+ *
+ * @param time the current time in milliseconds, measured from the UNIX epoch of
+ * 1970-01-01T00:00Z (UTC)
+ * @return a ULID
+ */
+ public static Ulid getMonotonicUlid(final long time) {
+ return MonotonicFactoryHolder.INSTANCE.create(time);
+ }
+
+ /**
+ * Returns a Hash ULID.
+ *
+ * The random component is replaced with the first 10 bytes of an SHA-256 hash.
+ *
+ * It always returns the same ULID for a specific pair of {@code time} and
+ * {@code string}.
+ *
+ * Usage example:
+ *
+ *
+ * The random component is replaced with the first 10 bytes of an SHA-256 hash.
+ *
+ * It always returns the same ULID for a specific pair of {@code time} and
+ * {@code bytes}.
+ *
+ * Usage example:
+ *
+ *
+ * This class is used by {@link UlidCreator}.
+ *
+ * You can use this class if you need to use a specific random generator
+ * strategy. However, most people just need {@link UlidCreator}.
+ *
+ * Instances of this class can behave in one of two ways: monotonic or
+ * non-monotonic (default).
+ *
+ * If the factory is monotonic, the random component is incremented by 1 if more
+ * than one ULID is generated within the same millisecond.
+ *
+ * The maximum ULIDs that can be generated per millisecond is 2^80.
+ */
+public final class UlidFactory {
+
+ private final LongSupplier timeFunction;
+ private final LongFunction
+ * It is equivalent to the default constructor {@code new UlidFactory()}.
+ *
+ * @return {@link UlidFactory}
+ */
+ public static UlidFactory newInstance() {
+ return new UlidFactory(new UlidFunction());
+ }
+
+ /**
+ * Returns a new factory.
+ *
+ * @param random a {@link Random} generator
+ * @return {@link UlidFactory}
+ */
+ public static UlidFactory newInstance(Random random) {
+ return new UlidFactory(new UlidFunction(random));
+ }
+
+ /**
+ * Returns a new factory.
+ *
+ * The given random function must return a long value.
+ *
+ * @param randomFunction a random function that returns a long value
+ * @return {@link UlidFactory}
+ */
+ 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
+ * The given random function must return a long value.
+ *
+ * @param randomFunction a random function that returns a long value
+ * @return {@link UlidFactory}
+ */
+ public static UlidFactory newMonotonicInstance(LongSupplier randomFunction) {
+ return new UlidFactory(new MonotonicFunction(randomFunction));
+ }
+
+ /**
+ * Returns a new monotonic 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 newMonotonicInstance(IntFunction
+ * The given random function must return a long value.
+ *
+ * @param randomFunction a random function that returns a long value
+ * @param clock a clock instance that provides the current time in
+ * milliseconds, measured from the UNIX epoch of
+ * 1970-01-01T00:00Z (UTC)
+ * @return {@link UlidFactory}
+ */
+ static UlidFactory newMonotonicInstance(LongSupplier randomFunction, Clock clock) {
+ Objects.requireNonNull(clock, "Clock instant must not be null");
+ return new UlidFactory(new MonotonicFunction(randomFunction), clock::millis);
+ }
+
+ /**
+ * 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 clock instance that provides the current time in
+ * milliseconds, measured from the UNIX epoch of
+ * 1970-01-01T00:00Z (UTC)
+ * @return {@link UlidFactory}
+ */
+ static UlidFactory newMonotonicInstance(IntFunction
+ * The given random function must return a long value.
+ *
+ * @param randomFunction a random function that returns a long value
+ * @param timeFunction a function that returns the current time in
+ * milliseconds, measured from the UNIX epoch of
+ * 1970-01-01T00:00Z (UTC)
+ * @return {@link UlidFactory}
+ */
+ public static UlidFactory newMonotonicInstance(LongSupplier randomFunction, LongSupplier timeFunction) {
+ return new UlidFactory(new MonotonicFunction(randomFunction), timeFunction);
+ }
+
+ /**
+ * 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 timeFunction a function that returns the current time in
+ * milliseconds, measured from the UNIX epoch of
+ * 1970-01-01T00:00Z (UTC)
+ * @return {@link UlidFactory}
+ */
+ public static UlidFactory newMonotonicInstance(IntFunction
+ *
+ * {@code
+ * long time = file.getCreatedAt();
+ * String name = file.getFileName();
+ * Ulid ulid = UlidCreator.getHashUlid(time, name);
+ * }
+ *
+ * @param time the time in milliseconds, measured from the UNIX epoch of
+ * 1970-01-01T00:00Z (UTC)
+ * @param string a string to be hashed using SHA-256 algorithm.
+ * @return a ULID
+ * @since 5.2.0
+ */
+ public static Ulid getHashUlid(final long time, String string) {
+ byte[] bytes = string.getBytes(StandardCharsets.UTF_8);
+ return getHashUlid(time, bytes);
+ }
+
+ /**
+ * Returns a Hash ULID.
+ * {@code
+ * long time = file.getCreatedAt();
+ * byte[] bytes = file.getFileBinary();
+ * Ulid ulid = UlidCreator.getHashUlid(time, bytes);
+ * }
+ *
+ * @param time the time in milliseconds, measured from the UNIX epoch of
+ * 1970-01-01T00:00Z (UTC)
+ * @param bytes a byte array to be hashed using SHA-256 algorithm.
+ * @return a ULID
+ * @since 5.2.0
+ */
+ public static Ulid getHashUlid(final long time, byte[] bytes) {
+ // Calculate the hash and take the first 10 bytes
+ byte[] hash = hasher("SHA-256").digest(bytes);
+ byte[] rand = Arrays.copyOf(hash, 10);
+ return new Ulid(time, rand);
+ }
+
+ private static MessageDigest hasher(final String algorithm) {
+ try {
+ return MessageDigest.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(String.format("%s not supported", algorithm));
+ }
+ }
+
+ private static class UlidFactoryHolder {
+ static final UlidFactory INSTANCE = UlidFactory.newInstance();
+ }
+
+ private static class MonotonicFactoryHolder {
+ static final UlidFactory INSTANCE = UlidFactory.newMonotonicInstance();
+ }
+}
diff --git a/src/main/java/com/andrewlalis/perfin/data/ulid/UlidFactory.java b/src/main/java/com/andrewlalis/perfin/data/ulid/UlidFactory.java
new file mode 100644
index 0000000..21f2862
--- /dev/null
+++ b/src/main/java/com/andrewlalis/perfin/data/ulid/UlidFactory.java
@@ -0,0 +1,508 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2020-2023 Fabio Lima
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package com.andrewlalis.perfin.data.ulid;
+
+import java.security.SecureRandom;
+import java.time.Clock;
+import java.util.Objects;
+import java.util.Random;
+import java.util.function.IntFunction;
+import java.util.function.LongFunction;
+import java.util.function.LongSupplier;
+
+/**
+ * A class that actually generates ULIDs.
+ *