THREAD_LOCAL_RANDOM = ThreadLocal.withInitial(SecureRandom::new);
+
+ @Override
+ public void nextBytes(byte[] bytes) {
+ THREAD_LOCAL_RANDOM.get().nextBytes(bytes);
+ }
+}
diff --git a/src/main/java/com/github/f4b6a3/ulid/strategy/random/OtherRandomStrategy.java b/src/main/java/com/github/f4b6a3/ulid/strategy/random/OtherRandomStrategy.java
new file mode 100644
index 0000000..be90846
--- /dev/null
+++ b/src/main/java/com/github/f4b6a3/ulid/strategy/random/OtherRandomStrategy.java
@@ -0,0 +1,46 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2018-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.random;
+
+import java.util.Random;
+
+import com.github.f4b6a3.ulid.strategy.RandomStrategy;
+
+/**
+ * It uses an instance of {@link java.util.Random} injected by constructor.
+ */
+public final class OtherRandomStrategy implements RandomStrategy {
+
+ private final Random random;
+
+ public OtherRandomStrategy(Random random) {
+ this.random = random;
+ }
+
+ @Override
+ public void nextBytes(byte[] bytes) {
+ this.random.nextBytes(bytes);
+ }
+}
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
index 2c9e514..eacceaf 100644
--- a/src/main/java/com/github/f4b6a3/ulid/strategy/timestamp/DefaultTimestampStrategy.java
+++ b/src/main/java/com/github/f4b6a3/ulid/strategy/timestamp/DefaultTimestampStrategy.java
@@ -26,10 +26,10 @@ package com.github.f4b6a3.ulid.strategy.timestamp;
import com.github.f4b6a3.ulid.strategy.TimestampStrategy;
-public class DefaultTimestampStrategy implements TimestampStrategy {
+public final class DefaultTimestampStrategy implements TimestampStrategy {
/**
- * Returns the count of milliseconds since 01-01-1970.
+ * Returns the count of milliseconds since 1970-01-01 (Unix epoch).
*/
@Override
public long getTimestamp() {
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
index 7b7e7a7..f8bbda0 100644
--- a/src/main/java/com/github/f4b6a3/ulid/strategy/timestamp/FixedTimestampStretegy.java
+++ b/src/main/java/com/github/f4b6a3/ulid/strategy/timestamp/FixedTimestampStretegy.java
@@ -26,9 +26,9 @@ package com.github.f4b6a3.ulid.strategy.timestamp;
import com.github.f4b6a3.ulid.strategy.TimestampStrategy;
-public class FixedTimestampStretegy implements TimestampStrategy {
+public final class FixedTimestampStretegy implements TimestampStrategy {
- protected long timestamp = 0;
+ private long timestamp = 0;
public FixedTimestampStretegy(long timestamp) {
this.timestamp = timestamp;
diff --git a/src/main/java/com/github/f4b6a3/ulid/util/UlidConverter.java b/src/main/java/com/github/f4b6a3/ulid/util/UlidConverter.java
index 11d6f6a..91a59cb 100644
--- a/src/main/java/com/github/f4b6a3/ulid/util/UlidConverter.java
+++ b/src/main/java/com/github/f4b6a3/ulid/util/UlidConverter.java
@@ -26,10 +26,11 @@ package com.github.f4b6a3.ulid.util;
import java.util.UUID;
-import com.github.f4b6a3.util.Base32Util;
-import com.github.f4b6a3.util.ByteUtil;
+import com.github.f4b6a3.ulid.exception.InvalidUlidException;
-public class UlidConverter {
+import static com.github.f4b6a3.ulid.util.UlidUtil.*;
+
+public final class UlidConverter {
private UlidConverter() {
}
@@ -39,8 +40,6 @@ public class UlidConverter {
*
* The returning string is encoded to Crockford's base32.
*
- * The timestamp and random components are encoded separated.
- *
* @param uuid a UUID
* @return a ULID
*/
@@ -49,19 +48,20 @@ public class UlidConverter {
final long msb = uuid.getMostSignificantBits();
final long lsb = uuid.getLeastSignificantBits();
- // Extract timestamp component
- final long timeNumber = (msb >>> 16);
- String timestampComponent = leftPad(Base32Util.toBase32Crockford(timeNumber));
+ final long time = ((msb & 0xffffffffffff0000L) >>> 16);
+ final long random1 = ((msb & 0x000000000000ffffL) << 24) | ((lsb & 0xffffff0000000000L) >>> 40);
+ final long random2 = (lsb & 0x000000ffffffffffL);
- // Extract randomness component
- byte[] randBytes = new byte[10];
- randBytes[0] = (byte) (msb >>> 8);
- randBytes[1] = (byte) (msb);
- byte[] lsbBytes = ByteUtil.toBytes(lsb);
- System.arraycopy(lsbBytes, 0, randBytes, 2, 8);
- String randomnessComponent = Base32Util.toBase32Crockford(randBytes);
+ final char[] timeComponent = zerofill(toBase32Crockford(time), 10);
+ final char[] randomComponent1 = zerofill(toBase32Crockford(random1), 8);
+ final char[] randomComponent2 = zerofill(toBase32Crockford(random2), 8);
- return timestampComponent + randomnessComponent;
+ char[] output = new char[ULID_CHAR_LENGTH];
+ System.arraycopy(timeComponent, 0, output, 0, 10);
+ System.arraycopy(randomComponent1, 0, output, 10, 8);
+ System.arraycopy(randomComponent2, 0, output, 18, 8);
+
+ return new String(output);
}
/**
@@ -70,34 +70,33 @@ public class UlidConverter {
* The input string must be encoded to Crockford's base32, following the ULID
* specification.
*
- * The timestamp and random components are decoded separated.
- *
* 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) {
UlidValidator.validate(ulid);
+
+ final char[] input = ulid.toCharArray();
+ final char[] timeComponent = new char[10];
+ final char[] randomComponent1 = new char[8];
+ final char[] randomComponent2 = new char[8];
+
+ System.arraycopy(input, 0, timeComponent, 0, 10);
+ System.arraycopy(input, 10, randomComponent1, 0, 8);
+ System.arraycopy(input, 18, randomComponent2, 0, 8);
- // Extract timestamp component
- final String timestampComponent = ulid.substring(0, 10);
- final long timeNumber = Base32Util.fromBase32CrockfordAsLong(timestampComponent);
+ final long time = fromBase32Crockford(timeComponent);
+ final long random1 = fromBase32Crockford(randomComponent1);
+ final long random2 = fromBase32Crockford(randomComponent2);
- // Extract randomness component
- final String randomnessComponent = ulid.substring(10, 26);
- byte[] randBytes = Base32Util.fromBase32Crockford(randomnessComponent);
- byte[] lsbBytes = new byte[8];
- System.arraycopy(randBytes, 2, lsbBytes, 0, 8);
-
- final long msb = (timeNumber << 16) | ((randBytes[0] << 8) & 0x0000ff00L) | ((randBytes[1]) & 0x000000ffL);
- final long lsb = ByteUtil.toNumber(lsbBytes);
+ final long msb = ((time & 0x0000ffffffffffffL) << 16) | ((random1 & 0x000000ffff000000L) >>> 24);
+ final long lsb = ((random1 & 0x0000000000ffffffL) << 40) | (random2 & 0x000000ffffffffffL);
return new UUID(msb, lsb);
}
- private static String leftPad(String unpadded) {
- return "0000000000".substring(unpadded.length()) + unpadded;
- }
}
diff --git a/src/main/java/com/github/f4b6a3/ulid/util/UlidUtil.java b/src/main/java/com/github/f4b6a3/ulid/util/UlidUtil.java
index 0abe55e..71a3e06 100644
--- a/src/main/java/com/github/f4b6a3/ulid/util/UlidUtil.java
+++ b/src/main/java/com/github/f4b6a3/ulid/util/UlidUtil.java
@@ -26,9 +26,14 @@ package com.github.f4b6a3.ulid.util;
import java.time.Instant;
-import com.github.f4b6a3.util.Base32Util;
+public final class UlidUtil {
-public class UlidUtil {
+ protected static final int BASE_32 = 32;
+
+ protected static final int ULID_CHAR_LENGTH = 26;
+
+ // Include 'O'->ZERO, 'I'->ONE and 'L'->ONE
+ protected static final char[] ALPHABET_CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZOIL".toCharArray();
private UlidUtil() {
}
@@ -50,11 +55,196 @@ public class UlidUtil {
public static String extractRandomnessComponent(String ulid) {
UlidValidator.validate(ulid);
- return ulid.substring(10, 26);
+ return ulid.substring(10, ULID_CHAR_LENGTH);
}
protected static long extractUnixMilliseconds(String ulid) {
- String milliseconds = ulid.substring(0, 10);
- return Base32Util.fromBase32CrockfordAsLong(milliseconds);
+ return fromBase32Crockford(extractTimestampComponent(ulid).toCharArray());
+ }
+
+ /**
+ * Get a number from a given array of bytes.
+ *
+ * @param bytes a byte array
+ * @return a long
+ */
+ public static long toNumber(final byte[] bytes) {
+ return toNumber(bytes, 0, bytes.length);
+ }
+
+ public static long toNumber(final byte[] bytes, final int start, final int end) {
+ long result = 0;
+ for (int i = start; i < end; i++) {
+ result = (result << 8) | (bytes[i] & 0xff);
+ }
+ return result;
+ }
+
+ /**
+ * Get an array of bytes from a given number.
+ *
+ * @param number a long value
+ * @return a byte array
+ */
+ protected static byte[] toBytes(final long number) {
+ return new byte[] { (byte) (number >>> 56), (byte) (number >>> 48), (byte) (number >>> 40),
+ (byte) (number >>> 32), (byte) (number >>> 24), (byte) (number >>> 16), (byte) (number >>> 8),
+ (byte) (number) };
+ }
+
+ protected static char[] removeHyphens(final char[] input) {
+
+ int count = 0;
+ char[] buffer = new char[input.length];
+
+ for (int i = 0; i < input.length; i++) {
+ if ((input[i] != '-')) {
+ buffer[count++] = input[i];
+ }
+ }
+
+ char[] output = new char[count];
+ System.arraycopy(buffer, 0, output, 0, count);
+
+ return output;
+ }
+
+ public static char[] toBase32Crockford(long number) {
+ return encode(number, UlidUtil.ALPHABET_CROCKFORD);
+ }
+
+ public static long fromBase32Crockford(char[] chars) {
+ return decode(chars, UlidUtil.ALPHABET_CROCKFORD);
+ }
+
+ protected static boolean isCrockfordBase32(final char[] chars) {
+ char[] input = toUpperCase(chars);
+ for (int i = 0; i < input.length; i++) {
+ if (!isCrockfordBase32(input[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ protected static boolean isCrockfordBase32(char c) {
+ for (int j = 0; j < ALPHABET_CROCKFORD.length; j++) {
+ if (c == ALPHABET_CROCKFORD[j]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected static char[] zerofill(char[] chars, int length) {
+ return lpad(chars, length, '0');
+ }
+
+ protected static char[] lpad(char[] chars, int length, char fill) {
+
+ int delta = 0;
+ int limit = 0;
+
+ if (length > chars.length) {
+ delta = length - chars.length;
+ limit = length;
+ } else {
+ delta = 0;
+ limit = chars.length;
+ }
+
+ char[] output = new char[chars.length + delta];
+ for (int i = 0; i < limit; i++) {
+ if (i < delta) {
+ output[i] = fill;
+ } else {
+ output[i] = chars[i - delta];
+ }
+ }
+ return output;
+ }
+
+ protected static char[] transliterate(char[] chars, char[] alphabet1, char[] alphabet2) {
+ char[] output = chars.clone();
+ for (int i = 0; i < output.length; i++) {
+ for (int j = 0; j < alphabet1.length; j++) {
+ if (output[i] == alphabet1[j]) {
+ output[i] = alphabet2[j];
+ break;
+ }
+ }
+ }
+ return output;
+ }
+
+ protected static char[] toUpperCase(final char[] chars) {
+ char[] output = new char[chars.length];
+ for (int i = 0; i < output.length; i++) {
+ if (chars[i] >= 0x61 && chars[i] <= 0x7a) {
+ output[i] = (char) ((int) chars[i] & 0xffffffdf);
+ } else {
+ output[i] = chars[i];
+ }
+ }
+ return output;
+ }
+
+ /**
+ * Encode a long number to base 32 char array.
+ *
+ * @param number a long number
+ * @param alphabet an alphabet
+ * @return a base32 encoded char array
+ */
+ protected static char[] encode(long number, char[] alphabet) {
+
+ final int CHARS_MAX = 13; // 13 * 5 = 65
+
+ if (number < 0) {
+ throw new IllegalArgumentException(String.format("Number '%d' is not a positive integer.", number));
+ }
+
+ long n = number;
+ char[] buffer = new char[CHARS_MAX];
+ char[] output;
+
+ int count = CHARS_MAX;
+ while (n > 0) {
+ buffer[--count] = alphabet[(int) (n % BASE_32)];
+ n = n / BASE_32;
+ }
+
+ output = new char[buffer.length - count];
+ System.arraycopy(buffer, count, output, 0, output.length);
+
+ return output;
+ }
+
+ /**
+ * Decode a base 32 char array to a long number.
+ *
+ * @param chars a base 32 encoded char array
+ * @param alphabet an alphabet
+ * @return a long number
+ */
+ protected static long decode(char[] chars, char[] alphabet) {
+
+ long n = 0;
+
+ for (int i = 0; i < chars.length; i++) {
+ int d = chr(chars[i], alphabet);
+ n = BASE_32 * n + d;
+ }
+
+ return n;
+ }
+
+ private static int chr(char c, char[] alphabet) {
+ for (int i = 0; i < alphabet.length; i++) {
+ if (alphabet[i] == c) {
+ return (byte) i;
+ }
+ }
+ return (byte) '0';
}
}
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 1d4d916..49f913b 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,11 @@ package com.github.f4b6a3.ulid.util;
import com.github.f4b6a3.ulid.exception.InvalidUlidException;
-public class UlidValidator {
+import static com.github.f4b6a3.ulid.util.UlidUtil.*;
- protected static final String ULID_PATTERN = "^[0-9a-tv-zA-TV-Z]{26}$";
+public final class UlidValidator {
- // Date: 10889-08-02T05:31:50.655Z
+ // Date: 10889-08-02T05:31:50.655Z (epoch time: 281474976710655)
protected static final long TIMESTAMP_MAX = (long) Math.pow(2, 48) - 1;
private UlidValidator() {
@@ -42,14 +42,12 @@ public class UlidValidator {
* A valid ULID string is a sequence of 26 characters from Crockford's base 32
* alphabet.
*
- * Dashes are ignored by this validator.
- *
*
* Examples of valid ULID strings:
- * - 0123456789ABCDEFGHJKMNPKRS (26 alphanumeric, case insensitive, except iI, lL, oO and uU)
- * - 0123456789ABCDEFGHIJKLMNOP (26 alphanumeric, case insensitive, except uU)
- * - 0123456789-ABCDEFGHJK-MNPKRS (26 alphanumeric, case insensitive, except iI, lL, oO and uU)
- * - 0123456789-ABCDEFGHIJ-KLMNOP (26 alphanumeric, case insensitive, except uU, with dashes)
+ * - 0123456789ABCDEFGHJKMNPKRS (26 alphanumeric, case insensitive, except U)
+ * - 0123456789ABCDEFGHIJKLMNOP (26 alphanumeric, case insensitive, including OIL, except U)
+ * - 0123456789-ABCDEFGHJK-MNPKRS (26 alphanumeric, case insensitive, except U, with hyphens)
+ * - 0123456789-ABCDEFGHIJ-KLMNOP (26 alphanumeric, case insensitive, including OIL, except U, with hyphens)
*
*
* @param ulid a ULID
@@ -57,16 +55,20 @@ public class UlidValidator {
*/
public static boolean isValid(String ulid) {
- if (ulid == null || ulid.isEmpty()) {
+ if (ulid == null) {
return false;
}
- String u = ulid.replaceAll("-", "");
- if (!u.matches(ULID_PATTERN)) {
+ char[] chars = removeHyphens(ulid.toCharArray());
+ if (chars.length != ULID_CHAR_LENGTH || !isCrockfordBase32(chars)) {
return false;
}
- long timestamp = UlidUtil.extractUnixMilliseconds(ulid);
+ // Extract time component
+ final char[] timestampComponent = new char[10];
+ System.arraycopy(chars, 0, timestampComponent, 0, 10);
+ final long timestamp = fromBase32Crockford(timestampComponent);
+
return timestamp >= 0 && timestamp <= TIMESTAMP_MAX;
}
diff --git a/src/test/java/com/github/f4b6a3/ulid/TestSuite.java b/src/test/java/com/github/f4b6a3/ulid/TestSuite.java
index c5cdfee..bc99110 100644
--- a/src/test/java/com/github/f4b6a3/ulid/TestSuite.java
+++ b/src/test/java/com/github/f4b6a3/ulid/TestSuite.java
@@ -3,7 +3,7 @@ package com.github.f4b6a3.ulid;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
-import com.github.f4b6a3.ulid.creator.UlidBasedGuidCreatorTest;
+import com.github.f4b6a3.ulid.creator.UlidSpecCreatorTest;
import com.github.f4b6a3.ulid.ulid.UlidCreatorTest;
import com.github.f4b6a3.ulid.util.UlidConverterTest;
import com.github.f4b6a3.ulid.util.UlidUtilTest;
@@ -12,7 +12,7 @@ import com.github.f4b6a3.ulid.util.UlidValidatorTest;
@RunWith(Suite.class)
@Suite.SuiteClasses({
UlidCreatorTest.class,
- UlidBasedGuidCreatorTest.class,
+ UlidSpecCreatorTest.class,
UlidConverterTest.class,
UlidUtilTest.class,
UlidValidatorTest.class,
diff --git a/src/test/java/com/github/f4b6a3/ulid/UniquenessTest.java b/src/test/java/com/github/f4b6a3/ulid/UniquenessTest.java
index dbb33c4..0cfba13 100644
--- a/src/test/java/com/github/f4b6a3/ulid/UniquenessTest.java
+++ b/src/test/java/com/github/f4b6a3/ulid/UniquenessTest.java
@@ -4,7 +4,7 @@ import java.util.HashSet;
import java.util.UUID;
import com.github.f4b6a3.ulid.UlidCreator;
-import com.github.f4b6a3.ulid.creator.UlidBasedGuidCreator;
+import com.github.f4b6a3.ulid.creator.UlidSpecCreator;
import com.github.f4b6a3.ulid.exception.UlidCreatorException;
import com.github.f4b6a3.ulid.strategy.timestamp.FixedTimestampStretegy;
@@ -27,7 +27,7 @@ public class UniquenessTest {
private boolean verbose; // Show progress or not
// GUID creator based on ULID spec
- private UlidBasedGuidCreator creator;
+ private UlidSpecCreator creator;
/**
* Initialize the test.
@@ -36,7 +36,7 @@ public class UniquenessTest {
* @param requestCount
* @param creator
*/
- public UniquenessTest(int threadCount, int requestCount, UlidBasedGuidCreator creator, boolean progress) {
+ public UniquenessTest(int threadCount, int requestCount, UlidSpecCreator creator, boolean progress) {
this.threadCount = threadCount;
this.requestCount = requestCount;
this.creator = creator;
@@ -125,7 +125,7 @@ public class UniquenessTest {
}
public static void execute(boolean verbose, int threadCount, int requestCount) {
- UlidBasedGuidCreator creator = UlidCreator.getUlidBasedCreator()
+ UlidSpecCreator creator = UlidCreator.getUlidSpecCreator()
.withTimestampStrategy(new FixedTimestampStretegy(System.currentTimeMillis()));
UniquenessTest test = new UniquenessTest(threadCount, requestCount, creator, verbose);
diff --git a/src/test/java/com/github/f4b6a3/ulid/creator/UlidBasedGuidCreatorMock.java b/src/test/java/com/github/f4b6a3/ulid/creator/UlidBasedGuidCreatorMock.java
deleted file mode 100644
index 98587b5..0000000
--- a/src/test/java/com/github/f4b6a3/ulid/creator/UlidBasedGuidCreatorMock.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.github.f4b6a3.ulid.creator;
-
-import com.github.f4b6a3.ulid.creator.UlidBasedGuidCreator;
-
-class UlidBasedGuidCreatorMock extends UlidBasedGuidCreator {
-
- public UlidBasedGuidCreatorMock(long previousTimestamp) {
- super();
- this.previousTimestamp = previousTimestamp;
- }
-
- public UlidBasedGuidCreatorMock(long randomMsb, long randomLsb, long randomMsbMax, long randomLsbMax, long previousTimestamp) {
-
- this.randomMsb = randomMsb;
- this.randomLsb = randomLsb;
-
- this.randomMsbMax = randomMsbMax;
- this.randomLsbMax = randomLsbMax;
-
- this.previousTimestamp = previousTimestamp;
- }
-
- public long getRandomMsb() {
- return this.randomMsb;
- }
-
- public long getRandomLsb() {
- return this.randomLsb;
- }
-
- public long getRandomHiMax() {
- return this.randomMsb;
- }
-
- public long getRandomLoMax() {
- return this.randomLsb;
- }
-}
\ No newline at end of file
diff --git a/src/test/java/com/github/f4b6a3/ulid/creator/UlidBasedGuidCreatorTest.java b/src/test/java/com/github/f4b6a3/ulid/creator/UlidBasedGuidCreatorTest.java
deleted file mode 100644
index 16fff10..0000000
--- a/src/test/java/com/github/f4b6a3/ulid/creator/UlidBasedGuidCreatorTest.java
+++ /dev/null
@@ -1,168 +0,0 @@
-package com.github.f4b6a3.ulid.creator;
-
-import java.util.Random;
-import java.util.UUID;
-
-import org.junit.Test;
-
-import com.github.f4b6a3.util.random.Xorshift128PlusRandom;
-import com.github.f4b6a3.ulid.exception.UlidCreatorException;
-import com.github.f4b6a3.ulid.strategy.timestamp.FixedTimestampStretegy;
-
-import static org.junit.Assert.*;
-
-public class UlidBasedGuidCreatorTest {
-
- private static final long DEFAULT_LOOP_MAX = 1_000_000;
-
- private static final long TIMESTAMP = System.currentTimeMillis();
-
- private static final Random RANDOM = new Xorshift128PlusRandom();
-
- @Test
- public void testRandomMostSignificantBits() {
-
- UlidBasedGuidCreatorMock creator = new UlidBasedGuidCreatorMock(TIMESTAMP);
- creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
-
- UUID uuid = creator.create();
- long firstMsb = creator.extractRandomMsb(uuid);
- for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
- uuid = creator.create();
-
- }
-
- long lastMsb = creator.extractRandomMsb(uuid);
- long expectedMsb = firstMsb;
- assertEquals(String.format("The last MSB should be iqual to the first %s.", expectedMsb), expectedMsb, lastMsb);
-
- creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP + 1));
- uuid = creator.create();
- lastMsb = uuid.getMostSignificantBits();
- assertNotEquals("The last MSB should be random after timestamp changed.", firstMsb, lastMsb);
- }
-
- @Test
- public void testRandomLeastSignificantBits() {
-
- UlidBasedGuidCreatorMock creator = new UlidBasedGuidCreatorMock(TIMESTAMP);
- creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
-
- UUID uuid = creator.create();
- long firstLsb = creator.extractRandomLsb(uuid);
- for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
- uuid = creator.create();
- }
-
- long lastLsb = creator.extractRandomLsb(uuid);
- long expected = firstLsb + DEFAULT_LOOP_MAX;
- assertEquals(String.format("The last LSB should be iqual to %s.", expected), expected, lastLsb);
-
- long notExpected = firstLsb + DEFAULT_LOOP_MAX + 1;
- creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP + 1));
- uuid = creator.create();
- lastLsb = uuid.getLeastSignificantBits();
- assertNotEquals("The last LSB should be random after timestamp changed.", notExpected, lastLsb);
- }
-
- @Test
- public void testIncrementOfRandomLeastSignificantBits() {
-
- UlidBasedGuidCreatorMock creator = new UlidBasedGuidCreatorMock(TIMESTAMP);
- creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
-
- long lsb = creator.getRandomLsb();
-
- UUID uuid = new UUID(0, 0);
- for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
- uuid = creator.create();
- }
-
- long expectedLsb = lsb + DEFAULT_LOOP_MAX;
- long randomLsb = creator.getRandomLsb();
- assertEquals("Wrong LSB after loop.", expectedLsb, randomLsb);
-
- randomLsb = creator.extractRandomLsb(uuid);
- assertEquals("Wrong LSB after loop.", expectedLsb, randomLsb);
- }
-
- @Test
- public void testIncrementOfRandomMostSignificantBits() {
-
- UlidBasedGuidCreatorMock creator = new UlidBasedGuidCreatorMock(TIMESTAMP);
- creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
-
- long msb = creator.getRandomMsb();
-
- UUID uuid = new UUID(0, 0);
- for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
- uuid = creator.create();
- }
-
- long expectedMsb = msb;
- long randomMsb = creator.getRandomMsb();
- assertEquals("Wrong MSB after loop.", expectedMsb, randomMsb);
-
- randomMsb = creator.extractRandomMsb(uuid);
- assertEquals("Wrong MSB after loop.", expectedMsb, randomMsb);
- }
-
- @Test
- public void testShouldThrowOverflowException1() {
-
- long msbMax = 0x000001ffffffffffL;
- long lsbMax = 0x000001ffffffffffL;
-
- long msb = msbMax - 1;
- long lsb = lsbMax - DEFAULT_LOOP_MAX;
-
- UlidBasedGuidCreatorMock creator = new UlidBasedGuidCreatorMock(msb, lsb, msbMax, lsbMax, TIMESTAMP);
- creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
-
- for (int i = 0; i < DEFAULT_LOOP_MAX - 1; i++) {
- creator.create();
- }
-
- try {
- creator.create();
- fail("It should throw an overflow exception.");
- } catch (UlidCreatorException e) {
- // success
- }
- }
-
- @Test
- public void testShouldThrowOverflowException2() {
-
- long msbMax = (RANDOM.nextLong() & UlidBasedGuidCreatorMock.HALF_RANDOM_COMPONENT)
- | UlidBasedGuidCreatorMock.INCREMENT_MAX;
- long lsbMax = (RANDOM.nextLong() & UlidBasedGuidCreatorMock.HALF_RANDOM_COMPONENT)
- | UlidBasedGuidCreatorMock.INCREMENT_MAX;
-
- long msb = msbMax - 1;
- long lsb = lsbMax - DEFAULT_LOOP_MAX;
-
- UlidBasedGuidCreatorMock creator = new UlidBasedGuidCreatorMock(msb, lsb, msbMax, lsbMax, TIMESTAMP);
- creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
-
- UUID uuid = new UUID(0, 0);
- for (int i = 0; i < DEFAULT_LOOP_MAX - 1; i++) {
- uuid = creator.create();
- }
-
- long expectedLsb = (lsbMax - 1) & UlidBasedGuidCreatorMock.HALF_RANDOM_COMPONENT;
- long randomLsb = creator.extractRandomLsb(uuid);
- assertEquals("Incorrect LSB after loop.", expectedLsb, randomLsb);
-
- long expectedMsb = (msbMax - 1) & UlidBasedGuidCreatorMock.HALF_RANDOM_COMPONENT;
- long randomMsb = creator.extractRandomMsb(uuid);
- assertEquals("Incorrect MSB after loop.", expectedMsb, randomMsb);
-
- try {
- creator.create();
- fail("It should throw an overflow exception.");
- } catch (UlidCreatorException e) {
- // success
- }
- }
-}
diff --git a/src/test/java/com/github/f4b6a3/ulid/creator/UlidSpecCreatorMock.java b/src/test/java/com/github/f4b6a3/ulid/creator/UlidSpecCreatorMock.java
new file mode 100644
index 0000000..4cbbd41
--- /dev/null
+++ b/src/test/java/com/github/f4b6a3/ulid/creator/UlidSpecCreatorMock.java
@@ -0,0 +1,38 @@
+package com.github.f4b6a3.ulid.creator;
+
+import com.github.f4b6a3.ulid.creator.UlidSpecCreator;
+
+class UlidSpecCreatorMock extends UlidSpecCreator {
+
+ public UlidSpecCreatorMock(long previousTimestamp) {
+ super();
+ this.previousTimestamp = previousTimestamp;
+ }
+
+ public UlidSpecCreatorMock(long random1, long random2, long randomMax1, long randomMax2, long previousTimestamp) {
+
+ this.random1 = random1;
+ this.random2 = random2;
+
+ this.randomMax1 = randomMax1;
+ this.randomMax2 = randomMax2;
+
+ this.previousTimestamp = previousTimestamp;
+ }
+
+ public long getRandom1() {
+ return this.random1;
+ }
+
+ public long getRandom2() {
+ return this.random2;
+ }
+
+ public long getRandomMax1() {
+ return this.random1;
+ }
+
+ public long getRandomMax2() {
+ return this.random2;
+ }
+}
\ 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
new file mode 100644
index 0000000..d630bc6
--- /dev/null
+++ b/src/test/java/com/github/f4b6a3/ulid/creator/UlidSpecCreatorTest.java
@@ -0,0 +1,255 @@
+package com.github.f4b6a3.ulid.creator;
+
+import java.math.BigInteger;
+import java.util.HashSet;
+import java.util.Random;
+import java.util.Set;
+import java.util.UUID;
+
+import org.junit.Test;
+
+import com.github.f4b6a3.ulid.UlidCreator;
+import com.github.f4b6a3.ulid.exception.UlidCreatorException;
+import com.github.f4b6a3.ulid.strategy.timestamp.FixedTimestampStretegy;
+
+import static org.junit.Assert.*;
+
+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 int availableProcessors() {
+ int processors = Runtime.getRuntime().availableProcessors();
+ if (processors < 4) {
+ processors = 4;
+ }
+ return processors;
+ }
+
+ @Test
+ public void testRandomMostSignificantBits() {
+
+ UlidSpecCreatorMock creator = new UlidSpecCreatorMock(TIMESTAMP);
+ 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();
+
+ }
+
+ 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() {
+
+ UlidSpecCreatorMock creator = new UlidSpecCreatorMock(TIMESTAMP);
+ 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();
+ }
+
+ 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() {
+
+ UlidSpecCreatorMock creator = new UlidSpecCreatorMock(TIMESTAMP);
+ creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
+
+ long random2 = creator.getRandom2();
+
+ 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.getRandom2();
+ assertEquals("Wrong low random after loop.", expected2, rand2);
+
+ rand2 = creator.extractRandom2(uuid);
+ assertEquals("Wrong low random after loop.", expected2, rand2);
+ }
+
+ @Test
+ public void testIncrementOfRandomMostSignificantBits() {
+
+ UlidSpecCreatorMock creator = new UlidSpecCreatorMock(TIMESTAMP);
+ creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
+
+ long random1 = creator.getRandom1();
+
+ UUID uuid = new UUID(0, 0);
+ for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
+ uuid = creator.create();
+ }
+
+ long expected1 = random1;
+ long rand1 = creator.getRandom1();
+ assertEquals("Wrong high random after loop.", expected1, rand1);
+
+ rand1 = creator.extractRandom1(uuid);
+ assertEquals("Wrong high random after loop.", expected1, rand1);
+ }
+
+ @Test
+ public void testShouldThrowOverflowException1() {
+
+ 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;
+
+ 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);
+
+ try {
+ uuid = creator.create();
+ fail("It should throw an overflow exception.");
+ } catch (UlidCreatorException e) {
+ // success
+ }
+ }
+
+ @Test
+ public void testShouldThrowOverflowException2() {
+
+ 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;
+
+ 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);
+ 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);
+
+ try {
+ creator.create();
+ fail("It should throw an overflow exception.");
+ } catch (UlidCreatorException e) {
+ // success
+ }
+ }
+
+ @Test
+ public void testGetUlidParallelGeneratorsShouldCreateUniqueUlids() 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].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));
+ }
+
+ public static class TestThread extends Thread {
+
+ public static Set hashSet = new HashSet<>();
+ private UlidSpecCreator creator;
+ private int loopLimit;
+
+ public TestThread(UlidSpecCreator creator, int loopLimit) {
+ this.creator = creator;
+ this.loopLimit = loopLimit;
+ }
+
+ public static void clearHashSet() {
+ hashSet = new HashSet<>();
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < loopLimit; i++) {
+ synchronized (hashSet) {
+ hashSet.add(creator.create());
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorTest.java b/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorTest.java
index 0d732f5..dec8c16 100644
--- a/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorTest.java
+++ b/src/test/java/com/github/f4b6a3/ulid/ulid/UlidCreatorTest.java
@@ -1,37 +1,20 @@
package com.github.f4b6a3.ulid.ulid;
-import org.junit.BeforeClass;
import org.junit.Test;
import com.github.f4b6a3.ulid.UlidCreator;
-import com.github.f4b6a3.ulid.creator.UlidBasedGuidCreator;
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.Set;
-import java.util.UUID;
public class UlidCreatorTest {
- private static int processors;
-
private static final int ULID_LENGTH = 26;
private static final int DEFAULT_LOOP_MAX = 100_000;
- private static final String DUPLICATE_UUID_MSG = "A duplicate ULID was created";
-
- @BeforeClass
- public static void beforeClass() {
-
- processors = Runtime.getRuntime().availableProcessors();
- if (processors < 4) {
- processors = 4;
- }
- }
-
@Test
public void testGetUlid() {
String[] list = new String[DEFAULT_LOOP_MAX];
@@ -52,9 +35,9 @@ public class UlidCreatorTest {
private void checkNullOrInvalid(String[] list) {
for (String ulid : list) {
- assertTrue("ULID is null", ulid != null);
+ assertNotNull("ULID is null", ulid);
assertTrue("ULID is empty", !ulid.isEmpty());
- assertTrue("ULID length is wrong ", ulid.length() == ULID_LENGTH);
+ assertEquals("ULID length is wrong", ULID_LENGTH, ulid.length());
assertTrue("ULID is not valid", UlidValidator.isValid(ulid));
}
}
@@ -67,7 +50,7 @@ public class UlidCreatorTest {
assertTrue(String.format("ULID is duplicated %s", ulid), set.add(ulid));
}
- assertTrue("There are duplicated ULIDs", set.size() == list.length);
+ assertEquals("There are duplicated ULIDs", set.size(), list.length);
}
private void checkCreationTime(String[] list, long startTime, long endTime) {
@@ -87,53 +70,7 @@ public class UlidCreatorTest {
Arrays.sort(other);
for (int i = 0; i < list.length; i++) {
- assertTrue("The ULID list is not ordered", list[i].equals(other[i]));
- }
- }
-
- @Test
- public void testGetUlidBasedGuidParallelGeneratorsShouldCreateUniqueUuids() throws InterruptedException {
-
- Thread[] threads = new Thread[processors];
- TestThread.clearHashSet();
-
- // Instantiate and start many threads
- for (int i = 0; i < processors; i++) {
- threads[i] = new TestThread(UlidCreator.getUlidBasedCreator(), 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
- assertTrue(DUPLICATE_UUID_MSG, TestThread.hashSet.size() == (DEFAULT_LOOP_MAX * processors));
- }
-
- private static class TestThread extends Thread {
-
- private static Set hashSet = new HashSet<>();
- private UlidBasedGuidCreator creator;
- private int loopLimit;
-
- public TestThread(UlidBasedGuidCreator creator, int loopLimit) {
- this.creator = creator;
- this.loopLimit = loopLimit;
- }
-
- public static void clearHashSet() {
- hashSet = new HashSet<>();
- }
-
- @Override
- public void run() {
- for (int i = 0; i < loopLimit; i++) {
- synchronized (hashSet) {
- hashSet.add(creator.create());
- }
- }
+ assertEquals("The ULID list is not ordered", list[i], other[i]);
}
}
}
diff --git a/src/test/java/com/github/f4b6a3/ulid/util/UlidConverterTest.java b/src/test/java/com/github/f4b6a3/ulid/util/UlidConverterTest.java
index 7199d76..2bfda7e 100644
--- a/src/test/java/com/github/f4b6a3/ulid/util/UlidConverterTest.java
+++ b/src/test/java/com/github/f4b6a3/ulid/util/UlidConverterTest.java
@@ -22,9 +22,9 @@ public class UlidConverterTest {
UUID uuid1 = UlidCreator.getUlid();
String ulid = UlidConverter.toString(uuid1);
- assertTrue("ULID is null", ulid != null);
+ assertNotNull("ULID is null", ulid);
assertTrue("ULID is empty", !ulid.isEmpty());
- assertTrue("ULID length is wrong ", ulid.length() == ULID_LENGTH);
+ assertEquals("ULID length is wrong ", ULID_LENGTH, ulid.length());
assertTrue("ULID is not valid", UlidValidator.isValid(ulid));
UUID uuid2 = UlidConverter.fromString(ulid);
diff --git a/src/test/java/com/github/f4b6a3/ulid/util/UlidUtilTest.java b/src/test/java/com/github/f4b6a3/ulid/util/UlidUtilTest.java
index 45da9ff..195190e 100644
--- a/src/test/java/com/github/f4b6a3/ulid/util/UlidUtilTest.java
+++ b/src/test/java/com/github/f4b6a3/ulid/util/UlidUtilTest.java
@@ -5,10 +5,8 @@ import java.time.Instant;
import org.junit.Test;
-import com.github.f4b6a3.util.Base32Util;
-import com.github.f4b6a3.util.ByteUtil;
import com.github.f4b6a3.ulid.exception.InvalidUlidException;
-import com.github.f4b6a3.ulid.util.UlidUtil;
+import static com.github.f4b6a3.ulid.util.UlidUtil.*;
public class UlidUtilTest {
@@ -21,20 +19,43 @@ public class UlidUtilTest {
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" };
+ private static final int[] NUMBERS = { 102685630, 725393777, 573697669, 614668535, 790665079, 728958755, 966150230,
+ 410015018, 605266173, 946077566, 214051168, 775737014, 723003700, 391609366, 147844737, 514081413,
+ 488279622, 550860813, 611087782, 223492126, 706308515, 158990768, 549042286, 26926303, 775714134, 602886016,
+ 27282100, 675097356, 641101167, 515280699, 454184468, 371424784, 633917378, 887459583, 792903202, 168552040,
+ 824806922, 696445335, 653338746, 357696553, 353677217, 972662902, 400738139, 537701151, 202077579,
+ 110209145, 356152341, 168702810, 684185451, 419840003, 480132486, 308833881, 997154252, 918202260,
+ 103304091, 328467776, 648729690, 733655121, 645189051, 342500864, 560919543, 509761384, 626871960,
+ 429248550, 319025067, 507317265, 348303729, 256009160, 660250872, 85224414, 414490625, 355994979, 318005886,
+ 326093128, 492813589, 569014099, 503350412, 168303553, 801566586, 800368918, 742601973, 395588591,
+ 257341245, 722366808, 501878988, 200718306, 184948029, 149469829, 992401543, 240364551, 976817281,
+ 161998068, 515579566, 275182272, 376045488, 899163436, 941443452, 974372015, 934795357, 958806784 };
+
+ private static final String[] NUMBERS_BASE_32_CROCKFORD = { "31XPXY", "NKS8BH", "H33VM5", "JA667Q", "QJ15VQ",
+ "NQ61S3", "WSCJ2P", "C70N9A", "J1787X", "W67ZVY", "6C4AB0", "Q3SKNP", "NHGA9M", "BNEZ0P", "4CZVM1",
+ "FA8GM5", "EHN3J6", "GDAY0D", "J6RXD6", "6N4E0Y", "N1JTD3", "4QM0DG", "GBKE3E", "SNQ6Z", "Q3RXAP", "HYYKW0",
+ "T0JNM", "M3TARC", "K3CVBF", "FBD3SV", "DH4KGM", "B26ZGG", "JWHKY2", "TEB3QZ", "QM5FH2", "50QSK8", "RJK3GA",
+ "MR5TCQ", "KF2A3T", "AN4119", "AH9BX1", "WZKA3P", "BY5HTV", "G0SARZ", "60PXCB", "393A3S", "AKMX0N",
+ "50WCTT", "MCFNVB", "CGCG03", "E9WFC6", "96GVJS", "XPYQEC", "VBN9WM", "32GJWV", "9S81A0", "KANN2T",
+ "NVNC2H", "K79KDV", "A6M9G0", "GPXWZQ", "F64NV8", "JNTKMR", "CSBM16", "9G7VXB", "F3T30H", "AC5CBH",
+ "7M4RY8", "KNN87R", "2H8TYY", "CB9801", "AKG3B3", "9F8RKY", "9PZJA8", "ENZF8N", "GYMXTK", "F0114C",
+ "50G6Y1", "QWDVVT", "QV9A8P", "P46D7N", "BS8CZF", "7NDDSX", "NGWWAR", "EYM46C", "5ZDDZ2", "5GC59X",
+ "4EHEM5", "XJDP47", "757B07", "X3J341", "4TFS7M", "FBP7NE", "86DWP0", "B6KZXG", "TSG99C", "W1TJBW",
+ "X17F5F", "VVFP2X", "WJCER0" };
+
@Test(expected = InvalidUlidException.class)
public void testExtractTimestamp() {
String ulid = "0000000000" + EXAMPLE_RANDOMNESS;
- long milliseconds = UlidUtil.extractTimestamp(ulid);
+ long milliseconds = extractTimestamp(ulid);
assertEquals(0, milliseconds);
ulid = "7ZZZZZZZZZ" + EXAMPLE_RANDOMNESS;
- milliseconds = UlidUtil.extractTimestamp(ulid);
+ milliseconds = extractTimestamp(ulid);
assertEquals(TIMESTAMP_MAX, milliseconds);
ulid = "8ZZZZZZZZZ" + EXAMPLE_RANDOMNESS;
- UlidUtil.extractTimestamp(ulid);
- fail("Should throw exception: invalid ULID");
+ extractTimestamp(ulid);
}
@Test
@@ -43,12 +64,11 @@ public class UlidUtilTest {
String randomnessComponent = EXAMPLE_RANDOMNESS;
for (String i : EXAMPLE_DATES) {
-
long milliseconds = Instant.parse(i).toEpochMilli();
- String timestampComponent = leftPad(Base32Util.toBase32Crockford(milliseconds));
+ String timestampComponent = new String(UlidUtil.zerofill(toBase32Crockford(milliseconds), 10));
String ulid = timestampComponent + randomnessComponent;
- long result = UlidUtil.extractTimestamp(ulid);
+ long result = extractTimestamp(ulid);
assertEquals(milliseconds, result);
}
@@ -65,11 +85,11 @@ public class UlidUtilTest {
long milliseconds = Instant.parse(i).toEpochMilli();
byte[] bytes = new byte[6];
- System.arraycopy(ByteUtil.toBytes(milliseconds), 2, bytes, 0, 6);
+ System.arraycopy(toBytes(milliseconds), 2, bytes, 0, 6);
- String timestampComponent = leftPad(Base32Util.toBase32Crockford(milliseconds));
+ String timestampComponent = new String(UlidUtil.zerofill(toBase32Crockford(milliseconds), 10));
String ulid = timestampComponent + randomnessComponent;
- Instant result = UlidUtil.extractInstant(ulid);
+ Instant result = extractInstant(ulid);
assertEquals(instant, result);
}
@@ -79,7 +99,7 @@ public class UlidUtilTest {
public void testExtractTimestampComponent() {
String ulid = EXAMPLE_ULID;
String expected = EXAMPLE_TIMESTAMP;
- String result = UlidUtil.extractTimestampComponent(ulid);
+ String result = extractTimestampComponent(ulid);
assertEquals(expected, result);
}
@@ -87,11 +107,157 @@ public class UlidUtilTest {
public void testExtractRandomnessComponent() {
String ulid = EXAMPLE_ULID;
String expected = EXAMPLE_RANDOMNESS;
- String result = UlidUtil.extractRandomnessComponent(ulid);
+ String result = extractRandomnessComponent(ulid);
assertEquals(expected, result);
}
- private String leftPad(String unpadded) {
- return "0000000000".substring(unpadded.length()) + unpadded;
+ @Test
+ public void testToUpperCase() {
+ String string = "Aq7zmxKxPc61QKiGRu8Y3PdYMer64lrRxfb9A5JAJuDeEhXSrbsxsaUoHrFzmEJUYBKJPgV+1rAd";
+ char[] chars1 = string.toCharArray();
+ char[] chars2 = UlidUtil.toUpperCase(chars1);
+ assertEquals(new String(string).toUpperCase(), new String(chars2));
+
+ string = "kL9zTzZfzlwKYCEmWKPxFYxINf6JZCSmSqykyG5ONWZcFkJG2WGc7gq71YCEzt2hYcsTvfQqEmn0";
+ chars1 = string.toCharArray();
+ chars2 = UlidUtil.toUpperCase(chars1);
+ assertEquals(new String(string).toUpperCase(), new String(chars2));
+
+ string = "XXEOUV3jJb3f+wpRPDVke9NgWwEgdkzChnKnpZZWS/mCSqTi757GmmqYdzuDGOa5ftqHI3/zqKrS";
+ chars1 = string.toCharArray();
+ chars2 = UlidUtil.toUpperCase(chars1);
+ assertEquals(new String(string).toUpperCase(), new String(chars2));
+
+ string = "t9LVRQZbCxTQgaxlajNE/VYpLpKiHtKt7jHrtxSDIJ2hrHaJI2UPF1zA7I35m9cKz01lHYD1IXlM";
+ chars1 = string.toCharArray();
+ chars2 = UlidUtil.toUpperCase(chars1);
+ assertEquals(new String(string).toUpperCase(), new String(chars2));
+
+ string = "jyS52J42LLT6GY+Zywo1R4tQv4bTfAqpFB6aiKEuA3yDxFkuXzuKe8PaGlUTaXD5WgRFMnO9nRLU";
+ chars1 = string.toCharArray();
+ chars2 = UlidUtil.toUpperCase(chars1);
+ assertEquals(new String(string).toUpperCase(), new String(chars2));
+ }
+
+ @Test
+ public void testZerofill() {
+ assertEquals("001", new String(UlidUtil.zerofill("1".toCharArray(), 3)));
+ assertEquals("000123", new String(UlidUtil.zerofill("123".toCharArray(), 6)));
+ assertEquals("0000000000", new String(UlidUtil.zerofill("".toCharArray(), 10)));
+ assertEquals("9876543210", new String(UlidUtil.zerofill("9876543210".toCharArray(), 10)));
+ assertEquals("0000000000123456", new String(UlidUtil.zerofill("123456".toCharArray(), 16)));
+ }
+
+ @Test
+ public void testLpad() {
+
+ String string = "";
+ char[] chars1 = string.toCharArray();
+ char[] chars2 = UlidUtil.lpad(chars1, 8, 'x');
+ assertEquals("xxxxxxxx", new String(chars2));
+
+ string = "";
+ chars1 = string.toCharArray();
+ chars2 = UlidUtil.lpad(chars1, 12, 'W');
+ assertEquals("WWWWWWWWWWWW", new String(chars2));
+
+ string = "TCgpYATMlK9BmSzX";
+ chars1 = string.toCharArray();
+ chars2 = UlidUtil.lpad(chars1, 13, '0');
+ assertEquals(string, new String(chars2));
+
+ string = "2kgy3m9U646L6TJ5";
+ chars1 = string.toCharArray();
+ chars2 = UlidUtil.lpad(chars1, 16, '0');
+ assertEquals(string, new String(chars2));
+
+ string = "2kgy3m9U646L6TJ5";
+ chars1 = string.toCharArray();
+ chars2 = UlidUtil.lpad(chars1, 17, '0');
+ assertEquals("0" + string, new String(chars2));
+
+ string = "LH6hfYcGJu06xSNF";
+ chars1 = string.toCharArray();
+ chars2 = UlidUtil.lpad(chars1, 25, '0');
+ assertEquals("000000000" + string, new String(chars2));
+
+ string = "t9LVRQZbCxTQgaxlajNE/VYpLpKiHtKt7jHrtxSDIJ2hrHaJI2UPF1zA7I35m9cKz01lHYD1IXlM";
+ chars1 = string.toCharArray();
+ chars2 = UlidUtil.lpad(chars1, 80, '0');
+ assertEquals("0000" + string, new String(chars2));
+ }
+
+ @Test
+ public void testRemoveHyphens() {
+ String string = "-ZGQ8yCsza-RFxlYyA-FaXa4wd-k4Owa-/ITDvqWOl4-Do3/NwW--Lawx6GcO-LmSRDsd3af3Zt-VMNnvLgIw9-";
+ char[] chars1 = string.toCharArray();
+ char[] chars2 = UlidUtil.removeHyphens(chars1);
+ assertEquals(string.replace("-", ""), new String(chars2));
+
+ string = "qi3q-EMvc1-Kk7XzYMj----SUnwf-lp0K7-Ucj-W-cDplP-2dG-3x+5y-r9JBc-ZT-0e--cRHoMbU/lBzZsJ6rcJ5zT/J";
+ chars1 = string.toCharArray();
+ chars2 = UlidUtil.removeHyphens(chars1);
+ assertEquals(string.replace("-", ""), new String(chars2));
+
+ string = "RXMD0DJV---Zf3Jqcv39uGjzBuiLkLNL-IvPnTyfMteEet-I7u-Z8oyE+BIUBf/OPi30iICP1TnQpMve4j";
+ chars1 = string.toCharArray();
+ chars2 = UlidUtil.removeHyphens(chars1);
+ assertEquals(string.replace("-", ""), new String(chars2));
+
+ string = "---dFH-b-ylQPA60-kuRxZ9-6q5MLd1-qLTKdma-rF2yEABt-t6mJg0U-ibIYcVnt-Guqdn-z-G-43Ob-W/-Gxah1+53a-";
+ chars1 = string.toCharArray();
+ chars2 = UlidUtil.removeHyphens(chars1);
+ assertEquals(string.replace("-", ""), new String(chars2));
+
+ string = "-------------------------------";
+ chars1 = string.toCharArray();
+ chars2 = UlidUtil.removeHyphens(chars1);
+ assertEquals(string.replace("-", ""), new String(chars2));
+ }
+
+ @Test
+ public void testTransliterate() {
+
+ char[] alphabetCrockford = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".toCharArray();
+ char[] alphabetDefault = "0123456789abcdefghijklmnopqrstuv".toCharArray();
+
+ char[] chars2 = UlidUtil.transliterate(alphabetCrockford, alphabetCrockford, alphabetDefault);
+ assertEquals(new String(alphabetDefault), new String(chars2));
+
+ chars2 = UlidUtil.transliterate(alphabetDefault, alphabetDefault, alphabetCrockford);
+ assertEquals(new String(alphabetCrockford), new String(chars2));
+ }
+
+ @Test
+ public void testIsBase32Crockford() {
+ assertTrue(UlidUtil.isCrockfordBase32("16JD".toCharArray()));
+ assertTrue(UlidUtil.isCrockfordBase32("01BX5ZZKBKACTAV9WEVGEMMVRY".toCharArray()));
+ assertTrue(UlidUtil.isCrockfordBase32(UlidUtil.ALPHABET_CROCKFORD));
+ assertFalse(UlidUtil.isCrockfordBase32("U6JD".toCharArray()));
+ assertFalse(UlidUtil.isCrockfordBase32("*1BX5ZZKBKACTAV9WEVGEMMVRY".toCharArray()));
+ assertFalse(UlidUtil.isCrockfordBase32("u".toCharArray()));
+ assertFalse(UlidUtil.isCrockfordBase32("U".toCharArray()));
+ }
+
+ @Test
+ public void testToBase32Crockford() {
+ assertEquals("7ZZZZZZZZZ", new String(UlidUtil.toBase32Crockford(281474976710655L)));
+ // Encode from long to base 32
+ for (int i = 0; i < NUMBERS.length; i++) {
+ String result = new String(UlidUtil.toBase32Crockford(NUMBERS[i]));
+ assertEquals(NUMBERS_BASE_32_CROCKFORD[i].length(), result.length());
+ assertEquals(NUMBERS_BASE_32_CROCKFORD[i], result);
+ }
+ }
+
+ @Test
+ public void testFromBase32Crockford() {
+ assertEquals(281474976710655L, UlidUtil.fromBase32Crockford("7ZZZZZZZZZ".toCharArray()));
+ // Decode from base 32 to long
+ long number = 0;
+ for (int i = 0; i < NUMBERS.length; i++) {
+ number = UlidUtil.fromBase32Crockford((NUMBERS_BASE_32_CROCKFORD[i]).toCharArray());
+ assertEquals(NUMBERS[i], number);
+ }
}
}
diff --git a/src/test/java/com/github/f4b6a3/ulid/util/UlidValidatorTest.java b/src/test/java/com/github/f4b6a3/ulid/util/UlidValidatorTest.java
index 936ab01..05e917c 100644
--- a/src/test/java/com/github/f4b6a3/ulid/util/UlidValidatorTest.java
+++ b/src/test/java/com/github/f4b6a3/ulid/util/UlidValidatorTest.java
@@ -9,7 +9,7 @@ import com.github.f4b6a3.ulid.util.UlidValidator;
public class UlidValidatorTest {
@Test
- public void testIsValidStrict() {
+ public void testIsValid() {
String ulid = null; // Null
assertFalse("Null ULID should be invalid.", UlidValidator.isValid(ulid));
@@ -38,7 +38,7 @@ public class UlidValidatorTest {
ulid = "#123456789ABCDEFGHJKMNPQRS"; // Special char
assertFalse("ULID with special chars should be invalid.", UlidValidator.isValid(ulid));
- ulid = "01234-56789-ABCDEFGHJKMNPQRS"; // Hyphens
+ ulid = "01234-56789-ABCDEFGHJKM---NPQRS"; // Hyphens
assertTrue("ULID with hiphens should be valid.", UlidValidator.isValid(ulid));
ulid = "8ZZZZZZZZZABCDEFGHJKMNPQRS"; // timestamp > (2^48)-1