From 1dffbf1ebbaac08f420a25f3177cebdd7a90e846 Mon Sep 17 00:00:00 2001 From: Fabio Lima Date: Sat, 13 Feb 2021 20:14:31 -0300 Subject: [PATCH] Add static methods for extracting time and random components #9 Add static methods to Ulid: - Ulid.getInstant(String); - Ulid.getTime(String); - Ulid.getRandom(String); List of changes: Updated Ulid Updated test cases Updated README.md Test coverage 99.4% --- README.md | 24 +++- .../java/com/github/f4b6a3/ulid/Ulid.java | 106 +++++++++++++++++- .../java/com/github/f4b6a3/ulid/UlidTest.java | 13 ++- 3 files changed, 134 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 438e163..df560f6 100644 --- a/README.md +++ b/README.md @@ -35,14 +35,11 @@ Add these lines to your `pom.xml`. com.github.f4b6a3 ulid-creator - 3.0.1 + 3.1.0 ``` See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator). -Implementation ------------------------------------------------------- - ### ULID The ULID is a 128 bit long identifier. The first 48 bits represent the count of milliseconds since Unix Epoch, 1970-01-01. The remaining 80 bits are generated by a secure random number generator. @@ -168,25 +165,40 @@ Get the creation instant of a ULID: Instant instant = ulid.getInstant(); // 2007-02-16T02:13:14.633Z ``` +```java +// static method +Instant instant = Ulid.getInstant("0123456789ABCDEFGHJKMNPQRS"); // 2007-02-16T02:13:14.633Z +``` + Get the time component of a ULID: ```java long time = ulid.getTime(); // 1171591994633 ``` +```java +// static method +long time = Ulid.getTime("0123456789ABCDEFGHJKMNPQRS"); // 1171591994633 +``` + Get the random component of a ULID: ```java byte[] random = ulid.getRandom(); // 10 bytes (80 bits) ``` +```java +// static method +byte[] random = Ulid.getRandom("0123456789ABCDEFGHJKMNPQRS"); // 10 bytes (80 bits) +``` + Use a `UlidFactory` instance with `java.util.Random` to generate ULIDs: ```java Random random = new Random(); UlidFactory factory = UlidCreator.getDefaultFactory().withRandomGenerator(random::nextBytes); -Ulid ulid = facory.create(); +Ulid ulid = factory.create(); ``` Use a `UlidFactory` instance with any random generator you like(*) to generate ULIDs: @@ -196,7 +208,7 @@ import com.github.niceguy.random.AwesomeRandom; // a hypothetical RNG AwesomeRandom awesomeRandom = new AwesomeRandom(); UlidFactory factory = UlidCreator.getDefaultFactory().withRandomGenerator(awesomeRandom::nextBytes); -Ulid ulid = facory.create(); +Ulid ulid = factory.create(); ``` (*) since it provides a void method like `nextBytes(byte[])`. diff --git a/src/main/java/com/github/f4b6a3/ulid/Ulid.java b/src/main/java/com/github/f4b6a3/ulid/Ulid.java index 8c0563a..d2285e1 100644 --- a/src/main/java/com/github/f4b6a3/ulid/Ulid.java +++ b/src/main/java/com/github/f4b6a3/ulid/Ulid.java @@ -423,6 +423,18 @@ public final class Ulid implements Serializable, Comparable { return Instant.ofEpochMilli(this.getTime()); } + /** + * Returns the instant of creation. + * + * The instant of creation is extracted from the time component. + * + * @param string a canonical string + * @return {@link Instant} + */ + public static Instant getInstant(String string) { + return Instant.ofEpochMilli(getTime(string)); + } + /** * Returns the time component as a number. * @@ -435,6 +447,35 @@ public final class Ulid implements Serializable, Comparable { return this.msb >>> 16; } + /** + * Returns the time component as a number. + * + * The time component is a number between 0 and 2^48-1. It is equivalent to the + * count of milliseconds since 1970-01-01 (Unix epoch). + * + * @param string a canonical string + * @return a number of milliseconds. + */ + public static long getTime(String string) { + + final char[] chars = toCharArray(string); + + long time = 0; + + time |= ALPHABET_VALUES[chars[0x00]] << 45; + time |= ALPHABET_VALUES[chars[0x01]] << 40; + time |= ALPHABET_VALUES[chars[0x02]] << 35; + time |= ALPHABET_VALUES[chars[0x03]] << 30; + time |= ALPHABET_VALUES[chars[0x04]] << 25; + time |= ALPHABET_VALUES[chars[0x05]] << 20; + time |= ALPHABET_VALUES[chars[0x06]] << 15; + time |= ALPHABET_VALUES[chars[0x07]] << 10; + time |= ALPHABET_VALUES[chars[0x08]] << 5; + time |= ALPHABET_VALUES[chars[0x09]]; + + return time; + } + /** * Returns the random component as a byte array. * @@ -443,8 +484,71 @@ public final class Ulid implements Serializable, Comparable { * @return a byte array */ public byte[] getRandom() { + final byte[] bytes = new byte[RANDOM_BYTES_LENGTH]; - System.arraycopy(this.toBytes(), TIME_BYTES_LENGTH, bytes, 0, RANDOM_BYTES_LENGTH); + + bytes[0x0] = (byte) (msb >>> 8); + bytes[0x1] = (byte) (msb); + + bytes[0x2] = (byte) (lsb >>> 56); + bytes[0x3] = (byte) (lsb >>> 48); + bytes[0x4] = (byte) (lsb >>> 40); + bytes[0x5] = (byte) (lsb >>> 32); + bytes[0x6] = (byte) (lsb >>> 24); + bytes[0x7] = (byte) (lsb >>> 16); + bytes[0x8] = (byte) (lsb >>> 8); + bytes[0x9] = (byte) (lsb); + + return bytes; + } + + /** + * Returns the random component as a byte array. + * + * The random component is an array of 10 bytes (80 bits). + * + * @param string a canonical string + * @return a byte array + */ + public static byte[] getRandom(String string) { + + final char[] chars = toCharArray(string); + + long random0 = 0; + long random1 = 0; + + random0 |= ALPHABET_VALUES[chars[0x0a]] << 35; + random0 |= ALPHABET_VALUES[chars[0x0b]] << 30; + random0 |= ALPHABET_VALUES[chars[0x0c]] << 25; + random0 |= ALPHABET_VALUES[chars[0x0d]] << 20; + random0 |= ALPHABET_VALUES[chars[0x0e]] << 15; + random0 |= ALPHABET_VALUES[chars[0x0f]] << 10; + random0 |= ALPHABET_VALUES[chars[0x10]] << 5; + random0 |= ALPHABET_VALUES[chars[0x11]]; + + random1 |= ALPHABET_VALUES[chars[0x12]] << 35; + random1 |= ALPHABET_VALUES[chars[0x13]] << 30; + random1 |= ALPHABET_VALUES[chars[0x14]] << 25; + random1 |= ALPHABET_VALUES[chars[0x15]] << 20; + random1 |= ALPHABET_VALUES[chars[0x16]] << 15; + random1 |= ALPHABET_VALUES[chars[0x17]] << 10; + random1 |= ALPHABET_VALUES[chars[0x18]] << 5; + random1 |= ALPHABET_VALUES[chars[0x19]]; + + final byte[] bytes = new byte[RANDOM_BYTES_LENGTH]; + + bytes[0x0] = (byte) (random0 >>> 32); + bytes[0x1] = (byte) (random0 >>> 24); + bytes[0x2] = (byte) (random0 >>> 16); + bytes[0x3] = (byte) (random0 >>> 8); + bytes[0x4] = (byte) (random0); + + bytes[0x5] = (byte) (random1 >>> 32); + bytes[0x6] = (byte) (random1 >>> 24); + bytes[0x7] = (byte) (random1 >>> 16); + bytes[0x8] = (byte) (random1 >>> 8); + bytes[0x9] = (byte) (random1); + return bytes; } diff --git a/src/test/java/com/github/f4b6a3/ulid/UlidTest.java b/src/test/java/com/github/f4b6a3/ulid/UlidTest.java index c22d851..9a3fd19 100644 --- a/src/test/java/com/github/f4b6a3/ulid/UlidTest.java +++ b/src/test/java/com/github/f4b6a3/ulid/UlidTest.java @@ -281,20 +281,29 @@ public class UlidTest { public void testGetTimeAndGetRandom() { long time = 0; - byte[] bytes = new byte[10]; + byte[] bytes = new byte[Ulid.RANDOM_BYTES_LENGTH]; Random random = new Random(); for (int i = 0; i < 100; i++) { time = random.nextLong() & TIME_MASK; random.nextBytes(bytes); - Ulid ulid = new Ulid(time, bytes); + // Instance methods + Ulid ulid = new Ulid(time, bytes); assertEquals(time, ulid.getTime()); // test Ulid.getTime() assertEquals(Instant.ofEpochMilli(time), ulid.getInstant()); // test Ulid.getInstant() for (int j = 0; j < bytes.length; j++) { assertEquals(bytes[j], ulid.getRandom()[j]); // test Ulid.getRandom() } + + // Static methods + String string = new Ulid(time, bytes).toString(); + assertEquals(time, Ulid.getTime(string)); // test Ulid.getTime() + assertEquals(Instant.ofEpochMilli(time), Ulid.getInstant(string)); // test Ulid.getInstant() + for (int j = 0; j < bytes.length; j++) { + assertEquals(bytes[j], Ulid.getRandom(string)[j]); // test Ulid.getRandom() + } } }