diff --git a/README.md b/README.md
index 9c7a254..2a5d494 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,153 @@
+
# ulid-creator
-A Java library for generating and handling ULIDs
+
+A Java library for generating and handling ULIDs - _Universally Unique Lexicographically Sortable Identifiers_.
+
+How to Use
+------------------------------------------------------
+
+Create a ULID:
+
+```java
+String ulid = UlidCreator.getUlid();
+```
+
+Create a fast ULID:
+
+```java
+String ulid = UlidCreator.getFastUlid();
+```
+
+Create a ULID as GUID object:
+
+```java
+UUID ulid = UlidCreator.getGuid();
+```
+
+Create a fast ULID as GUID object:
+
+```java
+UUID ulid = UlidCreator.getFastGuid();
+```
+
+Create a ULID as byte sequence:
+
+```java
+byte[] ulid = UlidCreator.getBytes();
+```
+
+Create a fast ULID as byte sequence:
+
+```java
+byte[] ulid = UlidCreator.getFastBytes();
+```
+
+### Maven dependency
+
+Work in progress.
+
+Implementation
+------------------------------------------------------
+
+### ULID
+
+The ULID is a unique and sortable 26 char sequence. See the [ULID specification](https://github.com/ulid/spec) for more information.
+
+See the section on GUIDs to know how the 128 bits are generated in this library.
+
+```java
+// ULIDs
+String ulid = UlidCreator.getUlid();
+```
+
+Examples of ULIDs:
+
+```text
+01E1PPRTMSQ34W7JR5YSND6B8T
+01E1PPRTMSQ34W7JR5YSND6B8V
+01E1PPRTMSQ34W7JR5YSND6B8W
+01E1PPRTMSQ34W7JR5YSND6B8X
+01E1PPRTMSQ34W7JR5YSND6B8Y
+01E1PPRTMSQ34W7JR5YSND6B8Z
+01E1PPRTMSQ34W7JR5YSND6B90
+01E1PPRTMSQ34W7JR5YSND6B91
+01E1PPRTMTYMX8G17TWSJJZMEE < millisecond changed
+01E1PPRTMTYMX8G17TWSJJZMEF
+01E1PPRTMTYMX8G17TWSJJZMEG
+01E1PPRTMTYMX8G17TWSJJZMEH
+01E1PPRTMTYMX8G17TWSJJZMEJ
+01E1PPRTMTYMX8G17TWSJJZMEK
+01E1PPRTMTYMX8G17TWSJJZMEM
+01E1PPRTMTYMX8G17TWSJJZMEN
+ ^ look ^ look
+
+|---------|--------------|
+ milli randomness
+```
+
+### GUID
+
+The GUIDs in this library are based on the ULID specification [9]. The first 48 bits represent the count of milliseconds since Unix Epoch, 1 January 1970. The remaining 60 bits are generated by a secure random number generator.
+
+Every time the timestamp changes the random part is reset to a new random value. If the current timestamp is equal to the previous one, the random bits are incremented by 1.
+
+The default random number generator is `SecureRandom`, but it's possible to use any RNG that extends `Random`.
+
+```java
+// GUID based on ULID spec
+UUID guid = UlidCreator.getGuid();
+```
+
+Examples of GUIDs based on ULID spec:
+
+```text
+01706d6c-6aac-80bd-7ff5-f660c2dd58ea
+01706d6c-6aac-80bd-7ff5-f660c2dd58eb
+01706d6c-6aac-80bd-7ff5-f660c2dd58ec
+01706d6c-6aac-80bd-7ff5-f660c2dd58ed
+01706d6c-6aac-80bd-7ff5-f660c2dd58ee
+01706d6c-6aac-80bd-7ff5-f660c2dd58ef
+01706d6c-6aac-80bd-7ff5-f660c2dd58f0
+01706d6c-6aac-80bd-7ff5-f660c2dd58f1
+01706d6c-6aad-c795-370c-98d0be881bb8 < millisecond changed
+01706d6c-6aad-c795-370c-98d0be881bb9
+01706d6c-6aad-c795-370c-98d0be881bba
+01706d6c-6aad-c795-370c-98d0be881bbb
+01706d6c-6aad-c795-370c-98d0be881bbc
+01706d6c-6aad-c795-370c-98d0be881bbd
+01706d6c-6aad-c795-370c-98d0be881bbe
+01706d6c-6aad-c795-370c-98d0be881bbf
+ ^ look ^ look
+
+|------------|---------------------|
+ millisecs randomness
+```
+
+#### How use the `GuidCreator` directly
+
+These are some examples of using the `GuidCreator` to create ULIDs:
+
+```java
+
+// with java random generator (java.util.Random)
+String ulid = UlidCreator.getGuidCreator().createUlid();
+
+// with java random generator (java.util.Random)
+String ulid = UlidCreator.getGuidCreator()
+ .withRandomGenerator(new Random())
+ .createUlid();
+
+// with fast random generator (Xorshift128Plus)
+String ulid = UlidCreator.getGuidCreator()
+ .withFastRandomGenerator()
+ .createUlid();
+
+// with fast random generator (Xorshift128Plus with salt)
+int salt = (int) FingerprintUtil.getFingerprint();
+Random random = new Xorshift128PlusRandom(salt);
+String ulid = UlidCreator.getGuidCreator()
+ .withRandomGenerator(random)
+ .createUlid();
+```
+
+If you use the `GuidCreator` directly, you need do handle the `UlidCreatorException`, in the case that too many ULIDs are requested within the same millisecond.
diff --git a/src/main/java/com/github/f4b6a3/ulid/UlidCreator.java b/src/main/java/com/github/f4b6a3/ulid/UlidCreator.java
index 3d71c18..2a2f58a 100644
--- a/src/main/java/com/github/f4b6a3/ulid/UlidCreator.java
+++ b/src/main/java/com/github/f4b6a3/ulid/UlidCreator.java
@@ -26,47 +26,95 @@ package com.github.f4b6a3.ulid;
import java.util.UUID;
-import com.github.f4b6a3.ulid.factory.LexicalOrderGuidCreator;
+import com.github.f4b6a3.ulid.exception.UlidCreatorException;
+import com.github.f4b6a3.ulid.guid.GuidCreator;
import com.github.f4b6a3.ulid.util.UlidUtil;
/**
* A factory for Universally Unique Lexicographically Sortable Identifiers.
*
* @see The ULID spec; https://github.com/ulid/spec
- *
*/
public class UlidCreator {
private UlidCreator() {
}
+ /**
+ * Returns a ULID.
+ *
+ * @return a ULID
+ */
public static String getUlid() {
- UUID guid = getLexicalOrderGuid();
- return UlidUtil.fromUuidToUlid(guid);
+ return GuidCreatorLazyHolder.INSTANCE.createUlid();
}
/**
- * Returns a Lexical Order GUID based on the ULID specification.
+ * Returns a fast ULID.
*
- * If you need a ULID string instead of a GUID, use
- * {@link UlidCreator#getUlid()}.
- *
- * @return a Lexical Order GUID
+ * @return a ULID
*/
- public static UUID getLexicalOrderGuid() {
- return LexicalOrderCreatorLazyHolder.INSTANCE.create();
+ public static String getFastUlid() {
+ return FastGuidCreatorLazyHolder.INSTANCE.createUlid();
}
/**
- * Returns a {@link LexicalOrderGuidCreator}.
+ * Returns ULID as GUID object.
*
- * @return {@link LexicalOrderGuidCreator}
+ * @return a GUID
*/
- public static LexicalOrderGuidCreator getLexicalOrderCreator() {
- return new LexicalOrderGuidCreator();
+ public static UUID getGuid() {
+ return GuidCreatorLazyHolder.INSTANCE.create();
}
- private static class LexicalOrderCreatorLazyHolder {
- static final LexicalOrderGuidCreator INSTANCE = getLexicalOrderCreator().withoutOverflowException();
+ /**
+ * Returns fast ULID as GUID object.
+ *
+ * @return a GUID
+ */
+ public static UUID getFastGuid() {
+ return FastGuidCreatorLazyHolder.INSTANCE.create();
+ }
+
+ /**
+ * Returns ULID as byte sequence.
+ *
+ * @return a GUID
+ */
+ public static byte[] getBytes() {
+ UUID guid = getGuid();
+ return UlidUtil.fromUuidToBytes(guid);
+ }
+
+ /**
+ * Returns fast ULID as byte sequence.
+ *
+ * @return a GUID
+ */
+ public static byte[] getFastBytes() {
+ UUID guid = getFastGuid();
+ return UlidUtil.fromUuidToBytes(guid);
+ }
+
+ /**
+ * Return a GUID creator for direct use.
+ *
+ * This library uses the {@link GuidCreator} internally to generate ULIDs.
+ *
+ * The {@link GuidCreator} throws a {@link UlidCreatorException} when too
+ * many values are requested in the same millisecond.
+ *
+ * @return a {@link GuidCreator}
+ */
+ public static GuidCreator getGuidCreator() {
+ return new GuidCreator();
+ }
+
+ private static class GuidCreatorLazyHolder {
+ static final GuidCreator INSTANCE = getGuidCreator().withoutOverflowException();
+ }
+
+ private static class FastGuidCreatorLazyHolder {
+ static final GuidCreator INSTANCE = getGuidCreator().withFastRandomGenerator().withoutOverflowException();
}
}
diff --git a/src/main/java/com/github/f4b6a3/ulid/factory/LexicalOrderGuidCreator.java b/src/main/java/com/github/f4b6a3/ulid/guid/GuidCreator.java
similarity index 82%
rename from src/main/java/com/github/f4b6a3/ulid/factory/LexicalOrderGuidCreator.java
rename to src/main/java/com/github/f4b6a3/ulid/guid/GuidCreator.java
index 7dcafdf..1d4fcd4 100644
--- a/src/main/java/com/github/f4b6a3/ulid/factory/LexicalOrderGuidCreator.java
+++ b/src/main/java/com/github/f4b6a3/ulid/guid/GuidCreator.java
@@ -22,13 +22,16 @@
* SOFTWARE.
*/
-package com.github.f4b6a3.ulid.factory;
+package com.github.f4b6a3.ulid.guid;
import java.security.SecureRandom;
import java.util.Random;
import java.util.UUID;
import com.github.f4b6a3.ulid.timestamp.TimestampStrategy;
+import com.github.f4b6a3.ulid.util.FingerprintUtil;
+import com.github.f4b6a3.ulid.util.UlidUtil;
+import com.github.f4b6a3.ulid.exception.UlidCreatorException;
import com.github.f4b6a3.ulid.random.Xorshift128PlusRandom;
import com.github.f4b6a3.ulid.timestamp.DefaultTimestampStrategy;
@@ -38,7 +41,7 @@ import com.github.f4b6a3.ulid.timestamp.DefaultTimestampStrategy;
*
* ULID specification: https://github.com/ulid/spec
*/
-public class LexicalOrderGuidCreator {
+public class GuidCreator {
protected static final long MAX_LOW = 0xffffffffffffffffL; // unsigned
protected static final long MAX_HIGH = 0x000000000000ffffL;
@@ -51,18 +54,18 @@ public class LexicalOrderGuidCreator {
protected long low;
protected long high;
- protected static final String OVERFLOW_MESSAGE = "The system caused an overflow in the generator by requesting too many GUIDs.";
+ protected static final String OVERFLOW_MESSAGE = "The system caused an overflow in the generator by requesting too many IDs.";
protected TimestampStrategy timestampStrategy;
- public LexicalOrderGuidCreator() {
+ public GuidCreator() {
this.reset();
this.timestampStrategy = new DefaultTimestampStrategy();
}
/**
*
- * Return a Lexical Order GUID.
+ * Return a GUID based on the ULID specification.
*
* It has two parts:
*
@@ -125,6 +128,26 @@ public class LexicalOrderGuidCreator {
return new UUID(msb, lsb);
}
+
+ /**
+ * Return a ULID.
+ *
+ * @return a ULID string
+ */
+ public synchronized String createUlid() {
+ UUID guid = create();
+ return UlidUtil.fromUuidToUlid(guid);
+ }
+
+ /**
+ * Return a ULID as byte sequence.
+ *
+ * @return a byte sequence
+ */
+ public synchronized byte[] createBytes() {
+ UUID guid = create();
+ return UlidUtil.fromUuidToBytes(guid);
+ }
/**
* Return the current timestamp and resets or increments the random part.
@@ -169,7 +192,7 @@ public class LexicalOrderGuidCreator {
this.high = 0L;
// Too many requests
if (enableOverflowException) {
- throw new LexicalOrderGuidException(OVERFLOW_MESSAGE);
+ throw new UlidCreatorException(OVERFLOW_MESSAGE);
}
}
}
@@ -179,11 +202,10 @@ public class LexicalOrderGuidCreator {
*
* @param timestampStrategy
* a timestamp strategy
- * @return {@link LexicalOrderGuidCreator}
+ * @return {@link GuidCreator}
*/
@SuppressWarnings("unchecked")
- public synchronized T withTimestampStrategy(
- TimestampStrategy timestampStrategy) {
+ public synchronized T withTimestampStrategy(TimestampStrategy timestampStrategy) {
this.timestampStrategy = timestampStrategy;
return (T) this;
}
@@ -201,10 +223,10 @@ public class LexicalOrderGuidCreator {
*
* @param random
* a random generator
- * @return {@link LexicalOrderGuidCreator}
+ * @return {@link GuidCreator}
*/
@SuppressWarnings("unchecked")
- public synchronized T withRandomGenerator(Random random) {
+ public synchronized T withRandomGenerator(Random random) {
this.random = random;
return (T) this;
}
@@ -218,11 +240,12 @@ public class LexicalOrderGuidCreator {
* See {@link Xorshift128PlusRandom} and
* {@link FingerprintUtil#getFingerprint()}
*
- * @return {@link LexicalOrderGuidCreator}
+ * @return {@link GuidCreator}
*/
@SuppressWarnings("unchecked")
- public synchronized T withFastRandomGenerator() {
- this.random = new Xorshift128PlusRandom();
+ public synchronized T withFastRandomGenerator() {
+ final int salt = (int) FingerprintUtil.getFingerprint();
+ this.random = new Xorshift128PlusRandom(salt);
return (T) this;
}
@@ -232,22 +255,14 @@ public class LexicalOrderGuidCreator {
* An exception thrown when too many requests within the same millisecond
* causes an overflow while incrementing the random bits of the GUID.
*
- * @return {@link LexicalOrderGuidCreator}
+ * @return {@link GuidCreator}
*/
@SuppressWarnings("unchecked")
- public synchronized T withoutOverflowException() {
+ public synchronized T withoutOverflowException() {
this.enableOverflowException = false;
return (T) this;
}
- public static class LexicalOrderGuidException extends RuntimeException {
- private static final long serialVersionUID = 1L;
-
- public LexicalOrderGuidException(String message) {
- super(message);
- }
- }
-
private static class SecureRandomLazyHolder {
static final Random INSTANCE = new SecureRandom();
}
diff --git a/src/main/java/com/github/f4b6a3/ulid/random/Xorshift128PlusRandom.java b/src/main/java/com/github/f4b6a3/ulid/random/Xorshift128PlusRandom.java
index a9195ce..de19962 100644
--- a/src/main/java/com/github/f4b6a3/ulid/random/Xorshift128PlusRandom.java
+++ b/src/main/java/com/github/f4b6a3/ulid/random/Xorshift128PlusRandom.java
@@ -43,7 +43,7 @@ public class Xorshift128PlusRandom extends Random {
public Xorshift128PlusRandom() {
this((int) System.nanoTime());
}
-
+
/**
* Constructor that receives an integer as 'salt'. This value is combined
* with the current milliseconds and the object hash code to generate two
@@ -58,7 +58,7 @@ public class Xorshift128PlusRandom extends Random {
this.seed[0] = (((long) salt) << 32) | (time & 0x00000000ffffffffL);
this.seed[1] = (((long) salt) << 32) | (hash & 0x00000000ffffffffL);
}
-
+
public Xorshift128PlusRandom(long[] seed) {
this.seed = seed;
}
diff --git a/src/main/java/com/github/f4b6a3/ulid/util/FingerprintUtil.java b/src/main/java/com/github/f4b6a3/ulid/util/FingerprintUtil.java
new file mode 100644
index 0000000..6de9aaf
--- /dev/null
+++ b/src/main/java/com/github/f4b6a3/ulid/util/FingerprintUtil.java
@@ -0,0 +1,177 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2020 Fabio Lima
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package com.github.f4b6a3.ulid.util;
+
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+
+public class FingerprintUtil {
+
+ private static MessageDigest messageDigest;
+
+ private FingerprintUtil() {
+ }
+
+ /**
+ * Returns long value representing a host fingerprint.
+ *
+ * The fingerprint is calculated from a list of system properties: OS + JVM
+ * + network details + system resources + locale + timezone.
+ *
+ * It uses these information to generate the fingerprint: operating system
+ * (name, version, arch), java virtual machine (vendor, version, runtime,
+ * VM), network settings (IP, MAC, host name, domain name), system resources
+ * (CPU cores, memory), locale (language, charset) and timezone. These
+ * information are concatenated and passed to a SHA-256 message digest.
+ *
+ * It returns the last 8 bytes of the resulting hash as long value.
+ *
+ * Read: https://en.wikipedia.org/wiki/Device_fingerprint
+ *
+ * @return a fingerprint as long value
+ */
+ public static long getFingerprint() {
+ String hash = getSystemDataHash();
+ return ByteUtil.toNumber(hash);
+ }
+
+ /**
+ * Returns a SHA-256 hash string generated from all the system data: OS +
+ * JVM + network details + system resources.
+ *
+ * @return a string
+ */
+ protected static String getSystemDataHash() {
+
+ if (messageDigest == null) {
+ messageDigest = getMessageDigest();
+ }
+
+ String os = getOperatingSystem();
+ String jvm = getJavaVirtualMachine();
+ String net = getNetwork();
+ String loc = getLocalization();
+ String res = getResources();
+ String string = String.join(" ", os, jvm, net, loc, res);
+
+ byte[] bytes = string.getBytes();
+ byte[] hash = messageDigest.digest(bytes);
+
+ return ByteUtil.toHexadecimal(hash);
+ }
+
+ /**
+ * Returns a string of the OS details.
+ *
+ * @return a string
+ */
+ protected static String getOperatingSystem() {
+ String name = System.getProperty("os.name");
+ String version = System.getProperty("os.version");
+ String arch = System.getProperty("os.arch");
+ return String.join(" ", name, version, arch);
+ }
+
+ /**
+ * Returns a string of the JVM details.
+ *
+ * @return a string
+ */
+ protected static String getJavaVirtualMachine() {
+ String vendor = System.getProperty("java.vendor");
+ String version = System.getProperty("java.version");
+ String rtName = System.getProperty("java.runtime.name");
+ String rtVersion = System.getProperty("java.runtime.version");
+ String vmName = System.getProperty("java.vm.name");
+ String vmVersion = System.getProperty("java.vm.version");
+ return String.join(" ", vendor, version, rtName, rtVersion, vmName, vmVersion);
+ }
+
+ /**
+ * Return a string with locale, charset, encoding and timezone.
+ *
+ * @return a string
+ */
+ protected static String getLocalization() {
+ String locale = Locale.getDefault().toString();
+ String charset = Charset.defaultCharset().toString();
+ String encoding = System.getProperty("file.encoding");
+ String timezone = TimeZone.getDefault().getID();
+ return String.join(" ", locale, charset, encoding, timezone);
+ }
+
+ /**
+ * Returns a string of CPU cores and maximum memory available.
+ *
+ * @return a string
+ */
+ protected static String getResources() {
+ int procs = Runtime.getRuntime().availableProcessors();
+ long memory = Runtime.getRuntime().maxMemory();
+ return String.join(" ", procs + " processors", memory + " bytes");
+ }
+
+ /**
+ * Returns a string of the network details.
+ *
+ * It's done in three two steps:
+ *
+ * 1. it tries to find the network data associated with the host name;
+ *
+ * 2. otherwise, it iterates through all interfaces to return the first one
+ * that is up and running.
+ *
+ * @return a string
+ */
+ protected static String getNetwork() {
+
+ NetworkData networkData = NetworkData.getNetworkData();
+
+ if (networkData == null) {
+ List networkDataList = NetworkData.getNetworkDataList();
+ if (networkDataList != null && !networkDataList.isEmpty()) {
+ networkData = networkDataList.get(0);
+ }
+ }
+
+ if (networkData == null) {
+ return null;
+ }
+
+ return networkData.toString();
+ }
+
+ private static MessageDigest getMessageDigest() {
+ try {
+ return MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException e) {
+ throw new InternalError("Message digest algorithm not supported.", e);
+ }
+ }
+}
diff --git a/src/main/java/com/github/f4b6a3/ulid/util/NetworkData.java b/src/main/java/com/github/f4b6a3/ulid/util/NetworkData.java
new file mode 100644
index 0000000..9b74fef
--- /dev/null
+++ b/src/main/java/com/github/f4b6a3/ulid/util/NetworkData.java
@@ -0,0 +1,198 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2018-2019 Fabio Lima
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package com.github.f4b6a3.ulid.util;
+
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+public class NetworkData {
+
+ private String hostName;
+ private String hostCanonicalName;
+ private String interfaceName;
+ private String interfaceDisplayName;
+ private String interfaceHardwareAddress;
+ private List interfaceAddresses;
+
+ public String getHostName() {
+ return hostName;
+ }
+
+ public void setHostName(String hostName) {
+ this.hostName = hostName;
+ }
+
+ public String getHostCanonicalName() {
+ return hostCanonicalName;
+ }
+
+ public void setHostCanonicalName(String hostCanonicalName) {
+ this.hostCanonicalName = hostCanonicalName;
+ }
+
+ public String getInterfaceName() {
+ return interfaceName;
+ }
+
+ public void setInterfaceName(String interfaceName) {
+ this.interfaceName = interfaceName;
+ }
+
+ public String getInterfaceDisplayName() {
+ return interfaceDisplayName;
+ }
+
+ public void setInterfaceDisplayName(String interfaceDisplayName) {
+ this.interfaceDisplayName = interfaceDisplayName;
+ }
+
+ public String getInterfaceHardwareAddress() {
+ return interfaceHardwareAddress;
+ }
+
+ public void setInterfaceHardwareAddress(String interfaceHardwareAddress) {
+ this.interfaceHardwareAddress = interfaceHardwareAddress;
+ }
+
+ public List getInterfaceAddresses() {
+ return interfaceAddresses;
+ }
+
+ public void setInterfaceAddresses(List interfaceAddresses) {
+ this.interfaceAddresses = interfaceAddresses;
+ }
+
+ @Override
+ public String toString() {
+
+ String interfaceAddressesString = null;
+ if (this.interfaceAddresses != null) {
+ interfaceAddressesString = String.join(" ", this.interfaceAddresses);
+ }
+
+ return String.join(" ", this.interfaceHardwareAddress, this.hostName, this.hostCanonicalName,
+ this.interfaceName, this.interfaceDisplayName, interfaceAddressesString);
+ }
+
+ /**
+ * Returns a {@link NetworkData}.
+ *
+ * This method returns the network data associated to the host name.
+ *
+ * @return a {@link NetworkData}
+ */
+ public static NetworkData getNetworkData() {
+
+ try {
+ InetAddress inetAddress = InetAddress.getLocalHost();
+ NetworkInterface networkInterface = NetworkInterface.getByInetAddress(inetAddress);
+ return buildNetworkData(networkInterface, inetAddress);
+ } catch (UnknownHostException | SocketException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns a list of {@link NetworkData}.
+ *
+ * This method iterates over all the network interfaces to return those that
+ * are up and running.
+ *
+ * NOTE: it may be VERY EXPENSIVE on Windows systems, because that OS
+ * creates a lot of virtual network interfaces.
+ *
+ * @return a list of {@link NetworkData}
+ */
+ public static List getNetworkDataList() {
+ try {
+ InetAddress inetAddress = InetAddress.getLocalHost();
+ List networkInterfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
+
+ HashSet networkDataHashSet = new HashSet<>();
+ for (NetworkInterface networkInterface : networkInterfaces) {
+ NetworkData networkData = buildNetworkData(networkInterface, inetAddress);
+ if (networkData != null) {
+ networkDataHashSet.add(networkData);
+ }
+ }
+ return new ArrayList<>(networkDataHashSet);
+ } catch (SocketException | NullPointerException | UnknownHostException e) {
+ return Collections.emptyList();
+ }
+ }
+
+ private static NetworkData buildNetworkData(NetworkInterface networkInterface, InetAddress inetAddress)
+ throws SocketException {
+ if (isPhysicalNetworkInterface(networkInterface)) {
+
+ String hostName = inetAddress != null ? inetAddress.getHostName() : null;
+ String hostCanonicalName = inetAddress != null ? inetAddress.getCanonicalHostName() : null;
+ String interfaceName = networkInterface.getName();
+ String interfaceDisplayName = networkInterface.getDisplayName();
+ String interfaceHardwareAddress = ByteUtil.toHexadecimal(networkInterface.getHardwareAddress());
+ List interfaceAddresses = getInterfaceAddresses(networkInterface);
+
+ NetworkData networkData = new NetworkData();
+ networkData.setHostName(hostName);
+ networkData.setHostCanonicalName(hostCanonicalName);
+ networkData.setInterfaceName(interfaceName);
+ networkData.setInterfaceDisplayName(interfaceDisplayName);
+ networkData.setInterfaceHardwareAddress(interfaceHardwareAddress);
+ networkData.setInterfaceAddresses(interfaceAddresses);
+
+ return networkData;
+ }
+ return null;
+ }
+
+ private static boolean isPhysicalNetworkInterface(NetworkInterface networkInterface) {
+ try {
+ return networkInterface != null && networkInterface.isUp()
+ && !(networkInterface.isLoopback() || networkInterface.isVirtual());
+ } catch (SocketException e) {
+ return false;
+ }
+ }
+
+ private static List getInterfaceAddresses(NetworkInterface networkInterface) {
+ HashSet addresses = new HashSet<>();
+ List interfaceAddresses = networkInterface.getInterfaceAddresses();
+ if (interfaceAddresses != null && !interfaceAddresses.isEmpty()) {
+ for (InterfaceAddress addr : interfaceAddresses) {
+ if (addr.getAddress() != null) {
+ addresses.add(addr.getAddress().getHostAddress());
+ }
+ }
+ }
+ return new ArrayList<>(addresses);
+ }
+}
diff --git a/src/test/java/com/github/f4b6a3/TestSuite.java b/src/test/java/com/github/f4b6a3/TestSuite.java
index d67701f..330ed02 100644
--- a/src/test/java/com/github/f4b6a3/TestSuite.java
+++ b/src/test/java/com/github/f4b6a3/TestSuite.java
@@ -4,7 +4,7 @@ import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import com.github.f4b6a3.ulid.UlidCreatorTest;
-import com.github.f4b6a3.ulid.factory.LexicalOrderGuidCreatorTest;
+import com.github.f4b6a3.ulid.guid.GuidCreatorTest;
import com.github.f4b6a3.ulid.random.NaiveRandomTest;
import com.github.f4b6a3.ulid.timestamp.DefaultTimestampStrategyTest;
import com.github.f4b6a3.ulid.util.Base32UtilTest;
@@ -16,11 +16,18 @@ import com.github.f4b6a3.ulid.util.UlidUtilTest;
DefaultTimestampStrategyTest.class,
ByteUtilTest.class,
NaiveRandomTest.class,
- LexicalOrderGuidCreatorTest.class,
+ GuidCreatorTest.class,
Base32UtilTest.class,
UlidUtilTest.class,
UlidCreatorTest.class,
})
+/**
+ *
+ * It bundles all JUnit test cases.
+ *
+ * Also see {@link UniquenesTest}.
+ *
+ */
public class TestSuite {
}
\ No newline at end of file
diff --git a/src/test/java/com/github/f4b6a3/UniquenessTest.java b/src/test/java/com/github/f4b6a3/UniquenessTest.java
new file mode 100644
index 0000000..2ce9148
--- /dev/null
+++ b/src/test/java/com/github/f4b6a3/UniquenessTest.java
@@ -0,0 +1,141 @@
+package com.github.f4b6a3;
+
+import java.util.HashSet;
+import java.util.UUID;
+
+import com.github.f4b6a3.ulid.UlidCreator;
+import com.github.f4b6a3.ulid.exception.UlidCreatorException;
+import com.github.f4b6a3.ulid.guid.GuidCreator;
+import com.github.f4b6a3.ulid.timestamp.FixedTimestampStretegy;
+
+/**
+ *
+ * This test starts many threads that keep requesting thousands of ULIDs to a
+ * single generator.
+ *
+ * This is is not included in the {@link TestSuite} because it takes a long time
+ * to finish.
+ */
+public class UniquenessTest {
+
+ private int threadCount; // Number of threads to run
+ private int requestCount; // Number of requests for thread
+
+ // private long[][] cacheLong; // Store values generated per thread
+ private HashSet hashSet;
+
+ private boolean verbose; // Show progress or not
+
+ // GUID creator based on ULID spec
+ private GuidCreator creator;
+
+ /**
+ * Initialize the test.
+ *
+ * @param threadCount
+ * @param requestCount
+ * @param creator
+ */
+ public UniquenessTest(int threadCount, int requestCount, GuidCreator creator, boolean progress) {
+ this.threadCount = threadCount;
+ this.requestCount = requestCount;
+ this.creator = creator;
+ this.verbose = progress;
+ this.initCache();
+ }
+
+ private void initCache() {
+ this.hashSet = new HashSet<>();
+ }
+
+ /**
+ * Initialize and start the threads.
+ */
+ public void start() {
+
+ Thread[] threads = new Thread[this.threadCount];
+
+ // Instantiate and start many threads
+ for (int i = 0; i < this.threadCount; i++) {
+ threads[i] = new Thread(new UniquenessTestThread(i, verbose));
+ threads[i].start();
+ }
+
+ // Wait all the threads to finish
+ for (Thread thread : threads) {
+ try {
+ thread.join();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ public class UniquenessTestThread implements Runnable {
+
+ private int id;
+ private boolean verbose;
+
+ public UniquenessTestThread(int id, boolean verbose) {
+ this.id = id;
+ this.verbose = verbose;
+ }
+
+ /**
+ * Run the test.
+ */
+ @Override
+ public void run() {
+
+ double progress = 0;
+ int max = requestCount;
+
+ for (int i = 0; i < max; i++) {
+
+ // Request a UUID
+ UUID uuid = null;
+ try {
+ uuid = creator.create();
+ } catch (UlidCreatorException e) {
+ // Ignore the overrun exception and try again
+ uuid = creator.create();
+ }
+
+ if (verbose) {
+ // Calculate and show progress
+ progress = (i * 1.0 / max) * 100;
+ if (progress % 1 == 0) {
+ System.out.println(String.format("[Thread %06d] %s %s %s%%", id, uuid, i, (int) progress));
+ }
+ }
+ synchronized (hashSet) {
+ // Insert the value in cache, if it does not exist in it.
+ if (!hashSet.add(uuid)) {
+ throw new UlidCreatorException(
+ String.format("[DUPLICATE][Thread %s] %s %s %s%%", id, uuid, i, (int) progress));
+ }
+ }
+ }
+
+ if (verbose) {
+ // Finished
+ System.out.println(String.format("[Thread %s] Done.", id));
+ }
+ }
+ }
+
+ public static void execute(boolean verbose, int threadCount, int requestCount) {
+ GuidCreator creator = UlidCreator.getGuidCreator()
+ .withTimestampStrategy(new FixedTimestampStretegy(System.currentTimeMillis()));
+
+ UniquenessTest test = new UniquenessTest(threadCount, requestCount, creator, verbose);
+ test.start();
+ }
+
+ public static void main(String[] args) {
+ boolean verbose = true;
+ int threadCount = 16; // Number of threads to run
+ int requestCount = 1_000_000; // Number of requests for thread
+ execute(verbose, threadCount, requestCount);
+ }
+}
diff --git a/src/test/java/com/github/f4b6a3/demo/DemoTest.java b/src/test/java/com/github/f4b6a3/demo/DemoTest.java
new file mode 100644
index 0000000..2a5b274
--- /dev/null
+++ b/src/test/java/com/github/f4b6a3/demo/DemoTest.java
@@ -0,0 +1,32 @@
+package com.github.f4b6a3.demo;
+
+import com.github.f4b6a3.ulid.UlidCreator;
+
+public class DemoTest {
+
+ private static final String HORIZONTAL_LINE = "----------------------------------------";
+
+ public static void printList() {
+ int max = 1_000;
+
+ System.out.println(HORIZONTAL_LINE);
+ System.out.println("### ULID");
+ System.out.println(HORIZONTAL_LINE);
+
+ for (int i = 0; i < max; i++) {
+ System.out.println(UlidCreator.getUlid());
+ }
+
+ System.out.println(HORIZONTAL_LINE);
+ System.out.println("### GUID");
+ System.out.println(HORIZONTAL_LINE);
+
+ for (int i = 0; i < max; i++) {
+ System.out.println(UlidCreator.getGuid());
+ }
+ }
+
+ public static void main(String[] args) {
+ printList();
+ }
+}
diff --git a/src/test/java/com/github/f4b6a3/ulid/UlidCreatorTest.java b/src/test/java/com/github/f4b6a3/ulid/UlidCreatorTest.java
index d4ec39b..3681817 100644
--- a/src/test/java/com/github/f4b6a3/ulid/UlidCreatorTest.java
+++ b/src/test/java/com/github/f4b6a3/ulid/UlidCreatorTest.java
@@ -12,7 +12,7 @@ import java.util.HashSet;
public class UlidCreatorTest {
private static final int ULID_LENGTH = 26;
- private static final int DEFAULT_LOOP_MAX = 10_000;
+ private static final int DEFAULT_LOOP_MAX = 100_000;
@Test
public void testGetUlid() {
diff --git a/src/test/java/com/github/f4b6a3/ulid/factory/LexicalOrderGuidCreatorMock.java b/src/test/java/com/github/f4b6a3/ulid/factory/LexicalOrderGuidCreatorMock.java
deleted file mode 100644
index 9f96741..0000000
--- a/src/test/java/com/github/f4b6a3/ulid/factory/LexicalOrderGuidCreatorMock.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.github.f4b6a3.ulid.factory;
-
-class LexicalOrderGuidCreatorMock extends LexicalOrderGuidCreator {
- public LexicalOrderGuidCreatorMock(long low, long high, long previousTimestamp) {
- super();
- this.low = low;
- this.high = high;
- this.previousTimestamp = previousTimestamp;
- }
-}
\ No newline at end of file
diff --git a/src/test/java/com/github/f4b6a3/ulid/guid/GuidCreatorMock.java b/src/test/java/com/github/f4b6a3/ulid/guid/GuidCreatorMock.java
new file mode 100644
index 0000000..539a653
--- /dev/null
+++ b/src/test/java/com/github/f4b6a3/ulid/guid/GuidCreatorMock.java
@@ -0,0 +1,12 @@
+package com.github.f4b6a3.ulid.guid;
+
+import com.github.f4b6a3.ulid.guid.GuidCreator;
+
+class GuidCreatorMock extends GuidCreator {
+ public GuidCreatorMock(long low, long high, long previousTimestamp) {
+ super();
+ this.low = low;
+ this.high = high;
+ this.previousTimestamp = previousTimestamp;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/github/f4b6a3/ulid/factory/LexicalOrderGuidCreatorTest.java b/src/test/java/com/github/f4b6a3/ulid/guid/GuidCreatorTest.java
similarity index 73%
rename from src/test/java/com/github/f4b6a3/ulid/factory/LexicalOrderGuidCreatorTest.java
rename to src/test/java/com/github/f4b6a3/ulid/guid/GuidCreatorTest.java
index cd13e6f..10496c0 100644
--- a/src/test/java/com/github/f4b6a3/ulid/factory/LexicalOrderGuidCreatorTest.java
+++ b/src/test/java/com/github/f4b6a3/ulid/guid/GuidCreatorTest.java
@@ -1,4 +1,4 @@
-package com.github.f4b6a3.ulid.factory;
+package com.github.f4b6a3.ulid.guid;
import java.util.Random;
import java.util.UUID;
@@ -6,19 +6,19 @@ import java.util.UUID;
import org.junit.Test;
import com.github.f4b6a3.ulid.exception.UlidCreatorException;
-import com.github.f4b6a3.ulid.factory.LexicalOrderGuidCreator.LexicalOrderGuidException;
+import com.github.f4b6a3.ulid.guid.GuidCreator;
import com.github.f4b6a3.ulid.random.Xorshift128PlusRandom;
import com.github.f4b6a3.ulid.timestamp.FixedTimestampStretegy;
import static org.junit.Assert.*;
-public class LexicalOrderGuidCreatorTest {
+public class GuidCreatorTest {
- private static final long DEFAULT_LOOP = 1000;
+ private static final long DEFAULT_LOOP_MAX = 100_000;
private static final long TIMESTAMP = System.currentTimeMillis();
- private static final long MAX_LOW = LexicalOrderGuidCreator.MAX_LOW;
- private static final long MAX_HIGH = LexicalOrderGuidCreator.MAX_HIGH;
+ private static final long MAX_LOW = GuidCreator.MAX_LOW;
+ private static final long MAX_HIGH = GuidCreator.MAX_HIGH;
private static final Random RANDOM = new Xorshift128PlusRandom();
@@ -28,13 +28,13 @@ public class LexicalOrderGuidCreatorTest {
long low = RANDOM.nextInt();
long high = RANDOM.nextInt(Short.MAX_VALUE);
- LexicalOrderGuidCreatorMock creator = new LexicalOrderGuidCreatorMock(low, high, TIMESTAMP);
+ GuidCreatorMock creator = new GuidCreatorMock(low, high, TIMESTAMP);
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
UUID uuid = creator.create();
long firstMsb = (short) uuid.getMostSignificantBits();
long lastMsb = 0;
- for (int i = 0; i <= DEFAULT_LOOP; i++) {
+ for (int i = 0; i <= DEFAULT_LOOP_MAX; i++) {
uuid = creator.create();
lastMsb = (short) uuid.getMostSignificantBits();
}
@@ -50,21 +50,21 @@ public class LexicalOrderGuidCreatorTest {
@Test
public void testRandomLeastSignificantBits() {
- LexicalOrderGuidCreator creator = new LexicalOrderGuidCreator();
+ GuidCreator creator = new GuidCreator();
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
UUID uuid = creator.create();
long firstLsb = uuid.getLeastSignificantBits();
long lastLsb = 0;
- for (int i = 0; i < DEFAULT_LOOP; i++) {
+ for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
uuid = creator.create();
lastLsb = uuid.getLeastSignificantBits();
}
- long expected = firstLsb + DEFAULT_LOOP;
+ 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 + 1;
+ long notExpected = firstLsb + DEFAULT_LOOP_MAX + 1;
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP + 1));
uuid = creator.create();
lastLsb = uuid.getLeastSignificantBits();
@@ -77,15 +77,15 @@ public class LexicalOrderGuidCreatorTest {
long low = RANDOM.nextInt();
long high = RANDOM.nextInt(Short.MAX_VALUE);
- LexicalOrderGuidCreatorMock creator = new LexicalOrderGuidCreatorMock(low, high, TIMESTAMP);
+ GuidCreatorMock creator = new GuidCreatorMock(low, high, TIMESTAMP);
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
UUID uuid = new UUID(0, 0);
- for (int i = 0; i < DEFAULT_LOOP; i++) {
+ for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
uuid = creator.create();
}
- long expected = low + DEFAULT_LOOP;
+ long expected = low + DEFAULT_LOOP_MAX;
long randomLsb = uuid.getLeastSignificantBits();
assertEquals(String.format("The LSB should be iqual to %s.", expected), expected, randomLsb);
}
@@ -96,11 +96,11 @@ public class LexicalOrderGuidCreatorTest {
long low = MAX_LOW;
long high = RANDOM.nextInt(Short.MAX_VALUE);
- LexicalOrderGuidCreatorMock creator = new LexicalOrderGuidCreatorMock(low, high, TIMESTAMP);
+ GuidCreatorMock creator = new GuidCreatorMock(low, high, TIMESTAMP);
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
UUID uuid = new UUID(0, 0);
- for (int i = 0; i < DEFAULT_LOOP; i++) {
+ for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
uuid = creator.create();
}
@@ -109,17 +109,17 @@ public class LexicalOrderGuidCreatorTest {
assertEquals(String.format("The MSB should be iqual to %s.", expected), expected, randomMsb);
}
- @Test(expected = LexicalOrderGuidException.class)
+ @Test(expected = UlidCreatorException.class)
public void testShouldThrowOverflowException() {
- long low = MAX_LOW - DEFAULT_LOOP;
+ long low = MAX_LOW - DEFAULT_LOOP_MAX;
long high = MAX_HIGH;
- LexicalOrderGuidCreatorMock creator = new LexicalOrderGuidCreatorMock(low, high, TIMESTAMP);
+ GuidCreatorMock creator = new GuidCreatorMock(low, high, TIMESTAMP);
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
UUID uuid = new UUID(0, 0);
- for (int i = 0; i < DEFAULT_LOOP; i++) {
+ for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
uuid = creator.create();
}
diff --git a/src/test/java/com/github/f4b6a3/ulid/random/NaiveRandomTest.java b/src/test/java/com/github/f4b6a3/ulid/random/NaiveRandomTest.java
index a6ccc88..b5226d5 100644
--- a/src/test/java/com/github/f4b6a3/ulid/random/NaiveRandomTest.java
+++ b/src/test/java/com/github/f4b6a3/ulid/random/NaiveRandomTest.java
@@ -6,7 +6,7 @@ import static org.junit.Assert.*;
public class NaiveRandomTest {
- private static final int DEFAULT_LOOP_LIMIT = 10_000;
+ private static final int DEFAULT_LOOP_LIMIT = 100_000;
private static final String EXPECTED_BIT_COUNT_RANDOM_LONG = "The average bit count expected for random long values is 32";
@Test
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 3e7e49a..e50f0f3 100644
--- a/src/test/java/com/github/f4b6a3/ulid/util/UlidUtilTest.java
+++ b/src/test/java/com/github/f4b6a3/ulid/util/UlidUtilTest.java
@@ -7,7 +7,7 @@ import java.util.UUID;
import org.junit.Test;
import com.github.f4b6a3.ulid.util.UlidUtil.UlidUtilException;
-import com.github.f4b6a3.ulid.util.ByteUtil;
+import com.github.f4b6a3.ulid.UlidCreator;
public class UlidUtilTest {
@@ -18,7 +18,7 @@ public class UlidUtilTest {
private static final long TIMESTAMP_MAX = 281474976710655l; // 2^48 - 1
private static final int ULID_LENGTH = 26;
- private static final int DEFAULT_LOOP_MAX = 10_000;
+ private static final int DEFAULT_LOOP_MAX = 100_000;
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" };
@@ -177,28 +177,25 @@ public class UlidUtilTest {
assertFalse("ULID with timestamp greater than (2^48)-1 should be invalid.", UlidUtil.isValid(ulid));
}
- // TODO
- // @Test
- // public void testToAndFromUlid() {
- //
- // for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
- //
- // // Use random values
- // UUID uuid = UuidCreator.getFastRandom();
- // String ulid = UlidUtil.fromUuidToUlid(uuid);
- //
- // assertTrue("ULID is null", ulid != null);
- // assertTrue("ULID is empty", !ulid.isEmpty());
- // assertTrue("ULID length is wrong ", ulid.length() == ULID_LENGTH);
- // assertTrue("ULID is not valid", UlidUtil.isValid(ulid, /* strict */
- // true));
- //
- // UUID result = UlidUtil.fromUlidToUuid(ulid);
- // assertEquals("Result ULID is different from original ULID", uuid,
- // result);
- //
- // }
- // }
+ @Test
+ public void testToAndFromUlid() {
+
+ for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
+
+ UUID uuid = UlidCreator.getFastGuid();
+ String ulid = UlidUtil.fromUuidToUlid(uuid);
+
+ assertTrue("ULID is null", ulid != null);
+ assertTrue("ULID is empty", !ulid.isEmpty());
+ assertTrue("ULID length is wrong ", ulid.length() == ULID_LENGTH);
+ assertTrue("ULID is not valid", UlidUtil.isValid(ulid, /* strict */
+ true));
+
+ UUID result = UlidUtil.fromUlidToUuid(ulid);
+ assertEquals("Result ULID is different from original ULID", uuid, result);
+
+ }
+ }
private String leftPad(String unpadded) {
return "0000000000".substring(unpadded.length()) + unpadded;