From 2ffa7a95dd0537440b52d652e758ec529d789ba5 Mon Sep 17 00:00:00 2001 From: Fabio Lima Date: Sun, 24 Jan 2021 00:08:24 -0300 Subject: [PATCH] Development of version 3.0.0 #7 Changed the uuid-creator to generate two types of ULID: default (non-monotonic) and monotonic. Until version 2.x.x this library only created monotonic ULIDs. The version 3.0.0 breaks compatibility. List of changes: Changed UlidCreator Created Ulid Created UlidSpecCreator // abstract Created DefaultUlidSpecCreator // implementation Created MonotonicUlidSpecCreator // implementation Removed UlidConverter Removed UlidUtil Removed UlidStruct Create and update test cases and more... Test coverage: 93.5% --- .../internal/UlidStruct.java => Ulid.java} | 138 ++++++------ .../com/github/f4b6a3/ulid/UlidCreator.java | 108 ++------- .../f4b6a3/ulid/creator/UlidSpecCreator.java | 197 +--------------- .../impl/DefaultUlidSpecCreator.java} | 59 +++-- .../impl/MonotonicUlidSpecCreator.java | 69 ++++++ .../f4b6a3/ulid/strategy/RandomStrategy.java | 1 + .../ulid/strategy/TimestampStrategy.java | 1 + .../com/github/f4b6a3/ulid/util/UlidUtil.java | 83 ------- .../f4b6a3/ulid/util/UlidValidator.java | 79 ++++++- .../com/github/f4b6a3/ulid/TestSuite.java | 24 +- .../github/f4b6a3/ulid/UniquenessTest.java | 16 +- .../ulid/creator/UlidSpecCreatorMock.java | 18 -- .../ulid/creator/UlidSpecCreatorTest.java | 210 ++++-------------- .../com/github/f4b6a3/ulid/demo/DemoTest.java | 18 +- .../ulid/UlidCreatorDefaultStringTest.java | 65 ++++++ .../ulid/ulid/UlidCreatorDefaultTest.java | 60 +++++ ...va => UlidCreatorMonotonicStringTest.java} | 37 +-- .../ulid/ulid/UlidCreatorMonotonicTest.java | 71 ++++++ .../f4b6a3/ulid/ulid/UlidCreatorUuidTest.java | 98 -------- .../f4b6a3/ulid/util/UlidConverterTest.java | 118 ---------- .../github/f4b6a3/ulid/util/UlidUtilTest.java | 156 ------------- .../{UlidStructTest.java => UlidTest.java} | 116 ++++------ 22 files changed, 575 insertions(+), 1167 deletions(-) rename src/main/java/com/github/f4b6a3/ulid/{util/internal/UlidStruct.java => Ulid.java} (73%) rename src/main/java/com/github/f4b6a3/ulid/{util/UlidConverter.java => creator/impl/DefaultUlidSpecCreator.java} (54%) create mode 100644 src/main/java/com/github/f4b6a3/ulid/creator/impl/MonotonicUlidSpecCreator.java delete mode 100644 src/main/java/com/github/f4b6a3/ulid/util/UlidUtil.java delete mode 100644 src/test/java/com/github/f4b6a3/ulid/creator/UlidSpecCreatorMock.java create mode 100644 src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorDefaultStringTest.java create mode 100644 src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorDefaultTest.java rename src/test/java/com/github/f4b6a3/ulid/ulid/{UlidCreatorStringTest.java => UlidCreatorMonotonicStringTest.java} (65%) create mode 100644 src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorMonotonicTest.java delete mode 100644 src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorUuidTest.java delete mode 100644 src/test/java/com/github/f4b6a3/ulid/util/UlidConverterTest.java delete mode 100644 src/test/java/com/github/f4b6a3/ulid/util/UlidUtilTest.java rename src/test/java/com/github/f4b6a3/ulid/util/internal/{UlidStructTest.java => UlidTest.java} (55%) diff --git a/src/main/java/com/github/f4b6a3/ulid/util/internal/UlidStruct.java b/src/main/java/com/github/f4b6a3/ulid/Ulid.java similarity index 73% rename from src/main/java/com/github/f4b6a3/ulid/util/internal/UlidStruct.java rename to src/main/java/com/github/f4b6a3/ulid/Ulid.java index 92b8d49..4553a60 100644 --- a/src/main/java/com/github/f4b6a3/ulid/util/internal/UlidStruct.java +++ b/src/main/java/com/github/f4b6a3/ulid/Ulid.java @@ -22,34 +22,33 @@ * SOFTWARE. */ -package com.github.f4b6a3.ulid.util.internal; +package com.github.f4b6a3.ulid; +import java.io.Serializable; import java.util.UUID; import com.github.f4b6a3.ulid.util.UlidValidator; /** - * This class represents the structure of a ULID. - * - * It is for internal use and test cases in this library. + * This class represents a ULID. */ -public final class UlidStruct { +public final class Ulid implements Serializable, Comparable { - public final long time; - public final long random1; - public final long random2; + private final long msb; + private final long lsb; - protected static final long TIMESTAMP_COMPONENT = 0x0000ffffffffffffL; - protected static final long HALF_RANDOM_COMPONENT = 0x000000ffffffffffL; + protected static final int STRING_LENGTH = 26; - public static final char[] BASE32_CHARS = // + protected static final char[] BASE32_CHARS = // { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', // 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', // 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z' }; - public static final long[] BASE32_VALUES = new long[128]; + protected static final long[] BASE32_VALUES = new long[128]; static { - + for (int i = 0; i < BASE32_VALUES.length; i++) { + BASE32_VALUES[i] = -1; + } // Numbers BASE32_VALUES['0'] = 0x00; BASE32_VALUES['1'] = 0x01; @@ -118,28 +117,19 @@ public final class UlidStruct { } - private UlidStruct() { - this.time = 0; - this.random1 = 0; - this.random2 = 0; + private static final long serialVersionUID = 2625269413446854731L; + + private Ulid() { + this.msb = 0; + this.lsb = 0; } - private UlidStruct(long time, long random1, long random2) { - this.time = time & TIMESTAMP_COMPONENT; - this.random1 = random1 & HALF_RANDOM_COMPONENT; - this.random2 = random2 & HALF_RANDOM_COMPONENT; + private Ulid(UUID ulid) { + this.msb = ulid.getMostSignificantBits(); + this.lsb = ulid.getLeastSignificantBits(); } - private UlidStruct(UUID ulid) { - final long msb = ulid.getMostSignificantBits(); - final long lsb = ulid.getLeastSignificantBits(); - - this.time = (msb >>> 16); - this.random1 = ((msb & 0x000000000000ffffL) << 24) | (lsb >>> 40); - this.random2 = (lsb & 0x000000ffffffffffL); - } - - private UlidStruct(String ulid) { + private Ulid(String ulid) { final char[] chars = ulid == null ? new char[0] : ulid.toCharArray(); UlidValidator.validate(chars); @@ -177,26 +167,41 @@ public final class UlidStruct { r2 |= BASE32_VALUES[chars[0x18]] << 5; r2 |= BASE32_VALUES[chars[0x19]]; - this.time = tm; - this.random1 = r1; - this.random2 = r2; + this.msb = (tm << 16) | (r1 >>> 24); + this.lsb = (r1 << 40) | (r2 & 0xffffffffffL); } - public static UlidStruct of(long time, long random1, long random2) { - return new UlidStruct(time, random1, random2); + private Ulid(long msb, long lsb) { + this.msb = msb; + this.lsb = lsb; } - public static UlidStruct of(UUID ulid) { - return new UlidStruct(ulid); + public static Ulid of(UUID ulid) { + return new Ulid(ulid); } - public static UlidStruct of(String ulid) { - return new UlidStruct(ulid); + public static Ulid of(String ulid) { + return new Ulid(ulid); } + public static Ulid of(long msb, long lsb) { + return new Ulid(msb, lsb); + } + + public UUID toUuid() { + return new UUID(this.msb, this.lsb); + } + + @Override public String toString() { - final char[] chars = new char[26]; + final char[] chars = new char[STRING_LENGTH]; + long long0 = this.msb; + long long1 = this.lsb; + + long time = long0 >>> 16; + long random1 = ((long0 & 0xffffL) << 24) | (long1 >>> 40); + long random2 = (long1 & 0xffffffffffL); chars[0x00] = BASE32_CHARS[(int) (time >>> 45 & 0b11111)]; chars[0x01] = BASE32_CHARS[(int) (time >>> 40 & 0b11111)]; @@ -230,33 +235,16 @@ public final class UlidStruct { return new String(chars); } - public String toString4() { - // apply RFC-4122 version 4 and variant 2 - final long random1v4 = ((this.random1 & 0x0fff3fffffL) | 0x4000000000L) | 0x0000800000L; - return UlidStruct.of(this.time, random1v4, this.random2).toString(); - } - - public UUID toUuid() { - - final long msb = (time << 16) | (random1 >>> 24); - final long lsb = (random1 << 40) | random2; - - return new UUID(msb, lsb); - } - - public UUID toUuid4() { - // apply RFC-4122 version 4 and variant 2 - final long random1v4 = ((this.random1 & 0x0fff3fffffL) | 0x4000000000L) | 0x0000800000L; - return UlidStruct.of(this.time, random1v4, this.random2).toUuid(); + public long getTimestamp() { + return this.msb >>> 16; } @Override public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + (int) (random1 ^ (random1 >>> 32)); - result = prime * result + (int) (random2 ^ (random2 >>> 32)); - result = prime * result + (int) (time ^ (time >>> 32)); + result = prime * result + (int) (lsb ^ (lsb >>> 32)); + result = prime * result + (int) (msb ^ (msb >>> 32)); return result; } @@ -268,13 +256,29 @@ public final class UlidStruct { return false; if (getClass() != obj.getClass()) return false; - UlidStruct other = (UlidStruct) obj; - if (random1 != other.random1) + Ulid other = (Ulid) obj; + if (lsb != other.lsb) return false; - if (random2 != other.random2) - return false; - if (time != other.time) + if (msb != other.msb) return false; return true; } + + @Override + public int compareTo(Ulid other) { + + if (this.msb < other.msb) + return -1; + + if (this.msb > other.msb) + return 1; + + if (this.lsb < other.lsb) + return -1; + + if (this.lsb > other.lsb) + return 1; + + return 0; + } } diff --git a/src/main/java/com/github/f4b6a3/ulid/UlidCreator.java b/src/main/java/com/github/f4b6a3/ulid/UlidCreator.java index 483d25a..584b575 100644 --- a/src/main/java/com/github/f4b6a3/ulid/UlidCreator.java +++ b/src/main/java/com/github/f4b6a3/ulid/UlidCreator.java @@ -24,116 +24,44 @@ package com.github.f4b6a3.ulid; -import java.util.UUID; - import com.github.f4b6a3.ulid.creator.UlidSpecCreator; -import com.github.f4b6a3.ulid.exception.InvalidUlidException; -import com.github.f4b6a3.ulid.util.UlidConverter; +import com.github.f4b6a3.ulid.creator.impl.DefaultUlidSpecCreator; +import com.github.f4b6a3.ulid.creator.impl.MonotonicUlidSpecCreator; -/** - * A factory for Universally Unique Lexicographically Sortable Identifiers. - * - * See the ULID spec: https://github.com/ulid/spec - */ public final class UlidCreator { private UlidCreator() { } - /** - * Returns a ULID as GUID from a string. - * - * The input string must be encoded to Crockford's base32, following the ULID - * specification. - * - * An exception is thrown if the ULID string is invalid. - * - * @param ulid a ULID string - * @return a UUID - * @throws InvalidUlidException if invalid - */ - public static UUID fromString(String ulid) { - return UlidConverter.fromString(ulid); + public static Ulid getUlid() { + return DefaultCreatorHolder.INSTANCE.create(); } - /** - * Convert a UUID to ULID string - * - * The returning string is encoded to Crockford's base32. - * - * @param uuid a UUID - * @return a ULID string - */ - public static String toString(UUID ulid) { - return UlidConverter.toString(ulid); + public static Ulid getUlid(Long timestamp) { + return DefaultCreatorHolder.INSTANCE.create(timestamp); } - /** - * Returns a ULID as GUID. - * - * The random component is generated by a secure random number generator: - * {@link java.security.SecureRandom}. - * - * @return a UUID - */ - public static UUID getUlid() { - return UlidSpecCreatorHolder.INSTANCE.create(); + public static Ulid getMonotonicUlid() { + return MonotonicCreatorHolder.INSTANCE.create(); } - /** - * Returns a ULID as GUID. - * - * It is compatible with the RFC-4122 UUID v4. - * - * The random component is generated by a secure random number generator: - * {@link java.security.SecureRandom}. - * - * @return a UUID - */ - public static UUID getUlid4() { - return UlidSpecCreatorHolder.INSTANCE.create4(); + public static Ulid getMonotonicUlid(Long timestamp) { + return MonotonicCreatorHolder.INSTANCE.create(timestamp); } - /** - * Returns a ULID string. - * - * The returning string is encoded to Crockford's base32. - * - * The random component is generated by a secure random number generator: - * {@link java.security.SecureRandom}. - * - * @return a ULID string - */ - public static String getUlidString() { - return UlidSpecCreatorHolder.INSTANCE.createString(); + public static DefaultUlidSpecCreator getDefaultCreator() { + return new DefaultUlidSpecCreator(); } - /** - * Returns a ULID string. - * - * It is compatible with the RFC-4122 UUID v4. - * - * The returning string is encoded to Crockford's base32. - * - * The random component is generated by a secure random number generator: - * {@link java.security.SecureRandom}. - * - * @return a ULID string - */ - public static String getUlidString4() { - return UlidSpecCreatorHolder.INSTANCE.createString4(); + public static MonotonicUlidSpecCreator getMonotonicCreator() { + return new MonotonicUlidSpecCreator(); } - /** - * Return a GUID creator for direct use. - * - * @return a {@link UlidSpecCreator} - */ - public static UlidSpecCreator getUlidSpecCreator() { - return new UlidSpecCreator(); + private static class DefaultCreatorHolder { + static final UlidSpecCreator INSTANCE = getDefaultCreator(); } - private static class UlidSpecCreatorHolder { - static final UlidSpecCreator INSTANCE = getUlidSpecCreator(); + private static class MonotonicCreatorHolder { + static final UlidSpecCreator INSTANCE = getMonotonicCreator(); } } diff --git a/src/main/java/com/github/f4b6a3/ulid/creator/UlidSpecCreator.java b/src/main/java/com/github/f4b6a3/ulid/creator/UlidSpecCreator.java index 73034ee..4d2a7d2 100644 --- a/src/main/java/com/github/f4b6a3/ulid/creator/UlidSpecCreator.java +++ b/src/main/java/com/github/f4b6a3/ulid/creator/UlidSpecCreator.java @@ -25,35 +25,15 @@ package com.github.f4b6a3.ulid.creator; import java.util.Random; -import java.util.UUID; +import com.github.f4b6a3.ulid.Ulid; import com.github.f4b6a3.ulid.strategy.RandomStrategy; import com.github.f4b6a3.ulid.strategy.random.DefaultRandomStrategy; import com.github.f4b6a3.ulid.strategy.random.OtherRandomStrategy; import com.github.f4b6a3.ulid.strategy.TimestampStrategy; import com.github.f4b6a3.ulid.strategy.timestamp.DefaultTimestampStrategy; -import com.github.f4b6a3.ulid.util.internal.UlidStruct; -/** - * Factory that creates lexicographically sortable GUIDs, based on the ULID - * specification - Universally Unique Lexicographically Sortable Identifier. - * - * ULID specification: https://github.com/ulid/spec - */ -public class UlidSpecCreator { - - protected long random1 = 0; - protected long random2 = 0; - - protected long randomMax1; - protected long randomMax2; - - protected static final long HALF_RANDOM_COMPONENT = 0x000000ffffffffffL; - protected static final long INCREMENT_MAX = 0x0000010000000000L; - - protected long previousTimestamp; - - protected static final String OVERRUN_MESSAGE = "The system overran the generator by requesting too many ULIDs."; +public abstract class UlidSpecCreator { protected TimestampStrategy timestampStrategy; protected RandomStrategy randomStrategy; @@ -63,164 +43,11 @@ public class UlidSpecCreator { this.randomStrategy = new DefaultRandomStrategy(); } - /** - * - * Return a GUID based on the ULID specification. - * - * A ULID has two parts: - * - * 1. A part of 48 bits that represent the amount of milliseconds since Unix - * Epoch, 1 January 1970. - * - * 2. A part of 80 bits that has a random value generated a secure random - * generator. - * - * The random part is reset to a new value every time the millisecond part - * changes. - * - * If more than one GUID is generated within the same millisecond, the random - * part is incremented by one. - * - * The maximum GUIDs that can be generated per millisecond is 2^80. - * - * The random part is generated by a secure random number generator: - * {@link java.security.SecureRandom}. - * - * ### Specification of Universally Unique Lexicographically Sortable ID - * - * #### Components - * - * ##### Timestamp - * - * It is a 48 bit integer. UNIX-time in milliseconds. Won't run out of space - * 'til the year 10889 AD. - * - * ##### Randomness - * - * It is a 80 bits integer. Cryptographically secure source of randomness, if - * possible. - * - * #### Sorting - * - * The left-most character must be sorted first, and the right-most character - * sorted last (lexical order). The default ASCII character set must be used. - * Within the same millisecond, sort order is not guaranteed. - * - * #### Monotonicity - * - * When generating a ULID within the same millisecond, we can provide some - * guarantees regarding sort order. Namely, if the same millisecond is detected, - * the random component is incremented by 1 bit in the least significant bit - * position (with carrying). - * - * If, in the extremely unlikely event that, you manage to generate more than - * 2^80 ULIDs within the same millisecond, or cause the random component to - * overflow with less, the generation will fail. - * - * @return {@link UUID} a GUID value - */ - public synchronized UUID create() { - return UlidStruct.of(this.getTimestamp(), random1, random2).toUuid(); + public synchronized Ulid create() { + return create(null); } - /** - * - * Return a GUID based on the ULID specification. - * - * It is compatible with the RFC-4122 UUID v4. - * - * @return {@link UUID} a GUID value - */ - public synchronized UUID create4() { - return UlidStruct.of(this.getTimestamp(), random1, random2).toUuid4(); - } - - /** - * Returns a ULID string. - * - * The returning string is encoded to Crockford's base32. - * - * The random component is generated by a secure random number generator: - * {@link java.security.SecureRandom}. - * - * @return a ULID string - */ - public synchronized String createString() { - return UlidStruct.of(this.getTimestamp(), random1, random2).toString(); - } - - /** - * Returns a ULID string. - * - * It is compatible with the RFC-4122 UUID v4. - * - * The returning string is encoded to Crockford's base32. - * - * The random component is generated by a secure random number generator: - * {@link java.security.SecureRandom}. - * - * @return a ULID string - */ - public synchronized String createString4() { - return UlidStruct.of(this.getTimestamp(), random1, random2).toString4(); - } - - /** - * Return the current timestamp and resets or increments the random part. - * - * @return timestamp - */ - protected synchronized long getTimestamp() { - - final long timestamp = this.timestampStrategy.getTimestamp(); - - if (timestamp == this.previousTimestamp) { - this.increment(); - } else { - this.reset(); - } - - this.previousTimestamp = timestamp; - return timestamp; - } - - /** - * Reset the random part of the GUID. - */ - protected synchronized void reset() { - - // Get random values - final byte[] bytes = new byte[10]; - this.randomStrategy.nextBytes(bytes); - - this.random1 = (long) (bytes[0x0] & 0xff) << 32; - this.random1 |= (long) (bytes[0x1] & 0xff) << 24; - this.random1 |= (long) (bytes[0x2] & 0xff) << 16; - this.random1 |= (long) (bytes[0x3] & 0xff) << 8; - this.random1 |= (long) (bytes[0x4] & 0xff); - - this.random2 = (long) (bytes[0x5] & 0xff) << 32; - this.random2 |= (long) (bytes[0x6] & 0xff) << 24; - this.random2 |= (long) (bytes[0x7] & 0xff) << 16; - this.random2 |= (long) (bytes[0x8] & 0xff) << 8; - this.random2 |= (long) (bytes[0x9] & 0xff); - - // Save the random values - this.randomMax1 = this.random1 | INCREMENT_MAX; - this.randomMax2 = this.random2 | INCREMENT_MAX; - } - - /** - * Increment the random part of the GUID. - */ - protected synchronized void increment() { - if (++this.random2 >= this.randomMax2) { - this.random2 = this.random2 & HALF_RANDOM_COMPONENT; - if ((++this.random1 >= this.randomMax1)) { - this.reset(); - } - } - } + public abstract Ulid create(Long timestamp); /** * Used for changing the timestamp strategy. @@ -266,18 +93,4 @@ public class UlidSpecCreator { this.randomStrategy = new OtherRandomStrategy(random); return (T) this; } - - /** - * For unit tests - */ - protected long extractRandom1(UUID uuid) { - return ((uuid.getMostSignificantBits() & 0x000000000000ffff) << 24) | (uuid.getLeastSignificantBits() >>> 40); - } - - /** - * For unit tests - */ - protected long extractRandom2(UUID uuid) { - return uuid.getLeastSignificantBits() & HALF_RANDOM_COMPONENT; - } } diff --git a/src/main/java/com/github/f4b6a3/ulid/util/UlidConverter.java b/src/main/java/com/github/f4b6a3/ulid/creator/impl/DefaultUlidSpecCreator.java similarity index 54% rename from src/main/java/com/github/f4b6a3/ulid/util/UlidConverter.java rename to src/main/java/com/github/f4b6a3/ulid/creator/impl/DefaultUlidSpecCreator.java index 03dc560..d0ad3b5 100644 --- a/src/main/java/com/github/f4b6a3/ulid/util/UlidConverter.java +++ b/src/main/java/com/github/f4b6a3/ulid/creator/impl/DefaultUlidSpecCreator.java @@ -22,43 +22,38 @@ * SOFTWARE. */ -package com.github.f4b6a3.ulid.util; +package com.github.f4b6a3.ulid.creator.impl; -import java.util.UUID; +import com.github.f4b6a3.ulid.Ulid; +import com.github.f4b6a3.ulid.creator.UlidSpecCreator; -import com.github.f4b6a3.ulid.exception.InvalidUlidException; -import com.github.f4b6a3.ulid.util.internal.UlidStruct; +public final class DefaultUlidSpecCreator extends UlidSpecCreator { -public final class UlidConverter { + @Override + public synchronized Ulid create(final Long timestamp) { - private UlidConverter() { - } + final long time = timestamp != null ? timestamp : this.timestampStrategy.getTimestamp(); - /** - * Convert a UUID to ULID string - * - * The returning string is encoded to Crockford's base32. - * - * @param ulid a UUID - * @return a ULID - */ - public static String toString(final UUID ulid) { - return UlidStruct.of(ulid).toString(); - } + // Get random values + final byte[] bytes = new byte[10]; + this.randomStrategy.nextBytes(bytes); - /** - * Converts a ULID string to a UUID. - * - * The input string must be encoded to Crockford's base32, following the ULID - * specification. - * - * An exception is thrown if the ULID string is invalid. - * - * @param ulid a ULID - * @return a UUID if valid - * @throws InvalidUlidException if invalid - */ - public static UUID fromString(final String ulid) { - return UlidStruct.of(ulid).toUuid(); + long msb = 0; + long lsb = 0; + + msb |= time << 16; + msb |= (long) (bytes[0x0] & 0xff) << 8; + msb |= (long) (bytes[0x1] & 0xff); + + lsb |= (long) (bytes[0x2] & 0xff) << 56; + lsb |= (long) (bytes[0x3] & 0xff) << 48; + lsb |= (long) (bytes[0x4] & 0xff) << 40; + lsb |= (long) (bytes[0x5] & 0xff) << 32; + lsb |= (long) (bytes[0x6] & 0xff) << 24; + lsb |= (long) (bytes[0x7] & 0xff) << 16; + lsb |= (long) (bytes[0x8] & 0xff) << 8; + lsb |= (long) (bytes[0x9] & 0xff); + + return Ulid.of(msb, lsb); } } diff --git a/src/main/java/com/github/f4b6a3/ulid/creator/impl/MonotonicUlidSpecCreator.java b/src/main/java/com/github/f4b6a3/ulid/creator/impl/MonotonicUlidSpecCreator.java new file mode 100644 index 0000000..243a385 --- /dev/null +++ b/src/main/java/com/github/f4b6a3/ulid/creator/impl/MonotonicUlidSpecCreator.java @@ -0,0 +1,69 @@ +/* + * MIT License + * + * Copyright (c) 2020 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.github.f4b6a3.ulid.creator.impl; + +import com.github.f4b6a3.ulid.Ulid; +import com.github.f4b6a3.ulid.creator.UlidSpecCreator; + +public final class MonotonicUlidSpecCreator extends UlidSpecCreator { + + protected long msb = 0; + protected long lsb = 0; + + protected long previousTimestamp; + + public synchronized Ulid create(final Long timestamp) { + + final long time = timestamp != null ? timestamp : this.timestampStrategy.getTimestamp(); + + if (time == this.previousTimestamp) { + this.lsb++; + } else { + // Get random values + final byte[] bytes = new byte[10]; + this.randomStrategy.nextBytes(bytes); + + msb = 0; + lsb = 0; + + msb |= time << 16; + msb |= (long) (bytes[0x0] & 0xff) << 8; + msb |= (long) (bytes[0x1] & 0xff); + + lsb |= (long) (bytes[0x2] & 0xff) << 56; + lsb |= (long) (bytes[0x3] & 0xff) << 48; + lsb |= (long) (bytes[0x4] & 0xff) << 40; + lsb |= (long) (bytes[0x5] & 0xff) << 32; + lsb |= (long) (bytes[0x6] & 0xff) << 24; + lsb |= (long) (bytes[0x7] & 0xff) << 16; + lsb |= (long) (bytes[0x8] & 0xff) << 8; + lsb |= (long) (bytes[0x9] & 0xff); + } + + this.previousTimestamp = time; + return Ulid.of(msb, lsb); + } + +} diff --git a/src/main/java/com/github/f4b6a3/ulid/strategy/RandomStrategy.java b/src/main/java/com/github/f4b6a3/ulid/strategy/RandomStrategy.java index ab2ec58..dd34e14 100644 --- a/src/main/java/com/github/f4b6a3/ulid/strategy/RandomStrategy.java +++ b/src/main/java/com/github/f4b6a3/ulid/strategy/RandomStrategy.java @@ -24,6 +24,7 @@ package com.github.f4b6a3.ulid.strategy; +@FunctionalInterface public interface RandomStrategy { void nextBytes(byte[] bytes); } diff --git a/src/main/java/com/github/f4b6a3/ulid/strategy/TimestampStrategy.java b/src/main/java/com/github/f4b6a3/ulid/strategy/TimestampStrategy.java index e6640b5..b006ec2 100644 --- a/src/main/java/com/github/f4b6a3/ulid/strategy/TimestampStrategy.java +++ b/src/main/java/com/github/f4b6a3/ulid/strategy/TimestampStrategy.java @@ -24,6 +24,7 @@ package com.github.f4b6a3.ulid.strategy; +@FunctionalInterface public interface TimestampStrategy { long getTimestamp(); } diff --git a/src/main/java/com/github/f4b6a3/ulid/util/UlidUtil.java b/src/main/java/com/github/f4b6a3/ulid/util/UlidUtil.java deleted file mode 100644 index 58331c1..0000000 --- a/src/main/java/com/github/f4b6a3/ulid/util/UlidUtil.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020 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.github.f4b6a3.ulid.util; - -import java.time.Instant; -import java.util.UUID; - -import com.github.f4b6a3.ulid.util.internal.UlidStruct; - -public final class UlidUtil { - - protected static final int BASE_32 = 32; - - protected static final int ULID_LENGTH = 26; - - // Include 'O'->ZERO, 'I'->ONE and 'L'->ONE - protected static final char[] ALPHABET_CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZOIL".toCharArray(); - - protected static final char[] ALPHABET_JAVA = "0123456789abcdefghijklmnopqrstuv011".toCharArray(); - - private UlidUtil() { - } - - public static long extractUnixMilliseconds(UUID ulid) { - return extractTimestamp(ulid); - } - - public static long extractUnixMilliseconds(String ulid) { - UlidValidator.validate(ulid); - return extractTimestamp(ulid); - } - - public static Instant extractInstant(UUID ulid) { - long milliseconds = extractTimestamp(ulid); - return Instant.ofEpochMilli(milliseconds); - } - - public static Instant extractInstant(String ulid) { - UlidValidator.validate(ulid); - long milliseconds = extractTimestamp(ulid); - return Instant.ofEpochMilli(milliseconds); - } - - protected static long extractTimestamp(UUID ulid) { - return (ulid.getMostSignificantBits() >>> 16); - } - - protected static long extractTimestamp(String ulid) { - return UlidStruct.of(ulid).time; - } - - public static String extractTimestampComponent(String ulid) { - UlidValidator.validate(ulid); - return ulid.substring(0, 10); - } - - public static String extractRandomnessComponent(String ulid) { - UlidValidator.validate(ulid); - return ulid.substring(10, ULID_LENGTH); - } -} diff --git a/src/main/java/com/github/f4b6a3/ulid/util/UlidValidator.java b/src/main/java/com/github/f4b6a3/ulid/util/UlidValidator.java index d739aa0..ef2822d 100644 --- a/src/main/java/com/github/f4b6a3/ulid/util/UlidValidator.java +++ b/src/main/java/com/github/f4b6a3/ulid/util/UlidValidator.java @@ -26,11 +26,82 @@ package com.github.f4b6a3.ulid.util; import com.github.f4b6a3.ulid.exception.InvalidUlidException; -import static com.github.f4b6a3.ulid.util.internal.UlidStruct.BASE32_VALUES; - public final class UlidValidator { - protected static final int ULID_LENGTH = 26; + protected static final int STRING_LENGTH = 26; + + protected static final long[] BASE32_VALUES = new long[128]; + static { + for (int i = 0; i < BASE32_VALUES.length; i++) { + BASE32_VALUES[i] = -1; + } + // Numbers + BASE32_VALUES['0'] = 0x00; + BASE32_VALUES['1'] = 0x01; + BASE32_VALUES['2'] = 0x02; + BASE32_VALUES['3'] = 0x03; + BASE32_VALUES['4'] = 0x04; + BASE32_VALUES['5'] = 0x05; + BASE32_VALUES['6'] = 0x06; + BASE32_VALUES['7'] = 0x07; + BASE32_VALUES['8'] = 0x08; + BASE32_VALUES['9'] = 0x09; + // Lower case + BASE32_VALUES['a'] = 0x0a; + BASE32_VALUES['b'] = 0x0b; + BASE32_VALUES['c'] = 0x0c; + BASE32_VALUES['d'] = 0x0d; + BASE32_VALUES['e'] = 0x0e; + BASE32_VALUES['f'] = 0x0f; + BASE32_VALUES['g'] = 0x10; + BASE32_VALUES['h'] = 0x11; + BASE32_VALUES['j'] = 0x12; + BASE32_VALUES['k'] = 0x13; + BASE32_VALUES['m'] = 0x14; + BASE32_VALUES['n'] = 0x15; + BASE32_VALUES['p'] = 0x16; + BASE32_VALUES['q'] = 0x17; + BASE32_VALUES['r'] = 0x18; + BASE32_VALUES['s'] = 0x19; + BASE32_VALUES['t'] = 0x1a; + BASE32_VALUES['v'] = 0x1b; + BASE32_VALUES['w'] = 0x1c; + BASE32_VALUES['x'] = 0x1d; + BASE32_VALUES['y'] = 0x1e; + BASE32_VALUES['z'] = 0x1f; + // Lower case OIL + BASE32_VALUES['o'] = 0x00; + BASE32_VALUES['i'] = 0x01; + BASE32_VALUES['l'] = 0x01; + // Upper case + BASE32_VALUES['A'] = 0x0a; + BASE32_VALUES['B'] = 0x0b; + BASE32_VALUES['C'] = 0x0c; + BASE32_VALUES['D'] = 0x0d; + BASE32_VALUES['E'] = 0x0e; + BASE32_VALUES['F'] = 0x0f; + BASE32_VALUES['G'] = 0x10; + BASE32_VALUES['H'] = 0x11; + BASE32_VALUES['J'] = 0x12; + BASE32_VALUES['K'] = 0x13; + BASE32_VALUES['M'] = 0x14; + BASE32_VALUES['N'] = 0x15; + BASE32_VALUES['P'] = 0x16; + BASE32_VALUES['Q'] = 0x17; + BASE32_VALUES['R'] = 0x18; + BASE32_VALUES['S'] = 0x19; + BASE32_VALUES['T'] = 0x1a; + BASE32_VALUES['V'] = 0x1b; + BASE32_VALUES['W'] = 0x1c; + BASE32_VALUES['X'] = 0x1d; + BASE32_VALUES['Y'] = 0x1e; + BASE32_VALUES['Z'] = 0x1f; + // Upper case OIL + BASE32_VALUES['O'] = 0x00; + BASE32_VALUES['I'] = 0x01; + BASE32_VALUES['L'] = 0x01; + + } private UlidValidator() { } @@ -147,6 +218,6 @@ public final class UlidValidator { return false; } } - return (c.length - hyphen) == ULID_LENGTH; + return (c.length - hyphen) == STRING_LENGTH; } } diff --git a/src/test/java/com/github/f4b6a3/ulid/TestSuite.java b/src/test/java/com/github/f4b6a3/ulid/TestSuite.java index 02a6174..3a4dd5f 100644 --- a/src/test/java/com/github/f4b6a3/ulid/TestSuite.java +++ b/src/test/java/com/github/f4b6a3/ulid/TestSuite.java @@ -4,22 +4,22 @@ import org.junit.runner.RunWith; import org.junit.runners.Suite; import com.github.f4b6a3.ulid.creator.UlidSpecCreatorTest; -import com.github.f4b6a3.ulid.ulid.UlidCreatorUuidTest; -import com.github.f4b6a3.ulid.ulid.UlidCreatorStringTest; -import com.github.f4b6a3.ulid.util.UlidConverterTest; -import com.github.f4b6a3.ulid.util.UlidUtilTest; +import com.github.f4b6a3.ulid.ulid.UlidCreatorDefaultTest; +import com.github.f4b6a3.ulid.ulid.UlidCreatorDefaultStringTest; +import com.github.f4b6a3.ulid.ulid.UlidCreatorMonotonicTest; +import com.github.f4b6a3.ulid.ulid.UlidCreatorMonotonicStringTest; import com.github.f4b6a3.ulid.util.UlidValidatorTest; -import com.github.f4b6a3.ulid.util.internal.UlidStructTest; +import com.github.f4b6a3.ulid.util.internal.UlidTest; @RunWith(Suite.class) @Suite.SuiteClasses({ - UlidCreatorUuidTest.class, - UlidCreatorStringTest.class, - UlidSpecCreatorTest.class, - UlidConverterTest.class, - UlidUtilTest.class, - UlidValidatorTest.class, - UlidStructTest.class, + UlidCreatorDefaultTest.class, + UlidCreatorDefaultStringTest.class, + UlidCreatorMonotonicTest.class, + UlidCreatorMonotonicStringTest.class, + UlidSpecCreatorTest.class, + UlidValidatorTest.class, + UlidTest.class, }) /** diff --git a/src/test/java/com/github/f4b6a3/ulid/UniquenessTest.java b/src/test/java/com/github/f4b6a3/ulid/UniquenessTest.java index bb69320..49241dc 100644 --- a/src/test/java/com/github/f4b6a3/ulid/UniquenessTest.java +++ b/src/test/java/com/github/f4b6a3/ulid/UniquenessTest.java @@ -1,8 +1,6 @@ package com.github.f4b6a3.ulid; import java.util.HashSet; -import java.util.UUID; - import com.github.f4b6a3.ulid.UlidCreator; import com.github.f4b6a3.ulid.creator.UlidSpecCreator; import com.github.f4b6a3.ulid.strategy.timestamp.FixedTimestampStretegy; @@ -21,11 +19,11 @@ public class UniquenessTest { private int requestCount; // Number of requests for thread // private long[][] cacheLong; // Store values generated per thread - private HashSet hashSet; + private HashSet hashSet; private boolean verbose; // Show progress or not - // GUID creator based on ULID spec + // ULID Spec creator private UlidSpecCreator creator; /** @@ -92,20 +90,20 @@ public class UniquenessTest { for (int i = 0; i < max; i++) { // Request a UUID - UUID uuid = creator.create(); + Ulid ulid = creator.create(); if (verbose) { // Calculate and show progress progress = (int) ((i * 1.0 / max) * 100); if (progress % 10 == 0) { - System.out.println(String.format("[Thread %06d] %s %s %s%%", id, uuid, i, (int) progress)); + System.out.println(String.format("[Thread %06d] %s %s %s%%", id, ulid, i, (int) progress)); } } synchronized (hashSet) { // Insert the value in cache, if it does not exist in it. - if (!hashSet.add(uuid)) { + if (!hashSet.add(ulid)) { System.err.println( - String.format("[Thread %06d] %s %s %s%% [DUPLICATE]", id, uuid, i, (int) progress)); + String.format("[Thread %06d] %s %s %s%% [DUPLICATE]", id, ulid, i, (int) progress)); } } } @@ -118,7 +116,7 @@ public class UniquenessTest { } public static void execute(boolean verbose, int threadCount, int requestCount) { - UlidSpecCreator creator = UlidCreator.getUlidSpecCreator() + UlidSpecCreator creator = UlidCreator.getMonotonicCreator() .withTimestampStrategy(new FixedTimestampStretegy(System.currentTimeMillis())); UniquenessTest test = new UniquenessTest(threadCount, requestCount, creator, verbose); diff --git a/src/test/java/com/github/f4b6a3/ulid/creator/UlidSpecCreatorMock.java b/src/test/java/com/github/f4b6a3/ulid/creator/UlidSpecCreatorMock.java deleted file mode 100644 index 712f07a..0000000 --- a/src/test/java/com/github/f4b6a3/ulid/creator/UlidSpecCreatorMock.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.github.f4b6a3.ulid.creator; - -import com.github.f4b6a3.ulid.creator.UlidSpecCreator; - -class UlidSpecCreatorMock extends UlidSpecCreator { - - public UlidSpecCreatorMock(long random1, long random2, long randomMax1, long randomMax2, long previousTimestamp) { - super(); - - this.random1 = random1; - this.random2 = random2; - - this.randomMax1 = randomMax1; - this.randomMax2 = randomMax2; - - this.previousTimestamp = previousTimestamp; - } -} \ No newline at end of file diff --git a/src/test/java/com/github/f4b6a3/ulid/creator/UlidSpecCreatorTest.java b/src/test/java/com/github/f4b6a3/ulid/creator/UlidSpecCreatorTest.java index affe83a..eb248de 100644 --- a/src/test/java/com/github/f4b6a3/ulid/creator/UlidSpecCreatorTest.java +++ b/src/test/java/com/github/f4b6a3/ulid/creator/UlidSpecCreatorTest.java @@ -1,6 +1,5 @@ package com.github.f4b6a3.ulid.creator; -import java.math.BigInteger; import java.util.HashSet; import java.util.Random; import java.util.Set; @@ -8,8 +7,8 @@ import java.util.UUID; import org.junit.Test; +import com.github.f4b6a3.ulid.Ulid; import com.github.f4b6a3.ulid.UlidCreator; -import com.github.f4b6a3.ulid.strategy.timestamp.FixedTimestampStretegy; import static org.junit.Assert.*; @@ -17,14 +16,12 @@ public class UlidSpecCreatorTest { private static final int DEFAULT_LOOP_MAX = 1_000_000; - private static final long TIMESTAMP = System.currentTimeMillis(); - - private static final Random RANDOM = new Random(); - protected static final String DUPLICATE_UUID_MSG = "A duplicate ULID was created."; protected static final int THREAD_TOTAL = availableProcessors(); + private static final Random RANDOM = new Random(); + private static int availableProcessors() { int processors = Runtime.getRuntime().availableProcessors(); if (processors < 4) { @@ -34,185 +31,53 @@ public class UlidSpecCreatorTest { } @Test - public void testRandomMostSignificantBits() { - - UlidSpecCreator creator = new UlidSpecCreator(); - creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP)); - - UUID uuid = creator.create(); - long firstRand1 = creator.extractRandom1(uuid); - for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - uuid = creator.create(); - + public void testGetUlidTimestamp() { + for (int i = 0; i < 100; i++) { + long timestamp = RANDOM.nextLong() & 0x0000ffffffffffffL; + Ulid ulid = UlidCreator.getUlid(timestamp); + assertEquals(timestamp, ulid.getTimestamp()); } - - long lastRand1 = creator.extractRandom1(uuid); - long expected1 = firstRand1; - assertEquals(String.format("The last high random should be iqual to the first %s.", expected1), expected1, - lastRand1); - - creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP + 1)); - uuid = creator.create(); - lastRand1 = uuid.getMostSignificantBits(); - assertNotEquals("The last high random should be random after timestamp changed.", firstRand1, lastRand1); } @Test - public void testRandomLeastSignificantBits() { - - UlidSpecCreator creator = new UlidSpecCreator(); - creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP)); - - UUID uuid = creator.create(); - long firstRnd2 = creator.extractRandom2(uuid); - for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - uuid = creator.create(); + public void testGetMonotonicUlidTimestamp() { + for (int i = 0; i < 100; i++) { + long timestamp = RANDOM.nextLong() & 0x0000ffffffffffffL; + Ulid ulid = UlidCreator.getMonotonicUlid(timestamp); + assertEquals(timestamp, ulid.getTimestamp()); } - - long lastRand2 = creator.extractRandom2(uuid); - long expected = firstRnd2 + DEFAULT_LOOP_MAX; - assertEquals(String.format("The last low random should be iqual to %s.", expected), expected, lastRand2); - - long notExpected = firstRnd2 + DEFAULT_LOOP_MAX + 1; - creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP + 1)); - uuid = creator.create(); - lastRand2 = uuid.getLeastSignificantBits(); - assertNotEquals("The last low random should be random after timestamp changed.", notExpected, lastRand2); } @Test - public void testIncrementOfRandomLeastSignificantBits() { - - UlidSpecCreator creator = new UlidSpecCreator(); - creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP)); - - creator.create(); - long random2 = creator.random2; - - UUID uuid = new UUID(0, 0); - for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - uuid = creator.create(); - } - - long expected2 = random2 + DEFAULT_LOOP_MAX; - long rand2 = creator.random2; - assertEquals("Wrong low random after loop.", expected2, rand2); - - rand2 = creator.extractRandom2(uuid); - assertEquals("Wrong low random after loop.", expected2, rand2); - } - - @Test - public void testIncrementOfRandomMostSignificantBits() { - - UlidSpecCreator creator = new UlidSpecCreator(); - creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP)); - - creator.create(); - long random1 = creator.random1; - - UUID uuid = new UUID(0, 0); - for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - uuid = creator.create(); - } - - long expected1 = random1; - long rand1 = creator.random1; - assertEquals("Wrong high random after loop.", expected1, rand1); - - rand1 = creator.extractRandom1(uuid); - assertEquals("Wrong high random after loop.", expected1, rand1); - } - - @Test - public void testIncrementRandomComponentMaximum1() { - - long random1 = 0x000000ffffffffffL; - long random2 = 0x000000ffffffffffL; - - long max1 = random1 | UlidSpecCreatorMock.INCREMENT_MAX; - long max2 = random2 | UlidSpecCreatorMock.INCREMENT_MAX; - - random1 = max1; - random2 = max2 - DEFAULT_LOOP_MAX; - - random2--; // Adjust - - UlidSpecCreatorMock creator = new UlidSpecCreatorMock(random1, random2, max1, max2, TIMESTAMP); - creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP)); - - UUID uuid = new UUID(0, 0); - for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - uuid = creator.create(); - } - - long hi1 = random1 & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT; - long lo1 = random2 & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT; - String concat1 = (Long.toHexString(hi1) + Long.toHexString(lo1)); - BigInteger bigint1 = new BigInteger(concat1, 16); - long hi2 = creator.extractRandom1(uuid); - long lo2 = creator.extractRandom2(uuid); - String concat2 = (Long.toHexString(hi2) + Long.toHexString(lo2)); - BigInteger bigint2 = new BigInteger(concat2, 16); - assertEquals(bigint1.add(BigInteger.valueOf(DEFAULT_LOOP_MAX)), bigint2); - - // This line resets the random component - uuid = creator.create(); - } - - @Test - public void testIncrementRandomComponentMaximum2() { - - long random1 = (RANDOM.nextLong() & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT); - long random2 = (RANDOM.nextLong() & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT); - - long max1 = random1 | UlidSpecCreatorMock.INCREMENT_MAX; - long max2 = random2 | UlidSpecCreatorMock.INCREMENT_MAX; - - random1 = max1; - random2 = max2 - DEFAULT_LOOP_MAX; - - random2--; // Adjust - - UlidSpecCreatorMock creator = new UlidSpecCreatorMock(random1, random2, max1, max2, TIMESTAMP); - creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP)); - - UUID uuid = new UUID(0, 0); - for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - uuid = creator.create(); - } - - long rand1 = creator.extractRandom1(uuid); - long expected1 = (max1 & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT); - assertEquals("Incorrect high random after loop.", expected1, rand1); - - long rand2 = creator.extractRandom2(uuid); - long expected2 = (max2 & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT) - 1; - assertEquals("Incorrect low random after loop.", expected2, rand2); - - long hi1 = random1 & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT; - long lo1 = random2 & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT; - String concat1 = (Long.toHexString(hi1) + Long.toHexString(lo1)); - BigInteger bigint1 = new BigInteger(concat1, 16); - long hi2 = creator.extractRandom1(uuid); - long lo2 = creator.extractRandom2(uuid); - String concat2 = (Long.toHexString(hi2) + Long.toHexString(lo2)); - BigInteger bigint2 = new BigInteger(concat2, 16); - assertEquals(bigint1.add(BigInteger.valueOf(DEFAULT_LOOP_MAX)), bigint2); - - // This line resets the random component - creator.create(); - } - - @Test - public void testGetUlidParallelGeneratorsShouldCreateUniqueUlids() throws InterruptedException { + public void testGetDefaultUlidInParallel() throws InterruptedException { Thread[] threads = new Thread[THREAD_TOTAL]; TestThread.clearHashSet(); // Instantiate and start many threads for (int i = 0; i < THREAD_TOTAL; i++) { - threads[i] = new TestThread(UlidCreator.getUlidSpecCreator(), DEFAULT_LOOP_MAX); + threads[i] = new TestThread(UlidCreator.getDefaultCreator(), DEFAULT_LOOP_MAX); + threads[i].start(); + } + + // Wait all the threads to finish + for (Thread thread : threads) { + thread.join(); + } + + // Check if the quantity of unique UUIDs is correct + assertEquals(DUPLICATE_UUID_MSG, TestThread.hashSet.size(), (DEFAULT_LOOP_MAX * THREAD_TOTAL)); + } + + @Test + public void testGetMonotonicUlidInParallel() throws InterruptedException { + + Thread[] threads = new Thread[THREAD_TOTAL]; + TestThread.clearHashSet(); + + // Instantiate and start many threads + for (int i = 0; i < THREAD_TOTAL; i++) { + threads[i] = new TestThread(UlidCreator.getMonotonicCreator(), DEFAULT_LOOP_MAX); threads[i].start(); } @@ -242,9 +107,10 @@ public class UlidSpecCreatorTest { @Override public void run() { + long timestamp = System.currentTimeMillis(); for (int i = 0; i < loopLimit; i++) { synchronized (hashSet) { - hashSet.add(creator.create()); + hashSet.add(creator.create(timestamp).toUuid()); } } } diff --git a/src/test/java/com/github/f4b6a3/ulid/demo/DemoTest.java b/src/test/java/com/github/f4b6a3/ulid/demo/DemoTest.java index ed951e2..79d6104 100644 --- a/src/test/java/com/github/f4b6a3/ulid/demo/DemoTest.java +++ b/src/test/java/com/github/f4b6a3/ulid/demo/DemoTest.java @@ -10,20 +10,20 @@ public class DemoTest { int max = 100; System.out.println(HORIZONTAL_LINE); - System.out.println("### ULID string"); - System.out.println(HORIZONTAL_LINE); - - for (int i = 0; i < max; i++) { - System.out.println(UlidCreator.getUlidString()); - } - - System.out.println(HORIZONTAL_LINE); - System.out.println("### ULID-based GUID"); + System.out.println("### ULID"); System.out.println(HORIZONTAL_LINE); for (int i = 0; i < max; i++) { System.out.println(UlidCreator.getUlid()); } + + System.out.println(HORIZONTAL_LINE); + System.out.println("### Monotonic ULID"); + System.out.println(HORIZONTAL_LINE); + + for (int i = 0; i < max; i++) { + System.out.println(UlidCreator.getMonotonicUlid()); + } } public static void main(String[] args) { diff --git a/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorDefaultStringTest.java b/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorDefaultStringTest.java new file mode 100644 index 0000000..81869f0 --- /dev/null +++ b/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorDefaultStringTest.java @@ -0,0 +1,65 @@ +package com.github.f4b6a3.ulid.ulid; + +import org.junit.Test; + +import com.github.f4b6a3.ulid.Ulid; +import com.github.f4b6a3.ulid.UlidCreator; +import com.github.f4b6a3.ulid.util.UlidValidator; + +import static org.junit.Assert.*; +import java.util.HashSet; + +public class UlidCreatorDefaultStringTest { + + private static final int ULID_LENGTH = 26; + private static final int DEFAULT_LOOP_MAX = 100_000; + + @Test + public void testGetUlid() { + String[] list = new String[DEFAULT_LOOP_MAX]; + + long startTime = System.currentTimeMillis(); + + for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { + list[i] = UlidCreator.getUlid().toString(); + } + + long endTime = System.currentTimeMillis(); + + checkNullOrInvalid(list); + checkUniqueness(list); + checkCreationTime(list, startTime, endTime); + } + + private void checkNullOrInvalid(String[] list) { + for (String ulid : list) { + assertNotNull("ULID is null", ulid); + assertTrue("ULID is empty", !ulid.isEmpty()); + assertEquals("ULID length is wrong", ULID_LENGTH, ulid.length()); + assertTrue("ULID is not valid", UlidValidator.isValid(ulid)); + } + } + + private void checkUniqueness(String[] list) { + + HashSet set = new HashSet<>(); + + for (String ulid : list) { + assertTrue(String.format("ULID is duplicated %s", ulid), set.add(ulid)); + } + + assertEquals("There are duplicated ULIDs", set.size(), list.length); + } + + private void checkCreationTime(String[] list, long startTime, long endTime) { + + assertTrue("Start time was after end time", startTime <= endTime); + + for (String ulid : list) { + long creationTime = Ulid.of(ulid).getTimestamp(); + assertTrue("Creation time was before start time " + creationTime + " " + startTime, + creationTime >= startTime); + assertTrue("Creation time was after end time", creationTime <= endTime); + } + } +} diff --git a/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorDefaultTest.java b/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorDefaultTest.java new file mode 100644 index 0000000..8a6037b --- /dev/null +++ b/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorDefaultTest.java @@ -0,0 +1,60 @@ +package com.github.f4b6a3.ulid.ulid; + +import org.junit.Test; + +import com.github.f4b6a3.ulid.Ulid; +import com.github.f4b6a3.ulid.UlidCreator; + +import static org.junit.Assert.*; +import java.util.HashSet; + +public class UlidCreatorDefaultTest { + + private static final int DEFAULT_LOOP_MAX = 100_000; + + @Test + public void testGetUlid() { + Ulid[] list = new Ulid[DEFAULT_LOOP_MAX]; + + long startTime = System.currentTimeMillis(); + + for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { + list[i] = UlidCreator.getUlid(); + } + + long endTime = System.currentTimeMillis(); + + checkNullOrInvalid(list); + checkUniqueness(list); + checkCreationTime(list, startTime, endTime); + } + + private void checkNullOrInvalid(Ulid[] list) { + for (Ulid ulid : list) { + assertNotNull("ULID is null", ulid); + } + } + + private void checkUniqueness(Ulid[] list) { + + HashSet set = new HashSet<>(); + + for (Ulid ulid : list) { + assertTrue(String.format("ULID is duplicated %s", ulid), set.add(ulid)); + } + + assertEquals("There are duplicated ULIDs", set.size(), list.length); + } + + private void checkCreationTime(Ulid[] list, long startTime, long endTime) { + + assertTrue("Start time was after end time", startTime <= endTime); + + for (Ulid ulid : list) { + long creationTime = ulid.getTimestamp(); + assertTrue("Creation time was before start time " + creationTime + " " + startTime, + creationTime >= startTime); + assertTrue("Creation time was after end time", creationTime <= endTime); + } + } +} diff --git a/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorStringTest.java b/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorMonotonicStringTest.java similarity index 65% rename from src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorStringTest.java rename to src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorMonotonicStringTest.java index b3404f8..83bd5a3 100644 --- a/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorStringTest.java +++ b/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorMonotonicStringTest.java @@ -2,17 +2,15 @@ package com.github.f4b6a3.ulid.ulid; import org.junit.Test; +import com.github.f4b6a3.ulid.Ulid; import com.github.f4b6a3.ulid.UlidCreator; -import com.github.f4b6a3.ulid.util.UlidConverter; -import com.github.f4b6a3.ulid.util.UlidUtil; import com.github.f4b6a3.ulid.util.UlidValidator; import static org.junit.Assert.*; import java.util.Arrays; import java.util.HashSet; -import java.util.UUID; -public class UlidCreatorStringTest { +public class UlidCreatorMonotonicStringTest { private static final int ULID_LENGTH = 26; private static final int DEFAULT_LOOP_MAX = 100_000; @@ -24,7 +22,7 @@ public class UlidCreatorStringTest { long startTime = System.currentTimeMillis(); for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - list[i] = UlidCreator.getUlidString(); + list[i] = UlidCreator.getMonotonicUlid().toString(); } long endTime = System.currentTimeMillis(); @@ -34,25 +32,6 @@ public class UlidCreatorStringTest { checkOrdering(list); checkCreationTime(list, startTime, endTime); } - - @Test - public void testGetUlid4() { - String[] list = new String[DEFAULT_LOOP_MAX]; - - long startTime = System.currentTimeMillis(); - - for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - list[i] = UlidCreator.getUlidString4(); - } - - long endTime = System.currentTimeMillis(); - - checkNullOrInvalid(list); - checkUniqueness(list); - checkOrdering(list); - checkCreationTime(list, startTime, endTime); - checkVersion4(list); - } private void checkNullOrInvalid(String[] list) { for (String ulid : list) { @@ -79,7 +58,7 @@ public class UlidCreatorStringTest { assertTrue("Start time was after end time", startTime <= endTime); for (String ulid : list) { - long creationTime = UlidUtil.extractUnixMilliseconds(ulid); + long creationTime = Ulid.of(ulid).getTimestamp(); assertTrue("Creation time was before start time " + creationTime + " " + startTime, creationTime >= startTime); assertTrue("Creation time was after end time", creationTime <= endTime); @@ -94,12 +73,4 @@ public class UlidCreatorStringTest { assertEquals("The ULID list is not ordered", list[i], other[i]); } } - - private void checkVersion4(String[] list) { - for (String ulid : list) { - UUID uuid = UlidConverter.fromString(ulid); - assertEquals(String.format("ULID is is not RFC-4122 version 4 %s", uuid), 4, uuid.version()); - assertEquals(String.format("ULID is is not RFC-4122 variant 2 %s", uuid), 2, uuid.variant()); - } - } } diff --git a/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorMonotonicTest.java b/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorMonotonicTest.java new file mode 100644 index 0000000..e61cfba --- /dev/null +++ b/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorMonotonicTest.java @@ -0,0 +1,71 @@ +package com.github.f4b6a3.ulid.ulid; + +import org.junit.Test; + +import com.github.f4b6a3.ulid.Ulid; +import com.github.f4b6a3.ulid.UlidCreator; + +import static org.junit.Assert.*; +import java.util.Arrays; +import java.util.HashSet; + +public class UlidCreatorMonotonicTest { + + private static final int DEFAULT_LOOP_MAX = 100_000; + + @Test + public void testGetUlid() { + Ulid[] list = new Ulid[DEFAULT_LOOP_MAX]; + + long startTime = System.currentTimeMillis(); + + for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { + list[i] = UlidCreator.getMonotonicUlid(); + } + + long endTime = System.currentTimeMillis(); + + checkNullOrInvalid(list); + checkUniqueness(list); + checkOrdering(list); + checkCreationTime(list, startTime, endTime); + } + + private void checkNullOrInvalid(Ulid[] list) { + for (Ulid ulid : list) { + assertNotNull("ULID is null", ulid); + } + } + + private void checkUniqueness(Ulid[] list) { + + HashSet set = new HashSet<>(); + + for (Ulid ulid : list) { + assertTrue(String.format("ULID is duplicated %s", ulid), set.add(ulid)); + } + + assertEquals("There are duplicated ULIDs", set.size(), list.length); + } + + private void checkCreationTime(Ulid[] list, long startTime, long endTime) { + + assertTrue("Start time was after end time", startTime <= endTime); + + for (Ulid ulid : list) { + long creationTime = ulid.getTimestamp(); + assertTrue("Creation time was before start time " + creationTime + " " + startTime, + creationTime >= startTime); + assertTrue("Creation time was after end time", creationTime <= endTime); + } + } + + private void checkOrdering(Ulid[] list) { + Ulid[] other = Arrays.copyOf(list, list.length); + Arrays.sort(other); + + for (int i = 0; i < list.length; i++) { + assertEquals("The ULID list is not ordered", list[i], other[i]); + } + } +} diff --git a/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorUuidTest.java b/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorUuidTest.java deleted file mode 100644 index fd43340..0000000 --- a/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorUuidTest.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.github.f4b6a3.ulid.ulid; - -import org.junit.Test; - -import com.github.f4b6a3.ulid.UlidCreator; -import com.github.f4b6a3.ulid.util.UlidUtil; - -import static org.junit.Assert.*; -import java.util.Arrays; -import java.util.HashSet; -import java.util.UUID; - -public class UlidCreatorUuidTest { - - private static final int DEFAULT_LOOP_MAX = 100_000; - - @Test - public void testGetUlid() { - UUID[] list = new UUID[DEFAULT_LOOP_MAX]; - - long startTime = System.currentTimeMillis(); - - for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - list[i] = UlidCreator.getUlid(); - } - - long endTime = System.currentTimeMillis(); - - checkNullOrInvalid(list); - checkUniqueness(list); - checkOrdering(list); - checkCreationTime(list, startTime, endTime); - } - - @Test - public void testGetUlid4() { - UUID[] list = new UUID[DEFAULT_LOOP_MAX]; - - long startTime = System.currentTimeMillis(); - - for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - list[i] = UlidCreator.getUlid4(); - } - - long endTime = System.currentTimeMillis(); - - checkNullOrInvalid(list); - checkUniqueness(list); - checkOrdering(list); - checkCreationTime(list, startTime, endTime); - checkVersion4(list); - } - - private void checkNullOrInvalid(UUID[] list) { - for (UUID ulid : list) { - assertNotNull("ULID is null", ulid); - } - } - - private void checkUniqueness(UUID[] list) { - - HashSet set = new HashSet<>(); - - for (UUID ulid : list) { - assertTrue(String.format("ULID is duplicated %s", ulid), set.add(ulid)); - } - - assertEquals("There are duplicated ULIDs", set.size(), list.length); - } - - private void checkCreationTime(UUID[] list, long startTime, long endTime) { - - assertTrue("Start time was after end time", startTime <= endTime); - - for (UUID ulid : list) { - long creationTime = UlidUtil.extractUnixMilliseconds(ulid); - assertTrue("Creation time was before start time " + creationTime + " " + startTime, - creationTime >= startTime); - assertTrue("Creation time was after end time", creationTime <= endTime); - } - } - - private void checkOrdering(UUID[] list) { - UUID[] other = Arrays.copyOf(list, list.length); - Arrays.sort(other); - - for (int i = 0; i < list.length; i++) { - assertEquals("The ULID list is not ordered", list[i], other[i]); - } - } - - private void checkVersion4(UUID[] list) { - for (UUID uuid : list) { - assertEquals(String.format("ULID is is not RFC-4122 version 4 %s", uuid), 4, uuid.version()); - assertEquals(String.format("ULID is is not RFC-4122 variant 2 %s", uuid), 2, uuid.variant()); - } - } -} diff --git a/src/test/java/com/github/f4b6a3/ulid/util/UlidConverterTest.java b/src/test/java/com/github/f4b6a3/ulid/util/UlidConverterTest.java deleted file mode 100644 index 80927ed..0000000 --- a/src/test/java/com/github/f4b6a3/ulid/util/UlidConverterTest.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.github.f4b6a3.ulid.util; - -import static org.junit.Assert.*; - -import java.util.Random; -import java.util.UUID; - -import org.junit.Test; - -import com.github.f4b6a3.ulid.UlidCreator; -import com.github.f4b6a3.ulid.util.UlidConverter; -import com.github.f4b6a3.ulid.util.UlidValidator; -import com.github.f4b6a3.ulid.util.internal.UlidStruct; - -public class UlidConverterTest { - - private static final int ULID_LENGTH = 26; - private static final int DEFAULT_LOOP_MAX = 100_000; - - @Test - public void testToAndFromUlid() { - - for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - - UUID uuid1 = UlidCreator.getUlid(); - String ulid = UlidConverter.toString(uuid1); - - assertNotNull("ULID is null", ulid); - assertTrue("ULID is empty", !ulid.isEmpty()); - assertEquals("ULID length is wrong ", ULID_LENGTH, ulid.length()); - assertTrue("ULID is not valid", UlidValidator.isValid(ulid)); - - UUID uuid2 = UlidConverter.fromString(ulid); - assertEquals("Result ULID is different from original ULID", uuid1, uuid2); - - } - } - - @Test - public void testToString1() { - - for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - - Random random = new Random(); - final long time = random.nextLong(); - final long random1 = random.nextLong(); - final long random2 = random.nextLong(); - UlidStruct struct0 = UlidStruct.of(time, random1, random2); - - String string1 = struct0.toString(); - UlidStruct struct1 = UlidStruct.of(string1); - - assertEquals(struct0.time, struct1.time); - assertEquals(struct0.random1, struct1.random1); - assertEquals(struct0.random2, struct1.random2); - } - } - - @Test - public void testToString2() { - - for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - - UUID ulid0 = UlidCreator.getUlid(); - UlidStruct struct0 = UlidStruct.of(ulid0); - - String string1 = UlidConverter.toString(ulid0); - UlidStruct struct1 = UlidStruct.of(string1); - - assertEquals(struct0.time, struct1.time); - assertEquals(struct0.random1, struct1.random1); - assertEquals(struct0.random2, struct1.random2); - } - } - - @Test - public void testToString3() { - - for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - - UUID ulid0 = UlidCreator.getUlid(); - UlidStruct struct0 = UlidStruct.of(ulid0); - - String string1 = struct0.toString(); - UlidStruct struct1 = UlidStruct.of(string1); - - assertEquals(struct0.time, struct1.time); - assertEquals(struct0.random1, struct1.random1); - assertEquals(struct0.random2, struct1.random2); - } - } - - @Test - public void testToString4() { - - for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - - UUID ulid0 = UlidCreator.getUlid(); - UlidStruct struct0 = UlidStruct.of(ulid0); - - String string1 = UlidConverter.toString(ulid0); - UlidStruct struct1 = UlidStruct.of(string1); - - String string2 = struct0.toString(); - UlidStruct struct2 = UlidStruct.of(string2); - - assertEquals(string1, string2); - - assertEquals(struct0.time, struct1.time); - assertEquals(struct0.random1, struct1.random1); - assertEquals(struct0.random2, struct1.random2); - - assertEquals(struct0.time, struct2.time); - assertEquals(struct0.random1, struct2.random1); - assertEquals(struct0.random2, struct2.random2); - } - } -} diff --git a/src/test/java/com/github/f4b6a3/ulid/util/UlidUtilTest.java b/src/test/java/com/github/f4b6a3/ulid/util/UlidUtilTest.java deleted file mode 100644 index d9eafc4..0000000 --- a/src/test/java/com/github/f4b6a3/ulid/util/UlidUtilTest.java +++ /dev/null @@ -1,156 +0,0 @@ -package com.github.f4b6a3.ulid.util; - -import static org.junit.Assert.*; -import java.time.Instant; -import java.util.Random; - -import org.junit.Test; - -import com.github.f4b6a3.ulid.exception.InvalidUlidException; -import com.github.f4b6a3.ulid.util.internal.UlidStructTest; - -import static com.github.f4b6a3.ulid.util.UlidUtil.*; - -public class UlidUtilTest { - - // Date: 10889-08-02T05:31:50.655Z: 281474976710655 (2^48-1) - private static final long TIMESTAMP_MAX = 0xffffffffffffL; - private static final long HALF_RANDOM_MAX = 0xffffffffffL; - - private static final int DEFAULT_LOOP_MAX = 100_000; - - private static final Random RANDOM = new Random(); - - @Test - public void testExtractTimestamp1() { - for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - - long time = RANDOM.nextLong() & TIMESTAMP_MAX; - long random1 = RANDOM.nextLong() & HALF_RANDOM_MAX; - long random2 = RANDOM.nextLong() & HALF_RANDOM_MAX; - - String timeComponent = UlidStructTest.toTimeComponent(time); - String randomComponent = UlidStructTest.toRandomComponent(random1, random2); - - String ulid = timeComponent + randomComponent; - long result = extractTimestamp(ulid); - assertEquals(time, result); - } - } - - @Test - public void testExtractTimestamp2() { - for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - - String ulid; - String timeComponent; - String randomComponent; - - long time; - long random1; - long random2; - - time = RANDOM.nextLong() & TIMESTAMP_MAX; - random1 = RANDOM.nextLong() & HALF_RANDOM_MAX; - random2 = RANDOM.nextLong() & HALF_RANDOM_MAX; - - timeComponent = UlidStructTest.toTimeComponent(time); - randomComponent = UlidStructTest.toRandomComponent(random1, random2); - - timeComponent = "7ZZZZZZZZZ"; - ulid = timeComponent + randomComponent; - time = extractTimestamp(ulid); - assertEquals(TIMESTAMP_MAX, time); - - timeComponent = "0000000000"; - ulid = timeComponent + randomComponent; - time = extractTimestamp(ulid); - assertEquals(0, time); - - try { - // Test the first extra bit added by the base32 encoding - char[] chars = timeComponent.toCharArray(); - chars[0] = 'G'; // GZZZZZZZZZ - timeComponent = new String(chars); - ulid = timeComponent + randomComponent; - extractTimestamp(ulid); - fail("Should throw an InvalidUlidException"); - } catch (InvalidUlidException e) { - // success - } - - try { - // Test the second extra bit added by the base32 encoding - char[] chars = timeComponent.toCharArray(); - chars[0] = '8'; // 8ZZZZZZZZZ - timeComponent = new String(chars); - ulid = timeComponent + randomComponent; - extractTimestamp(ulid); - fail("Should throw an InvalidUlidException"); - } catch (InvalidUlidException e) { - // success - } - } - } - - @Test - public void testExtractUnixMilliseconds() { - for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - long time = RANDOM.nextLong() & TIMESTAMP_MAX; - long random1 = RANDOM.nextLong() & HALF_RANDOM_MAX; - long random2 = RANDOM.nextLong() & HALF_RANDOM_MAX; - String ulid = UlidStructTest.toString(time, random1, random2); - long result = extractUnixMilliseconds(ulid); - assertEquals(time, result); - } - } - - @Test - public void testExtractInstant() { - for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - long time = RANDOM.nextLong() & TIMESTAMP_MAX; - Instant instant = Instant.ofEpochMilli(time); - long random1 = RANDOM.nextLong() & HALF_RANDOM_MAX; - long random2 = RANDOM.nextLong() & HALF_RANDOM_MAX; - String ulid = UlidStructTest.toString(time, random1, random2); - Instant result = extractInstant(ulid); - assertEquals(instant, result); - } - } - - @Test - public void testExtractTimestampComponent() { - for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - long time = RANDOM.nextLong() & TIMESTAMP_MAX; - long random1 = RANDOM.nextLong() & HALF_RANDOM_MAX; - long random2 = RANDOM.nextLong() & HALF_RANDOM_MAX; - String ulid = UlidStructTest.toString(time, random1, random2); - - char[] chars = ulid.toCharArray(); - char[] timeComponent = new char[10]; - System.arraycopy(chars, 0, timeComponent, 0, 10); - String expected = new String(timeComponent); - - String result = extractTimestampComponent(ulid); - assertEquals(expected, result); - } - } - - @Test - public void testExtractRandomnessComponent() { - for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - long time = RANDOM.nextLong() & TIMESTAMP_MAX; - long random1 = RANDOM.nextLong() & HALF_RANDOM_MAX; - long random2 = RANDOM.nextLong() & HALF_RANDOM_MAX; - String ulid = UlidStructTest.toString(time, random1, random2); - - char[] chars = ulid.toCharArray(); - char[] randomComponent = new char[16]; - System.arraycopy(chars, 10, randomComponent, 0, 16); - String expected = new String(randomComponent); - - String result = extractRandomnessComponent(ulid); - assertEquals(expected, result); - } - } -} diff --git a/src/test/java/com/github/f4b6a3/ulid/util/internal/UlidStructTest.java b/src/test/java/com/github/f4b6a3/ulid/util/internal/UlidTest.java similarity index 55% rename from src/test/java/com/github/f4b6a3/ulid/util/internal/UlidStructTest.java rename to src/test/java/com/github/f4b6a3/ulid/util/internal/UlidTest.java index f303013..b7e1f3b 100644 --- a/src/test/java/com/github/f4b6a3/ulid/util/internal/UlidStructTest.java +++ b/src/test/java/com/github/f4b6a3/ulid/util/internal/UlidTest.java @@ -7,7 +7,9 @@ import java.util.UUID; import org.junit.Test; -public class UlidStructTest { +import com.github.f4b6a3.ulid.Ulid; + +public class UlidTest { private static final int DEFAULT_LOOP_MAX = 100_000; @@ -19,43 +21,17 @@ public class UlidStructTest { for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { UUID uuid0 = UUID.randomUUID(); String string0 = toString(uuid0); - String string1 = UlidStruct.of(string0).toString(); + String string1 = Ulid.of(string0).toString(); assertEquals(string0, string1); } - - // Test RFC-4122 UUID version 4 - final long versionMask = 0xffffffffffff0fffL; - final long variantMask = 0x3fffffffffffffffL; - for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - UUID uuid0 = UUID.randomUUID(); - String string0 = toString(uuid0); - String string1 = UlidStruct.of(string0).toString4(); // UUID v4 in base32 - UUID uuid1 = toUuid(fromString(string1)); - assertEquals(uuid0.getMostSignificantBits() & versionMask, uuid1.getMostSignificantBits() & versionMask); - assertEquals(uuid0.getLeastSignificantBits() & variantMask, uuid1.getLeastSignificantBits() & variantMask); - assertEquals(4, uuid1.version()); - assertEquals(2, uuid1.variant()); - } } @Test public void testOfAndToUuid() { for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { UUID uuid0 = UUID.randomUUID(); - UUID uuid1 = UlidStruct.of(uuid0).toUuid(); - assertEquals(uuid0, uuid1); - } - - // Test RFC-4122 UUID version 4 - final long versionMask = 0xffffffffffff0fffL; - final long variantMask = 0x3fffffffffffffffL; - for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - UUID uuid0 = UUID.randomUUID(); - UUID uuid1 = UlidStruct.of(uuid0).toUuid4(); // UUID v4 - assertEquals(uuid0.getMostSignificantBits() & versionMask, uuid1.getMostSignificantBits() & versionMask); - assertEquals(uuid0.getLeastSignificantBits() & variantMask, uuid1.getLeastSignificantBits() & variantMask); - assertEquals(4, uuid1.version()); - assertEquals(2, uuid1.variant()); + UUID uuid1 = Ulid.of(uuid0).toUuid(); + assertEquals(uuid0.toString(), uuid1.toString()); } } @@ -63,14 +39,12 @@ public class UlidStructTest { public void testConstructorLongs() { for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { Random random = new Random(); - final long time = random.nextLong(); - final long random1 = random.nextLong(); - final long random2 = random.nextLong(); - UlidStruct struct0 = UlidStruct.of(time, random1, random2); // <-- under test + final long msb = random.nextLong(); + final long lsb = random.nextLong(); + Ulid ulid0 = Ulid.of(msb, lsb); // <-- under test - assertEquals(time & 0xffffffffffffL, struct0.time); - assertEquals(random1 & 0xffffffffffL, struct0.random1); - assertEquals(random2 & 0xffffffffffL, struct0.random2); + assertEquals(msb, ulid0.toUuid().getMostSignificantBits()); + assertEquals(lsb, ulid0.toUuid().getLeastSignificantBits()); } } @@ -78,14 +52,13 @@ public class UlidStructTest { public void testConstructorString() { for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { Random random = new Random(); - final long time = random.nextLong(); final long random1 = random.nextLong(); final long random2 = random.nextLong(); - UlidStruct struct0 = UlidStruct.of(time, random1, random2); + Ulid ulid0 = Ulid.of(random1, random2); - String string1 = toString(struct0); - UlidStruct struct1 = UlidStruct.of(string1); // <-- under test - assertEquals(struct0, struct1); + String string1 = toString(ulid0); + Ulid struct1 = Ulid.of(string1); // <-- under test + assertEquals(ulid0, struct1); } } @@ -96,7 +69,7 @@ public class UlidStructTest { final long msb = random.nextLong(); final long lsb = random.nextLong(); final UUID uuid0 = new UUID(msb, lsb); - UlidStruct struct0 = UlidStruct.of(uuid0); // <-- under test + Ulid struct0 = Ulid.of(uuid0); // <-- under test UUID uuid1 = toUuid(struct0); assertEquals(uuid0, uuid1); @@ -108,13 +81,12 @@ public class UlidStructTest { for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { Random random = new Random(); - final long time = random.nextLong(); final long random1 = random.nextLong(); final long random2 = random.nextLong(); - UlidStruct struct0 = UlidStruct.of(time, random1, random2); + Ulid ulid0 = Ulid.of(random1, random2); - String string1 = toString(struct0); - String string2 = struct0.toString(); // <-- under test + String string1 = toString(ulid0); + String string2 = ulid0.toString(); // <-- under test assertEquals(string1, string2); } } @@ -124,18 +96,17 @@ public class UlidStructTest { for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { Random random = new Random(); - final long time = random.nextLong(); final long random1 = random.nextLong(); final long random2 = random.nextLong(); - UlidStruct struct0 = UlidStruct.of(time, random1, random2); + Ulid ulid0 = Ulid.of(random1, random2); - UUID uuid1 = toUuid(struct0); - UUID uuid2 = struct0.toUuid(); // <-- under test + UUID uuid1 = toUuid(ulid0); + UUID uuid2 = ulid0.toUuid(); // <-- under test assertEquals(uuid1, uuid2); } } - public static UlidStruct fromString(String string) { + public static Ulid fromString(String string) { long time = 0; long random1 = 0; @@ -153,17 +124,16 @@ public class UlidStructTest { random1 = Long.parseUnsignedLong(r1, 32); random2 = Long.parseUnsignedLong(r2, 32); - return UlidStruct.of(time, random1, random2); + long msb = (time << 16) | (random1 >>> 24); + long lsb = (random1 << 40) | (random2 & 0xffffffffffL); + + return Ulid.of(msb, lsb); } - public static UUID toUuid(UlidStruct struct) { + public static UUID toUuid(Ulid struct) { - long time = struct.time & 0xffffffffffffL; - long random1 = struct.random1 & 0xffffffffffL; - long random2 = struct.random2 & 0xffffffffffL; - - final long msb = (time << 16) | (random1 >>> 24); - final long lsb = (random1 << 40) | random2; + long msb = struct.toUuid().getMostSignificantBits(); + long lsb = struct.toUuid().getLeastSignificantBits(); return new UUID(msb, lsb); } @@ -180,38 +150,36 @@ public class UlidStructTest { return new UUID(msb, lsb); } - public static String toString(UlidStruct struct) { - return toString(struct.time, struct.random1, struct.random2); + public static String toString(Ulid ulid) { + return toString(ulid.toUuid().getMostSignificantBits(), ulid.toUuid().getLeastSignificantBits()); } public static String toString(UUID uuid) { final long msb = uuid.getMostSignificantBits(); final long lsb = uuid.getLeastSignificantBits(); - - final long time = (msb >>> 16); - final long random1 = ((msb & 0xffffL) << 24) | (lsb >>> 40); - final long random2 = (lsb & 0xffffffffffL); - - return toString(time, random1, random2); + return toString(msb, lsb); } - public static String toString(final long time, final long random1, final long random2) { - String timeComponent = toTimeComponent(time); - String randomComponent = toRandomComponent(random1, random2); + public static String toString(final long msb, final long lsb) { + String timeComponent = toTimeComponent(msb >>> 16); + String randomComponent = toRandomComponent(msb, lsb); return timeComponent + randomComponent; } - + public static String toTimeComponent(final long time) { final String tzero = "0000000000"; String tm = Long.toUnsignedString(time, 32); tm = tzero.substring(0, tzero.length() - tm.length()) + tm; return transliterate(tm, ALPHABET_JAVA, ALPHABET_CROCKFORD); } - - public static String toRandomComponent(final long random1, final long random2) { + + public static String toRandomComponent(final long msb, final long lsb) { final String zeros = "00000000"; + final long random1 = ((msb & 0xffffL) << 24) | (lsb >>> 40); + final long random2 = (lsb & 0xffffffffffL); + String r1 = Long.toUnsignedString(random1, 32); String r2 = Long.toUnsignedString(random2, 32);