From 7f7d13b5ad2d2d8a9742c51d78054ae705610b61 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Mon, 1 Jan 2024 11:20:30 -0500 Subject: [PATCH] Added external ULID library to solve modularity issue, and added package-linux-deb.sh for running jpackage on linux. --- design/perfin-logo.svg | 12 +- design/perfin-logo_256.png | Bin 0 -> 11025 bytes pom.xml | 57 +- scripts/package-linux-deb.sh | 34 + run-jar.sh => scripts/run-jar.sh | 3 + .../data/impl/JdbcAttachmentRepository.java | 2 +- .../andrewlalis/perfin/data/ulid/Ulid.java | 814 ++++++++++++++++++ .../perfin/data/ulid/UlidCreator.java | 167 ++++ .../perfin/data/ulid/UlidFactory.java | 508 +++++++++++ src/main/java/module-info.java | 2 - 10 files changed, 1585 insertions(+), 14 deletions(-) create mode 100644 design/perfin-logo_256.png create mode 100755 scripts/package-linux-deb.sh rename run-jar.sh => scripts/run-jar.sh (68%) create mode 100644 src/main/java/com/andrewlalis/perfin/data/ulid/Ulid.java create mode 100644 src/main/java/com/andrewlalis/perfin/data/ulid/UlidCreator.java create mode 100644 src/main/java/com/andrewlalis/perfin/data/ulid/UlidFactory.java 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 0000000000000000000000000000000000000000..3a97420db9521726d158f623b022b091d2d3c4e6 GIT binary patch literal 11025 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 2.2.224 - - com.github.f4b6a3 - ulid-creator - 5.2.2 - @@ -106,6 +101,58 @@ + + + + org.moditect + moditect-maven-plugin + 1.0.0.Final + + + add-module-infos + generate-resources + + add-module-info + + + ${project.build.directory}/modules + + + + com.h2database + h2 + 2.2.224 + + + module com.h2database { + requires java.compiler; + requires jdk.net; + requires static lucene.core; + requires static lucene.queryparser; + requires static slf4j.api; + requires static jakarta.servlet; + requires transitive java.desktop; + requires transitive java.instrument; + requires java.logging; + requires transitive java.management; + requires static java.naming; + requires transitive java.scripting; + requires java.sql; + requires transitive java.transaction.xa; + requires transitive java.xml; + requires static javax.servlet.api; + requires static org.locationtech.jts; + requires static org.osgi.service.jdbc; + requires static osgi.core; + provides java.sql.Driver with org.h2.Driver; + } + + + + + + + \ No newline at end of file diff --git a/scripts/package-linux-deb.sh b/scripts/package-linux-deb.sh new file mode 100755 index 0000000..22786df --- /dev/null +++ b/scripts/package-linux-deb.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +# Creates a native linux installer for Perfin, in the .deb format (for Ubuntu, Debian, etc.) + +mvn clean package + +function join_by { + local d=${1-} f=${2-} + if shift 2; then + printf %s "$f" "${@/#/$d}" + fi +} + +# Gets a ":"-separated string of all the dependency jar-files. +module_jar_files=(target/lib/*) +module_jar_files_path=$(join_by ":" ${module_jar_files[@]}) +module_path="target/classes:$module_jar_files_path" + +# Fix because H2 is not modular: +rm target/lib/h2-*.jar +module_path="$module_path:target/modules/h2-2.2.224.jar" + +jpackage \ + --name "Perfin" \ + --app-version "0.0.1" \ + --description "Desktop application for personal finance. Add your accounts, track transactions, and store receipts, invoices, and more." \ + --icon design/perfin-logo_256.png \ + --vendor "Andrew Lalis" \ + --module com.andrewlalis.perfin/com.andrewlalis.perfin.PerfinApp \ + --module-path $module_path \ + --add-modules com.h2database \ + --linux-deb-maintainer "andrewlalisofficial@gmail.com" \ + --linux-shortcut \ + --linux-menu-group "Office;Finance;Java" \ diff --git a/run-jar.sh b/scripts/run-jar.sh similarity index 68% rename from run-jar.sh rename to scripts/run-jar.sh index ab86ff2..c81299c 100755 --- a/run-jar.sh +++ b/scripts/run-jar.sh @@ -1,5 +1,8 @@ #!/usr/bin/env bash +# A helper script to (optionally) build and run a JAR packaged version of Perfin. +# Provide the "build" argument to rebuild the project before running it. + shouldBuild=0 for i in "$@" ; do if [[ $i == "build" ]] ; then diff --git a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAttachmentRepository.java b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAttachmentRepository.java index 65e3e80..2acdbc3 100644 --- a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAttachmentRepository.java +++ b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAttachmentRepository.java @@ -1,10 +1,10 @@ package com.andrewlalis.perfin.data.impl; import com.andrewlalis.perfin.data.AttachmentRepository; +import com.andrewlalis.perfin.data.ulid.UlidCreator; import com.andrewlalis.perfin.data.util.DbUtil; import com.andrewlalis.perfin.data.util.FileUtil; import com.andrewlalis.perfin.model.Attachment; -import com.github.f4b6a3.ulid.UlidCreator; import java.io.IOException; import java.io.UncheckedIOException; diff --git a/src/main/java/com/andrewlalis/perfin/data/ulid/Ulid.java b/src/main/java/com/andrewlalis/perfin/data/ulid/Ulid.java new file mode 100644 index 0000000..c6c6678 --- /dev/null +++ b/src/main/java/com/andrewlalis/perfin/data/ulid/Ulid.java @@ -0,0 +1,814 @@ +/* + * 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.io.Serializable; +import java.time.Instant; +import java.util.Arrays; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +/** + * A class that represents ULIDs. + *

