[#6] Move v4 generators to UlidStruct

Moved v4 generators to UlidStruct

List of Changes:

Create UlidStruct.toUuid4()
Create UlidStruct.toString4()
Create UlidCreatorUuidTest // test cases
Create UlidCreatorStringTest // test cases
Add UlidUtil.extractUnixMilliseconds(UUID ulid)
Add UlidUtil.extractUnixMilliseconds(String ulid)
This commit is contained in:
Fabio Lima 2020-11-08 17:40:50 -03:00
parent 686fec9ce8
commit 75d313c9ed
12 changed files with 233 additions and 76 deletions

View File

@ -33,7 +33,7 @@ Add these lines to your `pom.xml`.
<dependency>
<groupId>com.github.f4b6a3</groupId>
<artifactId>ulid-creator</artifactId>
<version>2.3.0</version>
<version>2.3.1</version>
</dependency>
```
See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator).

View File

@ -121,8 +121,7 @@ public class UlidSpecCreator {
* @return {@link UUID} a GUID value
*/
public synchronized UUID create() {
final UlidStruct struct = new UlidStruct(this.getTimestamp(), random1, random2);
return struct.toUuid();
return UlidStruct.of(this.getTimestamp(), random1, random2).toUuid();
}
/**
@ -134,7 +133,7 @@ public class UlidSpecCreator {
* @return {@link UUID} a GUID value
*/
public synchronized UUID create4() {
return applyVersion4(create());
return UlidStruct.of(this.getTimestamp(), random1, random2).toUuid4();
}
/**
@ -148,8 +147,7 @@ public class UlidSpecCreator {
* @return a ULID string
*/
public synchronized String createString() {
final UlidStruct struct = new UlidStruct(this.getTimestamp(), random1, random2);
return struct.toString();
return UlidStruct.of(this.getTimestamp(), random1, random2).toString();
}
/**
@ -165,9 +163,7 @@ public class UlidSpecCreator {
* @return a ULID string
*/
public synchronized String createString4() {
UUID uuid = applyVersion4(create());
final UlidStruct struct = new UlidStruct(uuid);
return struct.toString();
return UlidStruct.of(this.getTimestamp(), random1, random2).toString4();
}
/**
@ -275,18 +271,4 @@ public class UlidSpecCreator {
protected long extractRandom2(UUID uuid) {
return uuid.getLeastSignificantBits() & HALF_RANDOM_COMPONENT;
}
/**
* Apply the RFC-4122 version 4 to a given UUID.
*
* It makes the returning UUID compatible with RFC-4122 UUID v4.
*
* @param ulid a UUID
* @return a UUID
*/
private static UUID applyVersion4(UUID ulid) {
final long msb = (ulid.getMostSignificantBits() & 0xffffffffffff0fffL) | 0x0000000000004000L; // set version
final long lsb = (ulid.getLeastSignificantBits() & 0x3fffffffffffffffL) | 0x8000000000000000L; // set variant
return new UUID(msb, lsb);
}
}

View File

@ -43,8 +43,7 @@ public final class UlidConverter {
* @return a ULID
*/
public static String toString(final UUID ulid) {
UlidStruct struct = new UlidStruct(ulid);
return struct.toString();
return UlidStruct.of(ulid).toString();
}
/**
@ -60,8 +59,6 @@ public final class UlidConverter {
* @throws InvalidUlidException if invalid
*/
public static UUID fromString(final String ulid) {
UlidStruct struct = new UlidStruct(ulid);
return struct.toUuid();
return UlidStruct.of(ulid).toUuid();
}
}

View File

@ -25,6 +25,7 @@
package com.github.f4b6a3.ulid.util;
import java.time.Instant;
import java.util.UUID;
public final class UlidUtil {
@ -37,20 +38,37 @@ public final class UlidUtil {
protected static final char[] ALPHABET_JAVA = "0123456789abcdefghijklmnopqrstuv011".toCharArray();
private UlidUtil() {
}
public static long extractTimestamp(String ulid) {
public static long extractUnixMilliseconds(UUID ulid) {
return extractTimestamp(ulid);
}
public static long extractUnixMilliseconds(String ulid) {
UlidValidator.validate(ulid);
return extractUnixMilliseconds(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);
}
private static long extractTimestamp(UUID ulid) {
return (ulid.getMostSignificantBits() >>> 16);
}
private static long extractTimestamp(String ulid) {
return fromBase32Crockford(extractTimestampComponent(ulid).toCharArray());
}
public static String extractTimestampComponent(String ulid) {
UlidValidator.validate(ulid);
return ulid.substring(0, 10);
@ -61,10 +79,6 @@ public final class UlidUtil {
return ulid.substring(10, ULID_LENGTH);
}
protected static long extractUnixMilliseconds(String ulid) {
return fromBase32Crockford(extractTimestampComponent(ulid).toCharArray());
}
/**
* Get a number from a given array of bytes.
*
@ -230,7 +244,7 @@ public final class UlidUtil {
/**
* Decode a base 32 char array to a long number.
*
* @param chars a base 32 encoded char array
* @param chars a base 32 encoded char array
* @param alphabet an alphabet
* @return a long number
*/

View File

@ -30,8 +30,8 @@ import static com.github.f4b6a3.ulid.util.internal.UlidStruct.BASE32_VALUES;
public final class UlidValidator {
// Date: 10889-08-02T05:31:50.655Z (epoch time: 281474976710655)
protected static final long TIMESTAMP_MAX = (long) Math.pow(2, 48) - 1;
// Date: 10889-08-02T05:31:50.655Z: 281474976710655 (2^48-1)
private static final long TIMESTAMP_MAX = 0xffffffffffffL;
protected static final int ULID_LENGTH = 26;
@ -74,9 +74,13 @@ public final class UlidValidator {
* @throws InvalidUlidException if invalid
*/
public static void validate(String ulid) {
if (!isValid(ulid)) {
throw new InvalidUlidException(String.format("Invalid ULID: %s.", ulid));
if(ulid != null) {
final char[] chars = ulid.toCharArray();
if(isValidString(chars) && isValidTimestamp(chars)) {
return; // valid
}
}
throw new InvalidUlidException(String.format("Invalid ULID: %s.", ulid));
}
/**

View File

@ -118,13 +118,19 @@ public final class UlidStruct {
}
public UlidStruct(long time, long random1, long random2) {
private UlidStruct() {
this.time = 0;
this.random1 = 0;
this.random2 = 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;
}
public UlidStruct(UUID uuid) {
private UlidStruct(UUID uuid) {
final long msb = uuid.getMostSignificantBits();
final long lsb = uuid.getLeastSignificantBits();
@ -133,7 +139,7 @@ public final class UlidStruct {
this.random2 = (lsb & 0x000000ffffffffffL);
}
public UlidStruct(String string) {
private UlidStruct(String string) {
UlidValidator.validate(string);
@ -176,6 +182,18 @@ public final class UlidStruct {
this.random2 = r2;
}
public static UlidStruct of(long time, long random1, long random2) {
return new UlidStruct(time, random1, random2);
}
public static UlidStruct of(UUID ulid) {
return new UlidStruct(ulid);
}
public static UlidStruct of(String ulid) {
return new UlidStruct(ulid);
}
public String toString() {
final char[] chars = new char[26];
@ -212,6 +230,12 @@ public final class UlidStruct {
return new String(chars);
}
public String toString4() {
// apply RFC-4122 version 4 and variant 2
final long newrandom1 = ((this.random1 & 0x0fff3fffffL) | 0x4000000000L) | 0x0000800000L;
return UlidStruct.of(this.time, newrandom1, this.random2).toString();
}
public UUID toUuid() {
final long msb = (time << 16) | (random1 >>> 24);
@ -220,6 +244,12 @@ public final class UlidStruct {
return new UUID(msb, lsb);
}
public UUID toUuid4() {
// apply RFC-4122 version 4 and variant 2
final long newrandom1 = ((this.random1 & 0x0fff3fffffL) | 0x4000000000L) | 0x0000800000L;
return UlidStruct.of(this.time, newrandom1, this.random2).toUuid();
}
@Override
public int hashCode() {
final int prime = 31;

View File

@ -4,7 +4,8 @@ import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import com.github.f4b6a3.ulid.creator.UlidSpecCreatorTest;
import com.github.f4b6a3.ulid.ulid.UlidCreatorTest;
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.util.UlidValidatorTest;
@ -12,7 +13,8 @@ import com.github.f4b6a3.ulid.util.internal.UlidStructTest;
@RunWith(Suite.class)
@Suite.SuiteClasses({
UlidCreatorTest.class,
UlidCreatorUuidTest.class,
UlidCreatorStringTest.class,
UlidSpecCreatorTest.class,
UlidConverterTest.class,
UlidUtilTest.class,

View File

@ -12,7 +12,7 @@ import java.util.Arrays;
import java.util.HashSet;
import java.util.UUID;
public class UlidCreatorTest {
public class UlidCreatorStringTest {
private static final int ULID_LENGTH = 26;
private static final int DEFAULT_LOOP_MAX = 100_000;
@ -79,7 +79,7 @@ public class UlidCreatorTest {
assertTrue("Start time was after end time", startTime <= endTime);
for (String ulid : list) {
long creationTime = UlidUtil.extractTimestamp(ulid);
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);
@ -98,7 +98,8 @@ public class UlidCreatorTest {
private void checkVersion4(String[] list) {
for (String ulid : list) {
UUID uuid = UlidConverter.fromString(ulid);
assertEquals(String.format("ULID is is not version 4 %s", uuid), 4, uuid.version());
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());
}
}
}

View File

@ -0,0 +1,98 @@
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<UUID> 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());
}
}
}

View File

@ -45,10 +45,10 @@ public class UlidConverterTest {
final long time = random.nextLong();
final long random1 = random.nextLong();
final long random2 = random.nextLong();
UlidStruct struct0 = new UlidStruct(time, random1, random2);
UlidStruct struct0 = UlidStruct.of(time, random1, random2);
String string1 = struct0.toString();
UlidStruct struct1 = new UlidStruct(string1);
UlidStruct struct1 = UlidStruct.of(string1);
assertEquals(struct0.time, struct1.time);
assertEquals(struct0.random1, struct1.random1);
@ -62,10 +62,10 @@ public class UlidConverterTest {
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
UUID ulid0 = UlidCreator.getUlid();
UlidStruct struct0 = new UlidStruct(ulid0);
UlidStruct struct0 = UlidStruct.of(ulid0);
String string1 = UlidConverter.toString(ulid0);
UlidStruct struct1 = new UlidStruct(string1);
UlidStruct struct1 = UlidStruct.of(string1);
assertEquals(struct0.time, struct1.time);
assertEquals(struct0.random1, struct1.random1);
@ -79,10 +79,10 @@ public class UlidConverterTest {
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
UUID ulid0 = UlidCreator.getUlid();
UlidStruct struct0 = new UlidStruct(ulid0);
UlidStruct struct0 = UlidStruct.of(ulid0);
String string1 = struct0.toString();
UlidStruct struct1 = new UlidStruct(string1);
UlidStruct struct1 = UlidStruct.of(string1);
assertEquals(struct0.time, struct1.time);
assertEquals(struct0.random1, struct1.random1);
@ -96,13 +96,13 @@ public class UlidConverterTest {
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
UUID ulid0 = UlidCreator.getUlid();
UlidStruct struct0 = new UlidStruct(ulid0);
UlidStruct struct0 = UlidStruct.of(ulid0);
String string1 = UlidConverter.toString(ulid0);
UlidStruct struct1 = new UlidStruct(string1);
UlidStruct struct1 = UlidStruct.of(string1);
String string2 = struct0.toString();
UlidStruct struct2 = new UlidStruct(string2);
UlidStruct struct2 = UlidStruct.of(string2);
assertEquals(string1, string2);

View File

@ -2,6 +2,7 @@ package com.github.f4b6a3.ulid.util;
import static org.junit.Assert.*;
import java.time.Instant;
import java.util.UUID;
import org.junit.Test;
@ -14,7 +15,8 @@ public class UlidUtilTest {
private static final String EXAMPLE_RANDOMNESS = "ABCDEFGHJKMNPQRS";
private static final String EXAMPLE_ULID = "0123456789ABCDEFGHJKMNPQRS";
private static final long TIMESTAMP_MAX = 281474976710655l; // 2^48 - 1
// Date: 10889-08-02T05:31:50.655Z: 281474976710655 (2^48-1)
private static final long TIMESTAMP_MAX = 0xffffffffffffL;
private static final String[] EXAMPLE_DATES = { "1970-01-01T00:00:00.000Z", "1985-10-26T01:16:00.123Z",
"2001-09-09T01:46:40.456Z", "2020-01-15T14:30:33.789Z", "2038-01-19T03:14:07.321Z" };
@ -43,19 +45,46 @@ public class UlidUtilTest {
"4EHEM5", "XJDP47", "757B07", "X3J341", "4TFS7M", "FBP7NE", "86DWP0", "B6KZXG", "TSG99C", "W1TJBW",
"X17F5F", "VVFP2X", "WJCER0" };
@Test(expected = InvalidUlidException.class)
public void testExtractTimestamp() {
@Test
public void testExtractTimestamp1() {
String ulid = "0000000000" + EXAMPLE_RANDOMNESS;
long milliseconds = extractTimestamp(ulid);
long milliseconds = extractUnixMilliseconds(ulid);
assertEquals(0, milliseconds);
ulid = "7ZZZZZZZZZ" + EXAMPLE_RANDOMNESS;
milliseconds = extractTimestamp(ulid);
milliseconds = extractUnixMilliseconds(ulid);
assertEquals(TIMESTAMP_MAX, milliseconds);
ulid = "8ZZZZZZZZZ" + EXAMPLE_RANDOMNESS;
extractTimestamp(ulid);
try {
ulid = "8ZZZZZZZZZ" + EXAMPLE_RANDOMNESS;
extractUnixMilliseconds(ulid);
fail("Should throw an InvalidUlidException");
} catch (InvalidUlidException e) {
// success
}
}
@Test
public void testExtractTimestamp2() {
String string = "0000000000" + EXAMPLE_RANDOMNESS;
UUID ulid = UlidConverter.fromString(string);
long milliseconds = extractUnixMilliseconds(ulid);
assertEquals(0, milliseconds);
string = "7ZZZZZZZZZ" + EXAMPLE_RANDOMNESS;
ulid = UlidConverter.fromString(string);
milliseconds = extractUnixMilliseconds(ulid);
assertEquals(TIMESTAMP_MAX, milliseconds);
try {
string = "8ZZZZZZZZZ" + EXAMPLE_RANDOMNESS;
ulid = UlidConverter.fromString(string);
fail("Should throw an InvalidUlidException");
} catch (InvalidUlidException e) {
// success
}
}
@Test
@ -68,7 +97,7 @@ public class UlidUtilTest {
String timestampComponent = new String(UlidUtil.zerofill(toBase32Crockford(milliseconds), 10));
String ulid = timestampComponent + randomnessComponent;
long result = extractTimestamp(ulid);
long result = extractUnixMilliseconds(ulid);
assertEquals(milliseconds, result);
}

View File

@ -21,7 +21,7 @@ public class UlidStructTest {
final long time = random.nextLong();
final long random1 = random.nextLong();
final long random2 = random.nextLong();
UlidStruct struct0 = new UlidStruct(time, random1, random2); // <-- under test
UlidStruct struct0 = UlidStruct.of(time, random1, random2); // <-- under test
assertEquals(time & 0xffffffffffffL, struct0.time);
assertEquals(random1 & 0xffffffffffL, struct0.random1);
@ -36,10 +36,10 @@ public class UlidStructTest {
final long time = random.nextLong();
final long random1 = random.nextLong();
final long random2 = random.nextLong();
UlidStruct struct0 = new UlidStruct(time, random1, random2);
UlidStruct struct0 = UlidStruct.of(time, random1, random2);
String string1 = toString(struct0);
UlidStruct struct1 = new UlidStruct(string1); // <-- under test
UlidStruct struct1 = UlidStruct.of(string1); // <-- under test
assertEquals(struct0, struct1);
}
}
@ -51,7 +51,7 @@ public class UlidStructTest {
final long msb = random.nextLong();
final long lsb = random.nextLong();
final UUID uuid0 = new UUID(msb, lsb);
UlidStruct struct0 = new UlidStruct(uuid0); // <-- under test
UlidStruct struct0 = UlidStruct.of(uuid0); // <-- under test
UUID uuid1 = toUuid(struct0);
assertEquals(uuid0, uuid1);
@ -66,7 +66,7 @@ public class UlidStructTest {
final long time = random.nextLong();
final long random1 = random.nextLong();
final long random2 = random.nextLong();
UlidStruct struct0 = new UlidStruct(time, random1, random2);
UlidStruct struct0 = UlidStruct.of(time, random1, random2);
String string1 = toString(struct0);
String string2 = struct0.toString(); // <-- under test
@ -82,7 +82,7 @@ public class UlidStructTest {
final long time = random.nextLong();
final long random1 = random.nextLong();
final long random2 = random.nextLong();
UlidStruct struct0 = new UlidStruct(time, random1, random2);
UlidStruct struct0 = UlidStruct.of(time, random1, random2);
UUID uuid1 = toUuid(struct0);
UUID uuid2 = struct0.toUuid(); // <-- under test
@ -108,7 +108,7 @@ public class UlidStructTest {
random1 = Long.parseUnsignedLong(r1, 32);
random2 = Long.parseUnsignedLong(r2, 32);
return new UlidStruct(time, random1, random2);
return UlidStruct.of(time, random1, random2);
}
public UUID toUuid(UlidStruct struct) {