diff --git a/src/main/java/com/github/f4b6a3/ulid/Ulid.java b/src/main/java/com/github/f4b6a3/ulid/Ulid.java index 4553a60..a21c505 100644 --- a/src/main/java/com/github/f4b6a3/ulid/Ulid.java +++ b/src/main/java/com/github/f4b6a3/ulid/Ulid.java @@ -25,6 +25,7 @@ package com.github.f4b6a3.ulid; import java.io.Serializable; +import java.time.Instant; import java.util.UUID; import com.github.f4b6a3.ulid.util.UlidValidator; @@ -37,99 +38,134 @@ public final class Ulid implements Serializable, Comparable { private final long msb; private final long lsb; - protected static final int STRING_LENGTH = 26; + protected static final long TIME_MAX = 281474976710655L; // 2^48 - 1 - protected static final char[] BASE32_CHARS = // + public static final int ULID_LENGTH = 26; + public static final int TIME_LENGTH = 10; + public static final int RANDOM_LENGTH = 16; + + public static final int ULID_BYTES_LENGTH = 16; + public static final int TIME_BYTES_LENGTH = 6; + public static final int RANDOM_BYTES_LENGTH = 10; + + protected static final char[] ENCODING_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' }; - protected static final long[] BASE32_VALUES = new long[128]; + protected static final long[] ENCODING_VALUES = new long[128]; static { - for (int i = 0; i < BASE32_VALUES.length; i++) { - BASE32_VALUES[i] = -1; + for (int i = 0; i < ENCODING_VALUES.length; i++) { + ENCODING_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; + ENCODING_VALUES['0'] = 0x00; + ENCODING_VALUES['1'] = 0x01; + ENCODING_VALUES['2'] = 0x02; + ENCODING_VALUES['3'] = 0x03; + ENCODING_VALUES['4'] = 0x04; + ENCODING_VALUES['5'] = 0x05; + ENCODING_VALUES['6'] = 0x06; + ENCODING_VALUES['7'] = 0x07; + ENCODING_VALUES['8'] = 0x08; + ENCODING_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; + ENCODING_VALUES['a'] = 0x0a; + ENCODING_VALUES['b'] = 0x0b; + ENCODING_VALUES['c'] = 0x0c; + ENCODING_VALUES['d'] = 0x0d; + ENCODING_VALUES['e'] = 0x0e; + ENCODING_VALUES['f'] = 0x0f; + ENCODING_VALUES['g'] = 0x10; + ENCODING_VALUES['h'] = 0x11; + ENCODING_VALUES['j'] = 0x12; + ENCODING_VALUES['k'] = 0x13; + ENCODING_VALUES['m'] = 0x14; + ENCODING_VALUES['n'] = 0x15; + ENCODING_VALUES['p'] = 0x16; + ENCODING_VALUES['q'] = 0x17; + ENCODING_VALUES['r'] = 0x18; + ENCODING_VALUES['s'] = 0x19; + ENCODING_VALUES['t'] = 0x1a; + ENCODING_VALUES['v'] = 0x1b; + ENCODING_VALUES['w'] = 0x1c; + ENCODING_VALUES['x'] = 0x1d; + ENCODING_VALUES['y'] = 0x1e; + ENCODING_VALUES['z'] = 0x1f; // Lower case OIL - BASE32_VALUES['o'] = 0x00; - BASE32_VALUES['i'] = 0x01; - BASE32_VALUES['l'] = 0x01; + ENCODING_VALUES['o'] = 0x00; + ENCODING_VALUES['i'] = 0x01; + ENCODING_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; + ENCODING_VALUES['A'] = 0x0a; + ENCODING_VALUES['B'] = 0x0b; + ENCODING_VALUES['C'] = 0x0c; + ENCODING_VALUES['D'] = 0x0d; + ENCODING_VALUES['E'] = 0x0e; + ENCODING_VALUES['F'] = 0x0f; + ENCODING_VALUES['G'] = 0x10; + ENCODING_VALUES['H'] = 0x11; + ENCODING_VALUES['J'] = 0x12; + ENCODING_VALUES['K'] = 0x13; + ENCODING_VALUES['M'] = 0x14; + ENCODING_VALUES['N'] = 0x15; + ENCODING_VALUES['P'] = 0x16; + ENCODING_VALUES['Q'] = 0x17; + ENCODING_VALUES['R'] = 0x18; + ENCODING_VALUES['S'] = 0x19; + ENCODING_VALUES['T'] = 0x1a; + ENCODING_VALUES['V'] = 0x1b; + ENCODING_VALUES['W'] = 0x1c; + ENCODING_VALUES['X'] = 0x1d; + ENCODING_VALUES['Y'] = 0x1e; + ENCODING_VALUES['Z'] = 0x1f; // Upper case OIL - BASE32_VALUES['O'] = 0x00; - BASE32_VALUES['I'] = 0x01; - BASE32_VALUES['L'] = 0x01; + ENCODING_VALUES['O'] = 0x00; + ENCODING_VALUES['I'] = 0x01; + ENCODING_VALUES['L'] = 0x01; } private static final long serialVersionUID = 2625269413446854731L; - private Ulid() { - this.msb = 0; - this.lsb = 0; + public Ulid(long mostSignificantBits, long leastSignificantBits) { + this.msb = mostSignificantBits; + this.lsb = leastSignificantBits; } - private Ulid(UUID ulid) { - this.msb = ulid.getMostSignificantBits(); - this.lsb = ulid.getLeastSignificantBits(); + // TODO: test + public static Ulid of(byte[] bytes) { + + if (bytes == null || bytes.length != ULID_BYTES_LENGTH) { + throw new IllegalArgumentException("Invalid ULID bytes"); + } + + long long0 = 0; + long long1 = 0; + + long0 |= (bytes[0x0] & 0xffL) << 56; + long0 |= (bytes[0x1] & 0xffL) << 48; + long0 |= (bytes[0x2] & 0xffL) << 40; + long0 |= (bytes[0x3] & 0xffL) << 32; + long0 |= (bytes[0x4] & 0xffL) << 24; + long0 |= (bytes[0x5] & 0xffL) << 16; + long0 |= (bytes[0x6] & 0xffL) << 8; + long0 |= (bytes[0x7] & 0xffL); + + long1 |= (bytes[0x8] & 0xffL) << 56; + long1 |= (bytes[0x9] & 0xffL) << 48; + long1 |= (bytes[0xa] & 0xffL) << 40; + long1 |= (bytes[0xb] & 0xffL) << 32; + long1 |= (bytes[0xc] & 0xffL) << 24; + long1 |= (bytes[0xd] & 0xffL) << 16; + long1 |= (bytes[0xe] & 0xffL) << 8; + long1 |= (bytes[0xf] & 0xffL); + + return new Ulid(long0, long1); } - private Ulid(String ulid) { + // TODO: optimize + public static Ulid of(String ulid) { final char[] chars = ulid == null ? new char[0] : ulid.toCharArray(); UlidValidator.validate(chars); @@ -138,64 +174,109 @@ public final class Ulid implements Serializable, Comparable { long r1 = 0; long r2 = 0; - tm |= BASE32_VALUES[chars[0x00]] << 45; - tm |= BASE32_VALUES[chars[0x01]] << 40; - tm |= BASE32_VALUES[chars[0x02]] << 35; - tm |= BASE32_VALUES[chars[0x03]] << 30; - tm |= BASE32_VALUES[chars[0x04]] << 25; - tm |= BASE32_VALUES[chars[0x05]] << 20; - tm |= BASE32_VALUES[chars[0x06]] << 15; - tm |= BASE32_VALUES[chars[0x07]] << 10; - tm |= BASE32_VALUES[chars[0x08]] << 5; - tm |= BASE32_VALUES[chars[0x09]]; + tm |= ENCODING_VALUES[chars[0x00]] << 45; + tm |= ENCODING_VALUES[chars[0x01]] << 40; + tm |= ENCODING_VALUES[chars[0x02]] << 35; + tm |= ENCODING_VALUES[chars[0x03]] << 30; + tm |= ENCODING_VALUES[chars[0x04]] << 25; + tm |= ENCODING_VALUES[chars[0x05]] << 20; + tm |= ENCODING_VALUES[chars[0x06]] << 15; + tm |= ENCODING_VALUES[chars[0x07]] << 10; + tm |= ENCODING_VALUES[chars[0x08]] << 5; + tm |= ENCODING_VALUES[chars[0x09]]; - r1 |= BASE32_VALUES[chars[0x0a]] << 35; - r1 |= BASE32_VALUES[chars[0x0b]] << 30; - r1 |= BASE32_VALUES[chars[0x0c]] << 25; - r1 |= BASE32_VALUES[chars[0x0d]] << 20; - r1 |= BASE32_VALUES[chars[0x0e]] << 15; - r1 |= BASE32_VALUES[chars[0x0f]] << 10; - r1 |= BASE32_VALUES[chars[0x10]] << 5; - r1 |= BASE32_VALUES[chars[0x11]]; + r1 |= ENCODING_VALUES[chars[0x0a]] << 35; + r1 |= ENCODING_VALUES[chars[0x0b]] << 30; + r1 |= ENCODING_VALUES[chars[0x0c]] << 25; + r1 |= ENCODING_VALUES[chars[0x0d]] << 20; + r1 |= ENCODING_VALUES[chars[0x0e]] << 15; + r1 |= ENCODING_VALUES[chars[0x0f]] << 10; + r1 |= ENCODING_VALUES[chars[0x10]] << 5; + r1 |= ENCODING_VALUES[chars[0x11]]; - r2 |= BASE32_VALUES[chars[0x12]] << 35; - r2 |= BASE32_VALUES[chars[0x13]] << 30; - r2 |= BASE32_VALUES[chars[0x14]] << 25; - r2 |= BASE32_VALUES[chars[0x15]] << 20; - r2 |= BASE32_VALUES[chars[0x16]] << 15; - r2 |= BASE32_VALUES[chars[0x17]] << 10; - r2 |= BASE32_VALUES[chars[0x18]] << 5; - r2 |= BASE32_VALUES[chars[0x19]]; + r2 |= ENCODING_VALUES[chars[0x12]] << 35; + r2 |= ENCODING_VALUES[chars[0x13]] << 30; + r2 |= ENCODING_VALUES[chars[0x14]] << 25; + r2 |= ENCODING_VALUES[chars[0x15]] << 20; + r2 |= ENCODING_VALUES[chars[0x16]] << 15; + r2 |= ENCODING_VALUES[chars[0x17]] << 10; + r2 |= ENCODING_VALUES[chars[0x18]] << 5; + r2 |= ENCODING_VALUES[chars[0x19]]; - this.msb = (tm << 16) | (r1 >>> 24); - this.lsb = (r1 << 40) | (r2 & 0xffffffffffL); - } + final long msb = (tm << 16) | (r1 >>> 24); + final long lsb = (r1 << 40) | (r2 & 0xffffffffffL); - private Ulid(long msb, long lsb) { - this.msb = msb; - this.lsb = lsb; - } - - public static Ulid of(UUID ulid) { - return new Ulid(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); + public static Ulid of(UUID uuid) { + return new Ulid(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits()); } + public static Ulid of(long time, byte[] random) { + + if ((time & 0xffff000000000000L) != 0) { + throw new IllegalArgumentException("Invalid time value"); + } + if (random == null || random.length != RANDOM_BYTES_LENGTH) { + throw new IllegalArgumentException("Invalid random bytes"); + } + + long msb = 0; + long lsb = 0; + + msb |= time << 16; + msb |= (long) (random[0x0] & 0xff) << 8; + msb |= (long) (random[0x1] & 0xff); + + lsb |= (long) (random[0x2] & 0xff) << 56; + lsb |= (long) (random[0x3] & 0xff) << 48; + lsb |= (long) (random[0x4] & 0xff) << 40; + lsb |= (long) (random[0x5] & 0xff) << 32; + lsb |= (long) (random[0x6] & 0xff) << 24; + lsb |= (long) (random[0x7] & 0xff) << 16; + lsb |= (long) (random[0x8] & 0xff) << 8; + lsb |= (long) (random[0x9] & 0xff); + + return new Ulid(msb, lsb); + } + + // TODO: test + public byte[] toBytes() { + + final byte[] bytes = new byte[ULID_BYTES_LENGTH]; + + 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; + } + + // TODO: test + public byte[] toBytes4() { + return Ulid.of(this.toUuid4()).toBytes(); + } + + // TODO: optimize @Override public String toString() { - final char[] chars = new char[STRING_LENGTH]; + final char[] chars = new char[ULID_LENGTH]; long long0 = this.msb; long long1 = this.lsb; @@ -203,42 +284,70 @@ public final class Ulid implements Serializable, Comparable { 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)]; - chars[0x02] = BASE32_CHARS[(int) (time >>> 35 & 0b11111)]; - chars[0x03] = BASE32_CHARS[(int) (time >>> 30 & 0b11111)]; - chars[0x04] = BASE32_CHARS[(int) (time >>> 25 & 0b11111)]; - chars[0x05] = BASE32_CHARS[(int) (time >>> 20 & 0b11111)]; - chars[0x06] = BASE32_CHARS[(int) (time >>> 15 & 0b11111)]; - chars[0x07] = BASE32_CHARS[(int) (time >>> 10 & 0b11111)]; - chars[0x08] = BASE32_CHARS[(int) (time >>> 5 & 0b11111)]; - chars[0x09] = BASE32_CHARS[(int) (time & 0b11111)]; + chars[0x00] = ENCODING_CHARS[(int) (time >>> 45 & 0b11111)]; + chars[0x01] = ENCODING_CHARS[(int) (time >>> 40 & 0b11111)]; + chars[0x02] = ENCODING_CHARS[(int) (time >>> 35 & 0b11111)]; + chars[0x03] = ENCODING_CHARS[(int) (time >>> 30 & 0b11111)]; + chars[0x04] = ENCODING_CHARS[(int) (time >>> 25 & 0b11111)]; + chars[0x05] = ENCODING_CHARS[(int) (time >>> 20 & 0b11111)]; + chars[0x06] = ENCODING_CHARS[(int) (time >>> 15 & 0b11111)]; + chars[0x07] = ENCODING_CHARS[(int) (time >>> 10 & 0b11111)]; + chars[0x08] = ENCODING_CHARS[(int) (time >>> 5 & 0b11111)]; + chars[0x09] = ENCODING_CHARS[(int) (time & 0b11111)]; - chars[0x0a] = BASE32_CHARS[(int) (random1 >>> 35 & 0b11111)]; - chars[0x0b] = BASE32_CHARS[(int) (random1 >>> 30 & 0b11111)]; - chars[0x0c] = BASE32_CHARS[(int) (random1 >>> 25 & 0b11111)]; - chars[0x0d] = BASE32_CHARS[(int) (random1 >>> 20 & 0b11111)]; - chars[0x0e] = BASE32_CHARS[(int) (random1 >>> 15 & 0b11111)]; - chars[0x0f] = BASE32_CHARS[(int) (random1 >>> 10 & 0b11111)]; - chars[0x10] = BASE32_CHARS[(int) (random1 >>> 5 & 0b11111)]; - chars[0x11] = BASE32_CHARS[(int) (random1 & 0b11111)]; + chars[0x0a] = ENCODING_CHARS[(int) (random1 >>> 35 & 0b11111)]; + chars[0x0b] = ENCODING_CHARS[(int) (random1 >>> 30 & 0b11111)]; + chars[0x0c] = ENCODING_CHARS[(int) (random1 >>> 25 & 0b11111)]; + chars[0x0d] = ENCODING_CHARS[(int) (random1 >>> 20 & 0b11111)]; + chars[0x0e] = ENCODING_CHARS[(int) (random1 >>> 15 & 0b11111)]; + chars[0x0f] = ENCODING_CHARS[(int) (random1 >>> 10 & 0b11111)]; + chars[0x10] = ENCODING_CHARS[(int) (random1 >>> 5 & 0b11111)]; + chars[0x11] = ENCODING_CHARS[(int) (random1 & 0b11111)]; - chars[0x12] = BASE32_CHARS[(int) (random2 >>> 35 & 0b11111)]; - chars[0x13] = BASE32_CHARS[(int) (random2 >>> 30 & 0b11111)]; - chars[0x14] = BASE32_CHARS[(int) (random2 >>> 25 & 0b11111)]; - chars[0x15] = BASE32_CHARS[(int) (random2 >>> 20 & 0b11111)]; - chars[0x16] = BASE32_CHARS[(int) (random2 >>> 15 & 0b11111)]; - chars[0x17] = BASE32_CHARS[(int) (random2 >>> 10 & 0b11111)]; - chars[0x18] = BASE32_CHARS[(int) (random2 >>> 5 & 0b11111)]; - chars[0x19] = BASE32_CHARS[(int) (random2 & 0b11111)]; + chars[0x12] = ENCODING_CHARS[(int) (random2 >>> 35 & 0b11111)]; + chars[0x13] = ENCODING_CHARS[(int) (random2 >>> 30 & 0b11111)]; + chars[0x14] = ENCODING_CHARS[(int) (random2 >>> 25 & 0b11111)]; + chars[0x15] = ENCODING_CHARS[(int) (random2 >>> 20 & 0b11111)]; + chars[0x16] = ENCODING_CHARS[(int) (random2 >>> 15 & 0b11111)]; + chars[0x17] = ENCODING_CHARS[(int) (random2 >>> 10 & 0b11111)]; + chars[0x18] = ENCODING_CHARS[(int) (random2 >>> 5 & 0b11111)]; + chars[0x19] = ENCODING_CHARS[(int) (random2 & 0b11111)]; return new String(chars); } - public long getTimestamp() { + // TODO: test + public String toString4() { + return Ulid.of(this.toUuid4()).toString(); + } + + public UUID toUuid() { + return new UUID(this.msb, this.lsb); + } + + // TODO: test + public UUID toUuid4() { + final long msb4 = (this.msb & 0xffffffffffff0fffL) | 0x0000000000004000L; // apply version 4 + final long lsb4 = (this.lsb & 0x3fffffffffffffffL) | 0x8000000000000000L; // apply variant RFC-4122 + return new UUID(msb4, lsb4); + } + + public long getTime() { return this.msb >>> 16; } + public Instant getInstant() { + return Instant.ofEpochMilli(this.getTime()); + } + + public long getMostSignificantBits() { + return this.msb; + } + + public long getLeastSignificantBits() { + return this.lsb; + } + @Override public int hashCode() { final int prime = 31; @@ -266,19 +375,14 @@ public final class Ulid implements Serializable, Comparable { @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 584b575..2dacd47 100644 --- a/src/main/java/com/github/f4b6a3/ulid/UlidCreator.java +++ b/src/main/java/com/github/f4b6a3/ulid/UlidCreator.java @@ -37,16 +37,16 @@ public final class UlidCreator { return DefaultCreatorHolder.INSTANCE.create(); } - public static Ulid getUlid(Long timestamp) { - return DefaultCreatorHolder.INSTANCE.create(timestamp); + public static Ulid getUlid(final long time) { + return DefaultCreatorHolder.INSTANCE.create(time); } public static Ulid getMonotonicUlid() { return MonotonicCreatorHolder.INSTANCE.create(); } - public static Ulid getMonotonicUlid(Long timestamp) { - return MonotonicCreatorHolder.INSTANCE.create(timestamp); + public static Ulid getMonotonicUlid(final long time) { + return MonotonicCreatorHolder.INSTANCE.create(time); } public static DefaultUlidSpecCreator getDefaultCreator() { 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 4d2a7d2..b487a88 100644 --- a/src/main/java/com/github/f4b6a3/ulid/creator/UlidSpecCreator.java +++ b/src/main/java/com/github/f4b6a3/ulid/creator/UlidSpecCreator.java @@ -30,36 +30,20 @@ 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; public abstract class UlidSpecCreator { - protected TimestampStrategy timestampStrategy; protected RandomStrategy randomStrategy; public UlidSpecCreator() { - this.timestampStrategy = new DefaultTimestampStrategy(); this.randomStrategy = new DefaultRandomStrategy(); } public synchronized Ulid create() { - return create(null); + return create(System.currentTimeMillis()); } - public abstract Ulid create(Long timestamp); - - /** - * Used for changing the timestamp strategy. - * - * @param timestampStrategy a timestamp strategy - * @return {@link UlidSpecCreator} - */ - @SuppressWarnings("unchecked") - public synchronized T withTimestampStrategy(TimestampStrategy timestampStrategy) { - this.timestampStrategy = timestampStrategy; - return (T) this; - } + public abstract Ulid create(final long time); /** * Replaces the default random strategy with another. diff --git a/src/main/java/com/github/f4b6a3/ulid/creator/impl/DefaultUlidSpecCreator.java b/src/main/java/com/github/f4b6a3/ulid/creator/impl/DefaultUlidSpecCreator.java index d0ad3b5..1c5803d 100644 --- a/src/main/java/com/github/f4b6a3/ulid/creator/impl/DefaultUlidSpecCreator.java +++ b/src/main/java/com/github/f4b6a3/ulid/creator/impl/DefaultUlidSpecCreator.java @@ -30,17 +30,14 @@ import com.github.f4b6a3.ulid.creator.UlidSpecCreator; public final class DefaultUlidSpecCreator extends UlidSpecCreator { @Override - public synchronized Ulid create(final Long timestamp) { - - final long time = timestamp != null ? timestamp : this.timestampStrategy.getTimestamp(); - - // Get random values - final byte[] bytes = new byte[10]; - this.randomStrategy.nextBytes(bytes); + public synchronized Ulid create(final long time) { long msb = 0; long lsb = 0; + final byte[] bytes = new byte[10]; + this.randomStrategy.nextBytes(bytes); + msb |= time << 16; msb |= (long) (bytes[0x0] & 0xff) << 8; msb |= (long) (bytes[0x1] & 0xff); @@ -54,6 +51,6 @@ public final class DefaultUlidSpecCreator extends UlidSpecCreator { lsb |= (long) (bytes[0x8] & 0xff) << 8; lsb |= (long) (bytes[0x9] & 0xff); - return Ulid.of(msb, lsb); + return new Ulid(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 index 243a385..2ec38ce 100644 --- a/src/main/java/com/github/f4b6a3/ulid/creator/impl/MonotonicUlidSpecCreator.java +++ b/src/main/java/com/github/f4b6a3/ulid/creator/impl/MonotonicUlidSpecCreator.java @@ -29,41 +29,46 @@ import com.github.f4b6a3.ulid.creator.UlidSpecCreator; public final class MonotonicUlidSpecCreator extends UlidSpecCreator { - protected long msb = 0; - protected long lsb = 0; + private long msb = 0; + private long lsb = 0; - protected long previousTimestamp; + private long lastTime; - public synchronized Ulid create(final Long timestamp) { + // 0xffffffffffffffffL + 1 = 0x0000000000000000L + private static final long UNSIGNED_OVERFLOW = 0x0000000000000000L; - final long time = timestamp != null ? timestamp : this.timestampStrategy.getTimestamp(); + @Override + public synchronized Ulid create(final long time) { - if (time == this.previousTimestamp) { - this.lsb++; + // TODO: test + if (time == this.lastTime) { + if (++this.lsb == UNSIGNED_OVERFLOW) { + // Increment the random bits of the MSB + this.msb = (this.msb & 0xffffffffffff0000L) | ((this.msb + 1) & 0x000000000000ffffL); + } } else { - // Get random values + + this.msb = 0; + this.lsb = 0; + final byte[] bytes = new byte[10]; this.randomStrategy.nextBytes(bytes); - msb = 0; - lsb = 0; + this.msb |= time << 16; + this.msb |= (long) (bytes[0x0] & 0xff) << 8; + this.msb |= (long) (bytes[0x1] & 0xff); - 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.lsb |= (long) (bytes[0x2] & 0xff) << 56; + this.lsb |= (long) (bytes[0x3] & 0xff) << 48; + this.lsb |= (long) (bytes[0x4] & 0xff) << 40; + this.lsb |= (long) (bytes[0x5] & 0xff) << 32; + this.lsb |= (long) (bytes[0x6] & 0xff) << 24; + this.lsb |= (long) (bytes[0x7] & 0xff) << 16; + this.lsb |= (long) (bytes[0x8] & 0xff) << 8; + this.lsb |= (long) (bytes[0x9] & 0xff); } - this.previousTimestamp = time; - return Ulid.of(msb, lsb); + this.lastTime = time; + return new Ulid(msb, lsb); } - } diff --git a/src/main/java/com/github/f4b6a3/ulid/strategy/TimestampStrategy.java b/src/main/java/com/github/f4b6a3/ulid/strategy/TimestampStrategy.java deleted file mode 100644 index b006ec2..0000000 --- a/src/main/java/com/github/f4b6a3/ulid/strategy/TimestampStrategy.java +++ /dev/null @@ -1,30 +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.strategy; - -@FunctionalInterface -public interface TimestampStrategy { - long getTimestamp(); -} diff --git a/src/main/java/com/github/f4b6a3/ulid/strategy/timestamp/DefaultTimestampStrategy.java b/src/main/java/com/github/f4b6a3/ulid/strategy/timestamp/DefaultTimestampStrategy.java deleted file mode 100644 index eacceaf..0000000 --- a/src/main/java/com/github/f4b6a3/ulid/strategy/timestamp/DefaultTimestampStrategy.java +++ /dev/null @@ -1,38 +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.strategy.timestamp; - -import com.github.f4b6a3.ulid.strategy.TimestampStrategy; - -public final class DefaultTimestampStrategy implements TimestampStrategy { - - /** - * Returns the count of milliseconds since 1970-01-01 (Unix epoch). - */ - @Override - public long getTimestamp() { - return System.currentTimeMillis(); - } -} diff --git a/src/main/java/com/github/f4b6a3/ulid/strategy/timestamp/FixedTimestampStretegy.java b/src/main/java/com/github/f4b6a3/ulid/strategy/timestamp/FixedTimestampStretegy.java deleted file mode 100644 index f8bbda0..0000000 --- a/src/main/java/com/github/f4b6a3/ulid/strategy/timestamp/FixedTimestampStretegy.java +++ /dev/null @@ -1,41 +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.strategy.timestamp; - -import com.github.f4b6a3.ulid.strategy.TimestampStrategy; - -public final class FixedTimestampStretegy implements TimestampStrategy { - - private long timestamp = 0; - - public FixedTimestampStretegy(long timestamp) { - this.timestamp = timestamp; - } - - @Override - public long getTimestamp() { - return this.timestamp; - } -} diff --git a/src/test/java/com/github/f4b6a3/ulid/UniquenessTest.java b/src/test/java/com/github/f4b6a3/ulid/UniquenessTest.java index 49241dc..9516985 100644 --- a/src/test/java/com/github/f4b6a3/ulid/UniquenessTest.java +++ b/src/test/java/com/github/f4b6a3/ulid/UniquenessTest.java @@ -3,7 +3,6 @@ package com.github.f4b6a3.ulid; import java.util.HashSet; import com.github.f4b6a3.ulid.UlidCreator; import com.github.f4b6a3.ulid.creator.UlidSpecCreator; -import com.github.f4b6a3.ulid.strategy.timestamp.FixedTimestampStretegy; /** * @@ -26,6 +25,8 @@ public class UniquenessTest { // ULID Spec creator private UlidSpecCreator creator; + private long time = System.currentTimeMillis(); // fixed timestamp + /** * Initialize the test. * @@ -90,7 +91,7 @@ public class UniquenessTest { for (int i = 0; i < max; i++) { // Request a UUID - Ulid ulid = creator.create(); + Ulid ulid = creator.create(time); if (verbose) { // Calculate and show progress @@ -116,8 +117,7 @@ public class UniquenessTest { } public static void execute(boolean verbose, int threadCount, int requestCount) { - UlidSpecCreator creator = UlidCreator.getMonotonicCreator() - .withTimestampStrategy(new FixedTimestampStretegy(System.currentTimeMillis())); + UlidSpecCreator creator = UlidCreator.getMonotonicCreator(); UniquenessTest test = new UniquenessTest(threadCount, requestCount, creator, verbose); test.start(); 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 eb248de..154df73 100644 --- a/src/test/java/com/github/f4b6a3/ulid/creator/UlidSpecCreatorTest.java +++ b/src/test/java/com/github/f4b6a3/ulid/creator/UlidSpecCreatorTest.java @@ -14,7 +14,7 @@ import static org.junit.Assert.*; public class UlidSpecCreatorTest { - private static final int DEFAULT_LOOP_MAX = 1_000_000; + private static final int DEFAULT_LOOP_MAX = 100_000; protected static final String DUPLICATE_UUID_MSG = "A duplicate ULID was created."; @@ -22,6 +22,8 @@ public class UlidSpecCreatorTest { private static final Random RANDOM = new Random(); + private static final long TIME_MASK = 0x0000ffffffffffffL; + private static int availableProcessors() { int processors = Runtime.getRuntime().availableProcessors(); if (processors < 4) { @@ -31,20 +33,20 @@ public class UlidSpecCreatorTest { } @Test - public void testGetUlidTimestamp() { + public void testGetUlidTime() { for (int i = 0; i < 100; i++) { - long timestamp = RANDOM.nextLong() & 0x0000ffffffffffffL; - Ulid ulid = UlidCreator.getUlid(timestamp); - assertEquals(timestamp, ulid.getTimestamp()); + long time = RANDOM.nextLong() & TIME_MASK; + Ulid ulid = UlidCreator.getUlid(time); + assertEquals(time, ulid.getTime()); } } @Test - public void testGetMonotonicUlidTimestamp() { + public void testGetMonotonicUlidTime() { for (int i = 0; i < 100; i++) { - long timestamp = RANDOM.nextLong() & 0x0000ffffffffffffL; - Ulid ulid = UlidCreator.getMonotonicUlid(timestamp); - assertEquals(timestamp, ulid.getTimestamp()); + long time = RANDOM.nextLong() & TIME_MASK; + Ulid ulid = UlidCreator.getMonotonicUlid(time); + assertEquals(time, ulid.getTime()); } } diff --git a/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorDefaultStringTest.java b/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorDefaultStringTest.java index 81869f0..ea7d23e 100644 --- a/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorDefaultStringTest.java +++ b/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorDefaultStringTest.java @@ -56,7 +56,7 @@ public class UlidCreatorDefaultStringTest { assertTrue("Start time was after end time", startTime <= endTime); for (String ulid : list) { - long creationTime = Ulid.of(ulid).getTimestamp(); + long creationTime = Ulid.of(ulid).getTime(); 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 index 8a6037b..565d991 100644 --- a/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorDefaultTest.java +++ b/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorDefaultTest.java @@ -51,7 +51,7 @@ public class UlidCreatorDefaultTest { assertTrue("Start time was after end time", startTime <= endTime); for (Ulid ulid : list) { - long creationTime = ulid.getTimestamp(); + long creationTime = ulid.getTime(); 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/UlidCreatorMonotonicStringTest.java b/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorMonotonicStringTest.java index 83bd5a3..57ecdda 100644 --- a/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorMonotonicStringTest.java +++ b/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorMonotonicStringTest.java @@ -58,7 +58,7 @@ public class UlidCreatorMonotonicStringTest { assertTrue("Start time was after end time", startTime <= endTime); for (String ulid : list) { - long creationTime = Ulid.of(ulid).getTimestamp(); + long creationTime = Ulid.of(ulid).getTime(); 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/UlidCreatorMonotonicTest.java b/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorMonotonicTest.java index e61cfba..80efe8d 100644 --- a/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorMonotonicTest.java +++ b/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorMonotonicTest.java @@ -53,7 +53,7 @@ public class UlidCreatorMonotonicTest { assertTrue("Start time was after end time", startTime <= endTime); for (Ulid ulid : list) { - long creationTime = ulid.getTimestamp(); + long creationTime = ulid.getTime(); 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/util/internal/UlidTest.java b/src/test/java/com/github/f4b6a3/ulid/util/internal/UlidTest.java index b7e1f3b..85dd723 100644 --- a/src/test/java/com/github/f4b6a3/ulid/util/internal/UlidTest.java +++ b/src/test/java/com/github/f4b6a3/ulid/util/internal/UlidTest.java @@ -41,7 +41,7 @@ public class UlidTest { Random random = new Random(); final long msb = random.nextLong(); final long lsb = random.nextLong(); - Ulid ulid0 = Ulid.of(msb, lsb); // <-- under test + Ulid ulid0 = new Ulid(msb, lsb); // <-- under test assertEquals(msb, ulid0.toUuid().getMostSignificantBits()); assertEquals(lsb, ulid0.toUuid().getLeastSignificantBits()); @@ -54,7 +54,7 @@ public class UlidTest { Random random = new Random(); final long random1 = random.nextLong(); final long random2 = random.nextLong(); - Ulid ulid0 = Ulid.of(random1, random2); + Ulid ulid0 = new Ulid(random1, random2); String string1 = toString(ulid0); Ulid struct1 = Ulid.of(string1); // <-- under test @@ -83,7 +83,7 @@ public class UlidTest { Random random = new Random(); final long random1 = random.nextLong(); final long random2 = random.nextLong(); - Ulid ulid0 = Ulid.of(random1, random2); + Ulid ulid0 = new Ulid(random1, random2); String string1 = toString(ulid0); String string2 = ulid0.toString(); // <-- under test @@ -98,7 +98,7 @@ public class UlidTest { Random random = new Random(); final long random1 = random.nextLong(); final long random2 = random.nextLong(); - Ulid ulid0 = Ulid.of(random1, random2); + Ulid ulid0 = new Ulid(random1, random2); UUID uuid1 = toUuid(ulid0); UUID uuid2 = ulid0.toUuid(); // <-- under test @@ -127,7 +127,7 @@ public class UlidTest { long msb = (time << 16) | (random1 >>> 24); long lsb = (random1 << 40) | (random2 & 0xffffffffffL); - return Ulid.of(msb, lsb); + return new Ulid(msb, lsb); } public static UUID toUuid(Ulid struct) {