+ * ULID is a 128-bit value that has two components: + *

    + *
  • Time component: a number of milliseconds since 1970-01-01 (Unix + * epoch). + *
  • Random component: a sequence of 80 random bits generated by a + * secure random generator. + *
+ *

+ * 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 { + + private static final long serialVersionUID = 2625269413446854731L; + + private final long msb; // most significant bits + private final long lsb; // least significant bits + + /** + * Number of characters of a ULID. + */ + public static final int ULID_CHARS = 26; + /** + * Number of characters of the time component of a ULID. + */ + public static final int TIME_CHARS = 10; + /** + * Number of characters of the random component of a ULID. + */ + public static final int RANDOM_CHARS = 16; + + /** + * Number of bytes of a ULID. + */ + public static final int ULID_BYTES = 16; + /** + * Number of bytes of the time component of a ULID. + */ + public static final int TIME_BYTES = 6; + /** + * Number of bytes of the random component of a ULID. + */ + public static final int RANDOM_BYTES = 10; + /** + * A special ULID that has all 128 bits set to ZERO. + */ + public static final Ulid MIN = new Ulid(0x0000000000000000L, 0x0000000000000000L); + /** + * A special ULID that has all 128 bits set to ONE. + */ + public static final Ulid MAX = new Ulid(0xffffffffffffffffL, 0xffffffffffffffffL); + + static final byte[] ALPHABET_VALUES = new byte[256]; + static final char[] ALPHABET_UPPERCASE = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".toCharArray(); + static final char[] ALPHABET_LOWERCASE = "0123456789abcdefghjkmnpqrstvwxyz".toCharArray(); + + static { + + // Initialize the alphabet map with -1 + Arrays.fill(ALPHABET_VALUES, (byte) -1); + + // Map the alphabets chars to values + for (int i = 0; i < ALPHABET_UPPERCASE.length; i++) { + ALPHABET_VALUES[ALPHABET_UPPERCASE[i]] = (byte) i; + } + for (int i = 0; i < ALPHABET_LOWERCASE.length; i++) { + ALPHABET_VALUES[ALPHABET_LOWERCASE[i]] = (byte) i; + } + + // Upper case OIL + ALPHABET_VALUES['O'] = 0x00; + ALPHABET_VALUES['I'] = 0x01; + ALPHABET_VALUES['L'] = 0x01; + + // Lower case OIL + ALPHABET_VALUES['o'] = 0x00; + ALPHABET_VALUES['i'] = 0x01; + ALPHABET_VALUES['l'] = 0x01; + } + + // 0xffffffffffffffffL + 1 = 0x0000000000000000L + private static final long INCREMENT_OVERFLOW = 0x0000000000000000L; + + /** + * Creates a new ULID. + *

+ * 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: + *

    + *
  • (1) This method can generate up to 1208925819614629174706176 (2^80) ULIDs + * per millisecond; + *
  • (2) This method can generate monotonic increasing ULIDs + * 99.999999999999992% ((2^80 - 10^9) / (2^80)) of the time within a single + * millisecond interval, considering an unrealistic rate of 1,000,000,000 ULIDs + * per millisecond. + *
+ *

+ * 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: + * + *

{@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. + *

+ * 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: + * + *

{@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. + *

+ * 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 ulidFunction; + + // ****************************** + // Constructors + // ****************************** + + /** + * Default constructor. + */ + public UlidFactory() { + this(new UlidFunction()); + } + + private UlidFactory(LongFunction ulidFunction) { + this(ulidFunction, System::currentTimeMillis); + } + + private UlidFactory(LongFunction ulidFunction, LongSupplier timeFunction) { + + Objects.requireNonNull(ulidFunction, "ULID function must not be null"); + Objects.requireNonNull(timeFunction, "Time function must not be null"); + + this.ulidFunction = ulidFunction; + this.timeFunction = timeFunction; + + if (this.ulidFunction instanceof MonotonicFunction) { + // initialize the internal state of the monotonic function + ((MonotonicFunction) this.ulidFunction).initialize(this.timeFunction); + } + } + + /** + * Returns a new factory. + *

+ * 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 randomFunction) { + return new UlidFactory(new UlidFunction(randomFunction)); + } + + /** + * Returns a new monotonic factory. + * + * @return {@link UlidFactory} + */ + public static UlidFactory newMonotonicInstance() { + return new UlidFactory(new MonotonicFunction()); + } + + /** + * Returns a new monotonic factory. + * + * @param random a {@link Random} generator + * @return {@link UlidFactory} + */ + public static UlidFactory newMonotonicInstance(Random random) { + return new UlidFactory(new MonotonicFunction(random)); + } + + /** + * Returns a new monotonic 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 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 randomFunction) { + return new UlidFactory(new MonotonicFunction(randomFunction)); + } + + /** + * Returns a new monotonic factory. + * + * @param random a {@link Random} generator + * @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(Random random, Clock clock) { + Objects.requireNonNull(clock, "Clock instant must not be null"); + return new UlidFactory(new MonotonicFunction(random), clock::millis); + } + + /** + * 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 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 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. + * + * @param random a {@link Random} generator + * @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(Random random, LongSupplier timeFunction) { + return new UlidFactory(new MonotonicFunction(random), timeFunction); + } + + /** + * 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 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 randomFunction, LongSupplier timeFunction) { + return new UlidFactory(new MonotonicFunction(randomFunction), timeFunction); + } + + // ****************************** + // Public methods + // ****************************** + + /** + * Returns a new ULID. + * + * @return a ULID + */ + public synchronized Ulid create() { + return this.ulidFunction.apply(timeFunction.getAsLong()); + } + + /** + * Returns a new ULID. + * + * @param time the current time in milliseconds, measured from the UNIX epoch of + * 1970-01-01T00:00Z (UTC) + * @return a ULID + */ + public synchronized Ulid create(final long time) { + return this.ulidFunction.apply(time); + } + + // ****************************** + // Package-private inner classes + // ****************************** + + /** + * Function that creates ULIDs. + */ + static final class UlidFunction implements LongFunction { + + private final IRandom random; + + private UlidFunction(IRandom random) { + this.random = random; + } + + public UlidFunction() { + this(IRandom.newInstance()); + } + + public UlidFunction(Random random) { + this(IRandom.newInstance(random)); + } + + public UlidFunction(LongSupplier randomFunction) { + this(IRandom.newInstance(randomFunction)); + } + + public UlidFunction(IntFunction randomFunction) { + this(IRandom.newInstance(randomFunction)); + } + + @Override + public Ulid apply(final long time) { + 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. + */ + static final class MonotonicFunction implements LongFunction { + + 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. + static final int CLOCK_DRIFT_TOLERANCE = 10_000; + + private MonotonicFunction(IRandom random) { + this.random = random; + } + + public MonotonicFunction() { + this(IRandom.newInstance()); + } + + public MonotonicFunction(Random random) { + this(IRandom.newInstance(random)); + } + + public MonotonicFunction(LongSupplier randomFunction) { + this(IRandom.newInstance(randomFunction)); + } + + public MonotonicFunction(IntFunction randomFunction) { + this(IRandom.newInstance(randomFunction)); + } + + void initialize(LongSupplier timeFunction) { + // initialize the factory with the instant 1970-01-01 00:00:00.000 UTC + this.lastUlid = new Ulid(0L, this.random.nextBytes(Ulid.RANDOM_BYTES)); + } + + @Override + public synchronized Ulid apply(final long time) { + + final long lastTime = lastUlid.getTime(); + + // 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. + // 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); + } + } + + static interface IRandom { + + public long nextLong(); + + public byte[] nextBytes(int length); + + static IRandom newInstance() { + return new ByteRandom(); + } + + 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); + } + } + } + + static IRandom newInstance(LongSupplier randomFunction) { + return new LongRandom(randomFunction); + } + + static IRandom newInstance(IntFunction randomFunction) { + return new ByteRandom(randomFunction); + } + } + + 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, 40... + bytes[i] = (byte) (random >>> shift); + } + + return bytes; + } + + static LongSupplier newRandomFunction(Random random) { + final Random entropy = random != null ? random : new SecureRandom(); + return entropy::nextLong; + } + } + + static class ByteRandom implements IRandom { + + private final IntFunction randomFunction; + + public ByteRandom() { + this(newRandomFunction(null)); + } + + public ByteRandom(Random random) { + this(newRandomFunction(random)); + } + + public ByteRandom(IntFunction 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 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; + }; + } + } +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 3936074..f70e4dc 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -7,8 +7,6 @@ module com.andrewlalis.perfin { requires com.fasterxml.jackson.databind; - requires com.github.f4b6a3.ulid; - requires java.sql; exports com.andrewlalis.perfin to javafx.graphics;