Rearranging the code

This commit is contained in:
Fabio Lima 2020-02-22 13:07:16 -03:00
parent 63bee56534
commit f885a03125
15 changed files with 871 additions and 103 deletions

153
README.md
View File

@ -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 <sup>[9]</sup>. 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.

View File

@ -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();
}
}

View File

@ -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:
*
@ -126,6 +129,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 extends LexicalOrderGuidCreator> T withTimestampStrategy(
TimestampStrategy timestampStrategy) {
public synchronized <T extends GuidCreator> 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 extends LexicalOrderGuidCreator> T withRandomGenerator(Random random) {
public synchronized <T extends GuidCreator> 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 extends LexicalOrderGuidCreator> T withFastRandomGenerator() {
this.random = new Xorshift128PlusRandom();
public synchronized <T extends GuidCreator> 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 extends LexicalOrderGuidCreator> T withoutOverflowException() {
public synchronized <T extends GuidCreator> 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();
}

View File

@ -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<NetworkData> 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);
}
}
}

View File

@ -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<String> 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<String> getInterfaceAddresses() {
return interfaceAddresses;
}
public void setInterfaceAddresses(List<String> 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<NetworkData> getNetworkDataList() {
try {
InetAddress inetAddress = InetAddress.getLocalHost();
List<NetworkInterface> networkInterfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
HashSet<NetworkData> 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<String> 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<String> getInterfaceAddresses(NetworkInterface networkInterface) {
HashSet<String> addresses = new HashSet<>();
List<InterfaceAddress> 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);
}
}

View File

@ -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 {
}

View File

@ -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<UUID> 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);
}
}

View File

@ -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();
}
}

View File

@ -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() {

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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

View File

@ -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;