Development of version 3.0.0 #7
continuing... List of changes: Improve Ulid Create UlidFactory Create DefaultUlidFactory Create MonotonicUlidFactory Improve UlidTest Create UlidFactoryTest Create DefaultUlidFactoryTest Create MonotonicUlidFactoryTest Update UniquenessTest Update README.md Update javadoc Test coverage: 99.3%
This commit is contained in:
parent
3aa29be465
commit
f1eaebd3bd
256
README.md
256
README.md
|
@ -1,13 +1,15 @@
|
||||||
|
|
||||||
|
|
||||||
# ULID Creator
|
# ULID Creator
|
||||||
|
|
||||||
A Java library for generating ULIDs.
|
A Java library for generating [ULIDs](https://github.com/ulid/spec).
|
||||||
|
|
||||||
* Generated in lexicographical order;
|
* Generated in lexicographical order;
|
||||||
* Can be stored as a UUID/GUID;
|
* Can be stored as a UUID/GUID;
|
||||||
* Can be stored as a string of 26 chars;
|
* Can be stored as a string of 26 chars;
|
||||||
|
* Can be stored as an array of 16 bytes;
|
||||||
* String format is encoded to [Crockford's base32](https://www.crockford.com/base32.html);
|
* String format is encoded to [Crockford's base32](https://www.crockford.com/base32.html);
|
||||||
* String format is URL safe, case insensitive and accepts hyphens.
|
* String format is URL safe and case insensitive.
|
||||||
|
|
||||||
How to Use
|
How to Use
|
||||||
------------------------------------------------------
|
------------------------------------------------------
|
||||||
|
@ -15,13 +17,13 @@ How to Use
|
||||||
Create a ULID:
|
Create a ULID:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
UUID ulid = UlidCreator.getUlid(); // 01706d6c-6aad-c795-370c-98d0be881bba
|
Ulid ulid = UlidCreator.getUlid();
|
||||||
```
|
```
|
||||||
|
|
||||||
Create a ULID string:
|
Create a Monotonic ULID:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
String ulid = UlidCreator.getUlidString(); // 01E1PPRTMSQ34W7JR5YSND6B8Z
|
Ulid ulid = UlidCreator.getMonotonicUlid();
|
||||||
```
|
```
|
||||||
|
|
||||||
### Maven dependency
|
### Maven dependency
|
||||||
|
@ -33,7 +35,7 @@ Add these lines to your `pom.xml`.
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.f4b6a3</groupId>
|
<groupId>com.github.f4b6a3</groupId>
|
||||||
<artifactId>ulid-creator</artifactId>
|
<artifactId>ulid-creator</artifactId>
|
||||||
<version>2.3.3</version>
|
<version>3.0.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator).
|
See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator).
|
||||||
|
@ -43,116 +45,162 @@ Implementation
|
||||||
|
|
||||||
### ULID
|
### ULID
|
||||||
|
|
||||||
The GUIDs in this library are based on the [ULID specification](https://github.com/ulid/spec). 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.
|
The ULID is a 128 bit long identifier. 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 `java.security.SecureRandom`.
|
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// GUID based on ULID spec
|
// Generate a ULID
|
||||||
UUID ulid = UlidCreator.getUlid();
|
Ulid ulid = UlidCreator.getUlid();
|
||||||
```
|
```
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// GUID based on ULID spec
|
// Generate a ULID with a specific time
|
||||||
// Compatible with RFC-4122 UUID v4
|
Ulid ulid = UlidCreator.getUlid(1234567890);
|
||||||
UUID ulid = UlidCreator.getUlid4();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Sequence of GUIDs based on ULID spec:
|
Sequence of ULIDs:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
01706d6c-6aac-80bd-7ff5-f660c2dd58ea
|
01EX8Y21KBH49ZZCA7KSKH6X1C
|
||||||
01706d6c-6aac-80bd-7ff5-f660c2dd58eb
|
01EX8Y21KBJTFK0JV5J20QPQNR
|
||||||
01706d6c-6aac-80bd-7ff5-f660c2dd58ec
|
01EX8Y21KBG2CS1V6WQCTVM7K6
|
||||||
01706d6c-6aac-80bd-7ff5-f660c2dd58ed
|
01EX8Y21KB8HPZNBP3PTW7HVEY
|
||||||
01706d6c-6aac-80bd-7ff5-f660c2dd58ee
|
01EX8Y21KB3HZV38VAPTPAG1TY
|
||||||
01706d6c-6aac-80bd-7ff5-f660c2dd58ef
|
01EX8Y21KB9FTEJHPAGAKYG9Z8
|
||||||
01706d6c-6aac-80bd-7ff5-f660c2dd58f0
|
01EX8Y21KBQGKGH2SVPQAYEFFC
|
||||||
01706d6c-6aac-80bd-7ff5-f660c2dd58f1
|
01EX8Y21KBY17J9WR9KQR8SE7H
|
||||||
01706d6c-6aad-c795-370c-98d0be881bb8 < millisecond changed
|
01EX8Y21KCVHYSJGVK4HBXDMR9 < millisecond changed
|
||||||
01706d6c-6aad-c795-370c-98d0be881bb9
|
01EX8Y21KC668W3PEDEAGDHMVG
|
||||||
01706d6c-6aad-c795-370c-98d0be881bba
|
01EX8Y21KC53D2S5ADQ2EST327
|
||||||
01706d6c-6aad-c795-370c-98d0be881bbb
|
01EX8Y21KCPQ3TENMTY1S7HV56
|
||||||
01706d6c-6aad-c795-370c-98d0be881bbc
|
01EX8Y21KC3755QF9STQEV05EB
|
||||||
01706d6c-6aad-c795-370c-98d0be881bbd
|
01EX8Y21KC5ZSHK908GMDK69WE
|
||||||
01706d6c-6aad-c795-370c-98d0be881bbe
|
01EX8Y21KCSGJS8S1FVS06B3SX
|
||||||
01706d6c-6aad-c795-370c-98d0be881bbf
|
01EX8Y21KC6ZBWQ0JBV337R1CN
|
||||||
^ look ^ look
|
^ look
|
||||||
|
|
||||||
|------------|---------------------|
|
|
||||||
millisecs randomness
|
|
||||||
```
|
|
||||||
|
|
||||||
### ULID string
|
|
||||||
|
|
||||||
The ULID string is a sequence of 26 chars. 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
|
|
||||||
// String based on ULID spec
|
|
||||||
String ulid = UlidCreator.getUlidString();
|
|
||||||
```
|
|
||||||
|
|
||||||
```java
|
|
||||||
// String based on ULID spec
|
|
||||||
// Compatible with RFC-4122 UUID v4
|
|
||||||
String ulid = UlidCreator.getUlidString4();
|
|
||||||
```
|
|
||||||
|
|
||||||
Sequence of Strings based on ULID spec:
|
|
||||||
|
|
||||||
```text
|
|
||||||
01E1PPRTMSQ34W7JR5YSND6B8T
|
|
||||||
01E1PPRTMSQ34W7JR5YSND6B8V
|
|
||||||
01E1PPRTMSQ34W7JR5YSND6B8W
|
|
||||||
01E1PPRTMSQ34W7JR5YSND6B8X
|
|
||||||
01E1PPRTMSQ34W7JR5YSND6B8Y
|
|
||||||
01E1PPRTMSQ34W7JR5YSND6B8Z
|
|
||||||
01E1PPRTMSQ34W7JR5YSND6B90
|
|
||||||
01E1PPRTMSQ34W7JR5YSND6B91
|
|
||||||
01E1PPRTMTYMX8G17TWSJJZMEE < millisecond changed
|
|
||||||
01E1PPRTMTYMX8G17TWSJJZMEF
|
|
||||||
01E1PPRTMTYMX8G17TWSJJZMEG
|
|
||||||
01E1PPRTMTYMX8G17TWSJJZMEH
|
|
||||||
01E1PPRTMTYMX8G17TWSJJZMEJ
|
|
||||||
01E1PPRTMTYMX8G17TWSJJZMEK
|
|
||||||
01E1PPRTMTYMX8G17TWSJJZMEM
|
|
||||||
01E1PPRTMTYMX8G17TWSJJZMEN
|
|
||||||
^ look ^ look
|
|
||||||
|
|
||||||
|---------|--------------|
|
|---------|--------------|
|
||||||
millisecs randomness
|
time random
|
||||||
```
|
```
|
||||||
|
|
||||||
### How use the `UlidSpecCreator` directly
|
### Monotonic ULID
|
||||||
|
|
||||||
These are some examples of using the `UlidSpecCreator` to create ULID strings:
|
The Monotonic ULID is a 128 bit long identifier. 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.
|
||||||
|
|
||||||
|
The random component is incremented by 1 whenever the current millisecond is equal to the previous one. But when the current millisecond is different, the random component changes to another random value.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// with your custom timestamp strategy
|
// Generate a Monotonic ULID
|
||||||
TimestampStrategy customStrategy = new CustomTimestampStrategy();
|
Ulid ulid = UlidCreator.getMonotonicUlid();
|
||||||
UlidSpecCreator creator = UlidCreator.getUlidSpecCreator()
|
|
||||||
.withTimestampStrategy(customStrategy);
|
|
||||||
String ulid = creator.createString();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// with your custom random strategy that wraps any random generator
|
// Generate a Monotonic ULID with a specific time
|
||||||
RandomStrategy customStrategy = new CustomRandomStrategy();
|
Ulid ulid = UlidCreator.getMonotonicUlid(1234567890);
|
||||||
UlidSpecCreator creator = UlidCreator.getUlidSpecCreator()
|
|
||||||
.withRandomStrategy(customStrategy);
|
|
||||||
String ulid = creator.createString();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Sequence of Monotonic ULIDs:
|
||||||
|
|
||||||
|
```text
|
||||||
|
01EX8Y7M8MDVX3M3EQG69EEMJW
|
||||||
|
01EX8Y7M8MDVX3M3EQG69EEMJX
|
||||||
|
01EX8Y7M8MDVX3M3EQG69EEMJY
|
||||||
|
01EX8Y7M8MDVX3M3EQG69EEMJZ
|
||||||
|
01EX8Y7M8MDVX3M3EQG69EEMK0
|
||||||
|
01EX8Y7M8MDVX3M3EQG69EEMK1
|
||||||
|
01EX8Y7M8MDVX3M3EQG69EEMK2
|
||||||
|
01EX8Y7M8MDVX3M3EQG69EEMK3
|
||||||
|
01EX8Y7M8N1G30CYF2PJR23J2J < millisecond changed
|
||||||
|
01EX8Y7M8N1G30CYF2PJR23J2K
|
||||||
|
01EX8Y7M8N1G30CYF2PJR23J2M
|
||||||
|
01EX8Y7M8N1G30CYF2PJR23J2N
|
||||||
|
01EX8Y7M8N1G30CYF2PJR23J2P
|
||||||
|
01EX8Y7M8N1G30CYF2PJR23J2Q
|
||||||
|
01EX8Y7M8N1G30CYF2PJR23J2R
|
||||||
|
01EX8Y7M8N1G30CYF2PJR23J2S
|
||||||
|
^ look ^ look
|
||||||
|
|
||||||
|
|---------|--------------|
|
||||||
|
time random
|
||||||
|
```
|
||||||
|
|
||||||
|
### Other usage examples
|
||||||
|
|
||||||
|
Create a ULID from a canonical string (26 chars):
|
||||||
|
|
||||||
|
```java
|
||||||
|
Ulid ulid = Ulid.from("0123456789ABCDEFGHJKMNPQRS");
|
||||||
|
```
|
||||||
|
|
||||||
|
Convert a ULID into a canonical string in upper case:
|
||||||
|
|
||||||
|
```java
|
||||||
|
String string = ulid.toUpperCase(); // 0123456789ABCDEFGHJKMNPQRS
|
||||||
|
```
|
||||||
|
|
||||||
|
Convert a ULID into a canonical string in lower case:
|
||||||
|
|
||||||
|
```java
|
||||||
|
String string = ulid.toLowerCase(); // 0123456789abcdefghjkmnpqrs
|
||||||
|
```
|
||||||
|
|
||||||
|
Convert a ULID into a UUID:
|
||||||
|
|
||||||
|
```java
|
||||||
|
UUID uuid = ulid.toUuid(); // 0110c853-1d09-52d8-d73e-1194e95b5f19
|
||||||
|
```
|
||||||
|
|
||||||
|
Convert a ULID into a [RFC-4122](https://tools.ietf.org/html/rfc4122) UUID v4:
|
||||||
|
|
||||||
|
```java
|
||||||
|
UUID uuid = ulid.toRfc4122().toUuid(); // 0110c853-1d09-42d8-973e-1194e95b5f19
|
||||||
|
// ^ UUID v4
|
||||||
|
```
|
||||||
|
|
||||||
|
Convert a ULID into a byte array:
|
||||||
|
|
||||||
|
```java
|
||||||
|
byte[] bytes = ulid.toBytes(); // 16 bytes (128 bits)
|
||||||
|
```
|
||||||
|
|
||||||
|
Get the creation instant of a ULID:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Instant instant = ulid.getInstant(); // 2007-02-16T02:13:14.633Z
|
||||||
|
```
|
||||||
|
|
||||||
|
Get the time component of a ULID:
|
||||||
|
|
||||||
|
```java
|
||||||
|
long time = ulid.getTime(); // 1171591994633
|
||||||
|
```
|
||||||
|
|
||||||
|
Get the random component of a ULID:
|
||||||
|
|
||||||
|
```java
|
||||||
|
byte[] random = ulid.getRandom(); // 10 bytes (80 bits)
|
||||||
|
```
|
||||||
|
|
||||||
|
Use a `UlidFctory` instance with `java.util.Random` to generate ULIDs:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// with `java.util.Random` number generator
|
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
UlidSpecCreator creator = UlidCreator.getUlidSpecCreator()
|
UlidFactory factory = UlidCreator.getDefaultFactory().withRandomGenerator(random::nextBytes);
|
||||||
.withRandomGenerator(random);
|
|
||||||
String ulid = creator.createString();
|
Ulid ulid = facory.create();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Use a `UlidFctory` instance with any random generator you like(*) to generate ULIDs:
|
||||||
|
|
||||||
|
```java
|
||||||
|
import com.github.niceguy.random.AwesomeRandom; // a hypothetical RNG
|
||||||
|
AwesomeRandom awesomeRandom = new AwesomeRandom();
|
||||||
|
UlidFactory factory = UlidCreator.getDefaultFactory().withRandomGenerator(awesomeRandom::nextBytes);
|
||||||
|
|
||||||
|
Ulid ulid = facory.create();
|
||||||
|
```
|
||||||
|
|
||||||
|
(*) since it provides a void method like `nextBytes(byte[])`.
|
||||||
|
|
||||||
Benchmark
|
Benchmark
|
||||||
------------------------------------------------------
|
------------------------------------------------------
|
||||||
|
|
||||||
|
@ -160,14 +208,18 @@ This section shows benchmarks comparing `UlidCreator` to `java.util.UUID`.
|
||||||
|
|
||||||
```
|
```
|
||||||
================================================================================
|
================================================================================
|
||||||
THROUGHPUT (operations/millis) Mode Cnt Score Error Units
|
THROUGHPUT (operations/msec) Mode Cnt Score Error Units
|
||||||
================================================================================
|
================================================================================
|
||||||
Throughput.JDK_RandomBased thrpt 5 2050,995 ± 21,636 ops/ms
|
Throughput.Uuid01_toString thrpt 5 2876,799 ± 39,938 ops/ms
|
||||||
|
Throughput.Uuid02_fromString thrpt 5 1936,569 ± 38,822 ops/ms
|
||||||
|
Throughput.Uuid03_RandomBased thrpt 5 2011,774 ± 21,198 ops/ms
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
Throughput.UlidCreator_Ulid thrpt 5 18524,721 ± 563,781 ops/ms
|
Throughput.UlidCreator01_toString thrpt 5 29487,382 ± 627,808 ops/ms
|
||||||
Throughput.UlidCreator_UlidString thrpt 5 12223,501 ± 89,836 ops/ms
|
Throughput.UlidCreator02_fromString thrpt 5 21194,263 ± 706,398 ops/ms
|
||||||
|
Throughput.UlidCreator03_Ulid thrpt 5 2745,123 ± 41,326 ops/ms
|
||||||
|
Throughput.UlidCreator04_MonotonicUlid thrpt 5 19542,344 ± 423,271 ops/ms
|
||||||
================================================================================
|
================================================================================
|
||||||
Total time: 00:04:00
|
Total time: 00:09:22
|
||||||
================================================================================
|
================================================================================
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -175,8 +227,8 @@ System: JVM 8, Ubuntu 20.04, CPU i5-3330, 8G RAM.
|
||||||
|
|
||||||
See: [uuid-creator-benchmark](https://github.com/fabiolimace/uuid-creator-benchmark)
|
See: [uuid-creator-benchmark](https://github.com/fabiolimace/uuid-creator-benchmark)
|
||||||
|
|
||||||
Links for generators
|
Other generators
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
* [UUID Creator](https://github.com/f4b6a3/uuid-creator)
|
* [UUID Creator](https://github.com/f4b6a3/uuid-creator): for generating UUIDs
|
||||||
* [ULID Creator](https://github.com/f4b6a3/ulid-creator)
|
* [TSID Creator](https://github.com/f4b6a3/tsid-creator): for generating Time Sortable IDs
|
||||||
* [TSID Creator](https://github.com/f4b6a3/tsid-creator)
|
|
||||||
|
|
2
pom.xml
2
pom.xml
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
<groupId>com.github.f4b6a3</groupId>
|
<groupId>com.github.f4b6a3</groupId>
|
||||||
<artifactId>ulid-creator</artifactId>
|
<artifactId>ulid-creator</artifactId>
|
||||||
<version>2.3.4-SNAPSHOT</version>
|
<version>3.0.1-SNAPSHOT</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>ulid-creator</name>
|
<name>ulid-creator</name>
|
||||||
|
|
|
@ -30,13 +30,23 @@ import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a ULID.
|
* This class represents a ULID.
|
||||||
|
*
|
||||||
|
* The ULID has two components:
|
||||||
|
*
|
||||||
|
* - Time component: a part of 48 bits that represent the amount of milliseconds
|
||||||
|
* since Unix Epoch, 1970-01-01.
|
||||||
|
*
|
||||||
|
* - Random component: a byte array of 80 bits that has a random value generated
|
||||||
|
* a secure random generator.
|
||||||
|
*
|
||||||
|
* Instances of this class are immutable.
|
||||||
*/
|
*/
|
||||||
public final class Ulid implements Serializable, Comparable<Ulid> {
|
public final class Ulid implements Serializable, Comparable<Ulid> {
|
||||||
|
|
||||||
private final long msb;
|
private static final long serialVersionUID = 2625269413446854731L;
|
||||||
private final long lsb;
|
|
||||||
|
|
||||||
protected static final long TIME_MAX = 281474976710655L; // 2^48 - 1
|
private final long msb; // most significant bits
|
||||||
|
private final long lsb; // least significant bits
|
||||||
|
|
||||||
public static final int ULID_LENGTH = 26;
|
public static final int ULID_LENGTH = 26;
|
||||||
public static final int TIME_LENGTH = 10;
|
public static final int TIME_LENGTH = 10;
|
||||||
|
@ -46,20 +56,17 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
||||||
public static final int TIME_BYTES_LENGTH = 6;
|
public static final int TIME_BYTES_LENGTH = 6;
|
||||||
public static final int RANDOM_BYTES_LENGTH = 10;
|
public static final int RANDOM_BYTES_LENGTH = 10;
|
||||||
|
|
||||||
// 0xffffffffffffffffL + 1 = 0x0000000000000000L
|
private static final char[] ALPHABET_UPPERCASE = //
|
||||||
private static final long INCREMENT_OVERFLOW = 0x0000000000000000L;
|
|
||||||
|
|
||||||
protected static final char[] ALPHABET_UPPERCASE = //
|
|
||||||
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', //
|
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', //
|
||||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', //
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', //
|
||||||
'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z' };
|
'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z' };
|
||||||
|
|
||||||
protected static final char[] ALPHABET_LOWERCASE = //
|
private static final char[] ALPHABET_LOWERCASE = //
|
||||||
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', //
|
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', //
|
||||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', //
|
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', //
|
||||||
'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z' };
|
'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z' };
|
||||||
|
|
||||||
protected static final long[] ALPHABET_VALUES = new long[128];
|
private static final long[] ALPHABET_VALUES = new long[128];
|
||||||
static {
|
static {
|
||||||
for (int i = 0; i < ALPHABET_VALUES.length; i++) {
|
for (int i = 0; i < ALPHABET_VALUES.length; i++) {
|
||||||
ALPHABET_VALUES[i] = -1;
|
ALPHABET_VALUES[i] = -1;
|
||||||
|
@ -129,139 +136,190 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
||||||
ALPHABET_VALUES['O'] = 0x00;
|
ALPHABET_VALUES['O'] = 0x00;
|
||||||
ALPHABET_VALUES['I'] = 0x01;
|
ALPHABET_VALUES['I'] = 0x01;
|
||||||
ALPHABET_VALUES['L'] = 0x01;
|
ALPHABET_VALUES['L'] = 0x01;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final long serialVersionUID = 2625269413446854731L;
|
// 0xffffffffffffffffL + 1 = 0x0000000000000000L
|
||||||
|
private static final long INCREMENT_OVERFLOW = 0x0000000000000000L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new ULID.
|
||||||
|
*
|
||||||
|
* Useful to make copies of ULIDs.
|
||||||
|
*
|
||||||
|
* @param ulid a ULID
|
||||||
|
*/
|
||||||
|
public Ulid(Ulid ulid) {
|
||||||
|
this.msb = ulid.getMostSignificantBits();
|
||||||
|
this.lsb = ulid.getLeastSignificantBits();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new ULID.
|
||||||
|
*
|
||||||
|
* @param mostSignificantBits the first 8 bytes as a long value
|
||||||
|
* @param leastSignificantBits the last 8 bytes as a long value
|
||||||
|
*/
|
||||||
public Ulid(long mostSignificantBits, long leastSignificantBits) {
|
public Ulid(long mostSignificantBits, long leastSignificantBits) {
|
||||||
this.msb = mostSignificantBits;
|
this.msb = mostSignificantBits;
|
||||||
this.lsb = leastSignificantBits;
|
this.lsb = leastSignificantBits;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Ulid of(Ulid ulid) {
|
/**
|
||||||
return new Ulid(ulid.getMostSignificantBits(), ulid.getLeastSignificantBits());
|
* Create a new ULID.
|
||||||
}
|
*
|
||||||
|
* @param time the time component in milliseconds since 1970-01-01
|
||||||
|
* @param random the random component in byte array
|
||||||
|
*/
|
||||||
|
public Ulid(long time, byte[] random) {
|
||||||
|
|
||||||
public static Ulid of(UUID uuid) {
|
// The time component has 48 bits.
|
||||||
return new Ulid(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits());
|
if ((time & 0xffff000000000000L) != 0) {
|
||||||
}
|
// ULID specification:
|
||||||
|
// "Any attempt to decode or encode a ULID larger than this (time > 2^48-1)
|
||||||
// TODO: test
|
// should be rejected by all implementations, to prevent overflow bugs."
|
||||||
public static Ulid of(byte[] bytes) {
|
throw new IllegalArgumentException("Invalid time value"); // time overflow!
|
||||||
|
}
|
||||||
if (bytes == null || bytes.length != ULID_BYTES_LENGTH) {
|
// The random component has 80 bits (10 bytes).
|
||||||
throw new IllegalArgumentException("Invalid ULID bytes");
|
if (random == null || random.length != RANDOM_BYTES_LENGTH) {
|
||||||
|
throw new IllegalArgumentException("Invalid random bytes"); // null or wrong length!
|
||||||
}
|
}
|
||||||
|
|
||||||
long long0 = 0;
|
long long0 = 0;
|
||||||
long long1 = 0;
|
long long1 = 0;
|
||||||
|
|
||||||
long0 |= (bytes[0x0] & 0xffL) << 56;
|
long0 |= time << 16;
|
||||||
long0 |= (bytes[0x1] & 0xffL) << 48;
|
long0 |= (long) (random[0x0] & 0xff) << 8;
|
||||||
long0 |= (bytes[0x2] & 0xffL) << 40;
|
long0 |= (long) (random[0x1] & 0xff);
|
||||||
long0 |= (bytes[0x3] & 0xffL) << 32;
|
|
||||||
long0 |= (bytes[0x4] & 0xffL) << 24;
|
|
||||||
long0 |= (bytes[0x5] & 0xffL) << 16;
|
|
||||||
long0 |= (bytes[0x6] & 0xffL) << 8;
|
|
||||||
long0 |= (bytes[0x7] & 0xffL);
|
|
||||||
|
|
||||||
long1 |= (bytes[0x8] & 0xffL) << 56;
|
long1 |= (long) (random[0x2] & 0xff) << 56;
|
||||||
long1 |= (bytes[0x9] & 0xffL) << 48;
|
long1 |= (long) (random[0x3] & 0xff) << 48;
|
||||||
long1 |= (bytes[0xa] & 0xffL) << 40;
|
long1 |= (long) (random[0x4] & 0xff) << 40;
|
||||||
long1 |= (bytes[0xb] & 0xffL) << 32;
|
long1 |= (long) (random[0x5] & 0xff) << 32;
|
||||||
long1 |= (bytes[0xc] & 0xffL) << 24;
|
long1 |= (long) (random[0x6] & 0xff) << 24;
|
||||||
long1 |= (bytes[0xd] & 0xffL) << 16;
|
long1 |= (long) (random[0x7] & 0xff) << 16;
|
||||||
long1 |= (bytes[0xe] & 0xffL) << 8;
|
long1 |= (long) (random[0x8] & 0xff) << 8;
|
||||||
long1 |= (bytes[0xf] & 0xffL);
|
long1 |= (long) (random[0x9] & 0xff);
|
||||||
|
|
||||||
return new Ulid(long0, long1);
|
this.msb = long0;
|
||||||
|
this.lsb = long1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: optimize
|
/**
|
||||||
public static Ulid of(String string) {
|
* Converts a UUID into a ULID.
|
||||||
|
*
|
||||||
final char[] chars = toCharArray(string);
|
* @param uuid a UUID
|
||||||
|
* @return a ULID
|
||||||
long tm = 0;
|
*/
|
||||||
long r1 = 0;
|
public static Ulid from(UUID uuid) {
|
||||||
long r2 = 0;
|
return new Ulid(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits());
|
||||||
|
|
||||||
tm |= ALPHABET_VALUES[chars[0x00]] << 45;
|
|
||||||
tm |= ALPHABET_VALUES[chars[0x01]] << 40;
|
|
||||||
tm |= ALPHABET_VALUES[chars[0x02]] << 35;
|
|
||||||
tm |= ALPHABET_VALUES[chars[0x03]] << 30;
|
|
||||||
tm |= ALPHABET_VALUES[chars[0x04]] << 25;
|
|
||||||
tm |= ALPHABET_VALUES[chars[0x05]] << 20;
|
|
||||||
tm |= ALPHABET_VALUES[chars[0x06]] << 15;
|
|
||||||
tm |= ALPHABET_VALUES[chars[0x07]] << 10;
|
|
||||||
tm |= ALPHABET_VALUES[chars[0x08]] << 5;
|
|
||||||
tm |= ALPHABET_VALUES[chars[0x09]];
|
|
||||||
|
|
||||||
r1 |= ALPHABET_VALUES[chars[0x0a]] << 35;
|
|
||||||
r1 |= ALPHABET_VALUES[chars[0x0b]] << 30;
|
|
||||||
r1 |= ALPHABET_VALUES[chars[0x0c]] << 25;
|
|
||||||
r1 |= ALPHABET_VALUES[chars[0x0d]] << 20;
|
|
||||||
r1 |= ALPHABET_VALUES[chars[0x0e]] << 15;
|
|
||||||
r1 |= ALPHABET_VALUES[chars[0x0f]] << 10;
|
|
||||||
r1 |= ALPHABET_VALUES[chars[0x10]] << 5;
|
|
||||||
r1 |= ALPHABET_VALUES[chars[0x11]];
|
|
||||||
|
|
||||||
r2 |= ALPHABET_VALUES[chars[0x12]] << 35;
|
|
||||||
r2 |= ALPHABET_VALUES[chars[0x13]] << 30;
|
|
||||||
r2 |= ALPHABET_VALUES[chars[0x14]] << 25;
|
|
||||||
r2 |= ALPHABET_VALUES[chars[0x15]] << 20;
|
|
||||||
r2 |= ALPHABET_VALUES[chars[0x16]] << 15;
|
|
||||||
r2 |= ALPHABET_VALUES[chars[0x17]] << 10;
|
|
||||||
r2 |= ALPHABET_VALUES[chars[0x18]] << 5;
|
|
||||||
r2 |= ALPHABET_VALUES[chars[0x19]];
|
|
||||||
|
|
||||||
final long msb = (tm << 16) | (r1 >>> 24);
|
|
||||||
final long lsb = (r1 << 40) | (r2 & 0xffffffffffL);
|
|
||||||
|
|
||||||
return new Ulid(msb, lsb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Ulid of(long time, byte[] random) {
|
/**
|
||||||
|
* Converts a byte array into a ULID.
|
||||||
|
*
|
||||||
|
* @param bytes a byte array
|
||||||
|
* @return a ULID
|
||||||
|
*/
|
||||||
|
public static Ulid from(byte[] bytes) {
|
||||||
|
|
||||||
if ((time & 0xffff000000000000L) != 0) {
|
if (bytes == null || bytes.length != ULID_BYTES_LENGTH) {
|
||||||
throw new IllegalArgumentException("Invalid time value");
|
throw new IllegalArgumentException("Invalid ULID bytes"); // null or wrong length!
|
||||||
}
|
|
||||||
if (random == null || random.length != RANDOM_BYTES_LENGTH) {
|
|
||||||
throw new IllegalArgumentException("Invalid random bytes");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
long msb = 0;
|
long msb = 0;
|
||||||
long lsb = 0;
|
long lsb = 0;
|
||||||
|
|
||||||
msb |= time << 16;
|
msb |= (bytes[0x0] & 0xffL) << 56;
|
||||||
msb |= (long) (random[0x0] & 0xff) << 8;
|
msb |= (bytes[0x1] & 0xffL) << 48;
|
||||||
msb |= (long) (random[0x1] & 0xff);
|
msb |= (bytes[0x2] & 0xffL) << 40;
|
||||||
|
msb |= (bytes[0x3] & 0xffL) << 32;
|
||||||
|
msb |= (bytes[0x4] & 0xffL) << 24;
|
||||||
|
msb |= (bytes[0x5] & 0xffL) << 16;
|
||||||
|
msb |= (bytes[0x6] & 0xffL) << 8;
|
||||||
|
msb |= (bytes[0x7] & 0xffL);
|
||||||
|
|
||||||
lsb |= (long) (random[0x2] & 0xff) << 56;
|
lsb |= (bytes[0x8] & 0xffL) << 56;
|
||||||
lsb |= (long) (random[0x3] & 0xff) << 48;
|
lsb |= (bytes[0x9] & 0xffL) << 48;
|
||||||
lsb |= (long) (random[0x4] & 0xff) << 40;
|
lsb |= (bytes[0xa] & 0xffL) << 40;
|
||||||
lsb |= (long) (random[0x5] & 0xff) << 32;
|
lsb |= (bytes[0xb] & 0xffL) << 32;
|
||||||
lsb |= (long) (random[0x6] & 0xff) << 24;
|
lsb |= (bytes[0xc] & 0xffL) << 24;
|
||||||
lsb |= (long) (random[0x7] & 0xff) << 16;
|
lsb |= (bytes[0xd] & 0xffL) << 16;
|
||||||
lsb |= (long) (random[0x8] & 0xff) << 8;
|
lsb |= (bytes[0xe] & 0xffL) << 8;
|
||||||
lsb |= (long) (random[0x9] & 0xff);
|
lsb |= (bytes[0xf] & 0xffL);
|
||||||
|
|
||||||
return new Ulid(msb, lsb);
|
return new Ulid(msb, lsb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a canonical string into a ULID.
|
||||||
|
*
|
||||||
|
* The input string must be 26 characters long and must contain only characters
|
||||||
|
* from Crockford's base 32 alphabet.
|
||||||
|
*
|
||||||
|
* The first character of the input string must be between 0 and 7.
|
||||||
|
*
|
||||||
|
* @param string a canonical string
|
||||||
|
* @return a ULID
|
||||||
|
*/
|
||||||
|
public static Ulid from(String string) {
|
||||||
|
|
||||||
|
final char[] chars = toCharArray(string);
|
||||||
|
|
||||||
|
long time = 0;
|
||||||
|
long random0 = 0;
|
||||||
|
long random1 = 0;
|
||||||
|
|
||||||
|
time |= ALPHABET_VALUES[chars[0x00]] << 45;
|
||||||
|
time |= ALPHABET_VALUES[chars[0x01]] << 40;
|
||||||
|
time |= ALPHABET_VALUES[chars[0x02]] << 35;
|
||||||
|
time |= ALPHABET_VALUES[chars[0x03]] << 30;
|
||||||
|
time |= ALPHABET_VALUES[chars[0x04]] << 25;
|
||||||
|
time |= ALPHABET_VALUES[chars[0x05]] << 20;
|
||||||
|
time |= ALPHABET_VALUES[chars[0x06]] << 15;
|
||||||
|
time |= ALPHABET_VALUES[chars[0x07]] << 10;
|
||||||
|
time |= ALPHABET_VALUES[chars[0x08]] << 5;
|
||||||
|
time |= ALPHABET_VALUES[chars[0x09]];
|
||||||
|
|
||||||
|
random0 |= ALPHABET_VALUES[chars[0x0a]] << 35;
|
||||||
|
random0 |= ALPHABET_VALUES[chars[0x0b]] << 30;
|
||||||
|
random0 |= ALPHABET_VALUES[chars[0x0c]] << 25;
|
||||||
|
random0 |= ALPHABET_VALUES[chars[0x0d]] << 20;
|
||||||
|
random0 |= ALPHABET_VALUES[chars[0x0e]] << 15;
|
||||||
|
random0 |= ALPHABET_VALUES[chars[0x0f]] << 10;
|
||||||
|
random0 |= ALPHABET_VALUES[chars[0x10]] << 5;
|
||||||
|
random0 |= ALPHABET_VALUES[chars[0x11]];
|
||||||
|
|
||||||
|
random1 |= ALPHABET_VALUES[chars[0x12]] << 35;
|
||||||
|
random1 |= ALPHABET_VALUES[chars[0x13]] << 30;
|
||||||
|
random1 |= ALPHABET_VALUES[chars[0x14]] << 25;
|
||||||
|
random1 |= ALPHABET_VALUES[chars[0x15]] << 20;
|
||||||
|
random1 |= ALPHABET_VALUES[chars[0x16]] << 15;
|
||||||
|
random1 |= ALPHABET_VALUES[chars[0x17]] << 10;
|
||||||
|
random1 |= ALPHABET_VALUES[chars[0x18]] << 5;
|
||||||
|
random1 |= ALPHABET_VALUES[chars[0x19]];
|
||||||
|
|
||||||
|
final long msb = (time << 16) | (random0 >>> 24);
|
||||||
|
final long lsb = (random0 << 40) | (random1 & 0xffffffffffL);
|
||||||
|
|
||||||
|
return new Ulid(msb, lsb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the ULID into a UUID.
|
||||||
|
*
|
||||||
|
* If you need a RFC-4122 UUID v4 do this: {@code Ulid.toRfc4122().toUuid()}.
|
||||||
|
*
|
||||||
|
* @return a UUID.
|
||||||
|
*/
|
||||||
public UUID toUuid() {
|
public UUID toUuid() {
|
||||||
return new UUID(this.msb, this.lsb);
|
return new UUID(this.msb, this.lsb);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test
|
/**
|
||||||
public UUID toUuid4() {
|
* Convert the ULID into a byte array.
|
||||||
final long msb4 = (this.msb & 0xffffffffffff0fffL) | 0x0000000000004000L; // apply version 4
|
*
|
||||||
final long lsb4 = (this.lsb & 0x3fffffffffffffffL) | 0x8000000000000000L; // apply variant RFC-4122
|
* @return an byte array.
|
||||||
return new UUID(msb4, lsb4);
|
*/
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: test
|
|
||||||
public byte[] toBytes() {
|
public byte[] toBytes() {
|
||||||
|
|
||||||
final byte[] bytes = new byte[ULID_BYTES_LENGTH];
|
final byte[] bytes = new byte[ULID_BYTES_LENGTH];
|
||||||
|
@ -287,64 +345,183 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test
|
/**
|
||||||
public byte[] toBytes4() {
|
* Converts the ULID into a canonical string in upper case.
|
||||||
return Ulid.of(this.toUuid4()).toBytes();
|
*
|
||||||
}
|
* The output string is 26 characters long and contains only characters from
|
||||||
|
* Crockford's base 32 alphabet.
|
||||||
@Override
|
*
|
||||||
public String toString() {
|
* See: https://www.crockford.com/base32.html
|
||||||
return this.toUpperCase();
|
*
|
||||||
}
|
* @return a string
|
||||||
|
*/
|
||||||
// TODO: test
|
|
||||||
public String toUpperCase() {
|
public String toUpperCase() {
|
||||||
return toString(ALPHABET_UPPERCASE);
|
return toString(ALPHABET_UPPERCASE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test
|
/**
|
||||||
public String toUpperCase4() {
|
* Converts the ULID into a canonical string in lower case.
|
||||||
return Ulid.of(this.toUuid4()).toUpperCase();
|
*
|
||||||
}
|
* The output string is 26 characters long and contains only characters from
|
||||||
|
* Crockford's base 32 alphabet.
|
||||||
// TODO: test
|
*
|
||||||
|
* It is at least twice as fast as {@code Ulid.toString().toLowerCase()}.
|
||||||
|
*
|
||||||
|
* See: https://www.crockford.com/base32.html
|
||||||
|
*
|
||||||
|
* @return a string
|
||||||
|
*/
|
||||||
public String toLowerCase() {
|
public String toLowerCase() {
|
||||||
return toString(ALPHABET_LOWERCASE);
|
return toString(ALPHABET_LOWERCASE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test
|
/**
|
||||||
public String toLowerCase4() {
|
* Converts the ULID into into another ULID that is compatible with UUID v4.
|
||||||
return Ulid.of(this.toUuid4()).toLowerCase();
|
*
|
||||||
}
|
* The bytes of the returned ULID are compliant with the RFC-4122 version 4.
|
||||||
|
*
|
||||||
public long getTime() {
|
* If you need a RFC-4122 UUID v4 do this: {@code Ulid.toRfc4122().toUuid()}.
|
||||||
return this.msb >>> 16;
|
*
|
||||||
|
* Read: https://tools.ietf.org/html/rfc4122
|
||||||
|
*
|
||||||
|
* ### RFC-4122 - 4.4. Algorithms for Creating a UUID from Truly Random or
|
||||||
|
* Pseudo-Random Numbers
|
||||||
|
*
|
||||||
|
* The version 4 UUID is meant for generating UUIDs from truly-random or
|
||||||
|
* pseudo-random numbers.
|
||||||
|
*
|
||||||
|
* The algorithm is as follows:
|
||||||
|
*
|
||||||
|
* - Set the two most significant bits (bits 6 and 7) of the
|
||||||
|
* clock_seq_hi_and_reserved to zero and one, respectively.
|
||||||
|
*
|
||||||
|
* - Set the four most significant bits (bits 12 through 15) of the
|
||||||
|
* time_hi_and_version field to the 4-bit version number from Section 4.1.3.
|
||||||
|
*
|
||||||
|
* - Set all the other bits to randomly (or pseudo-randomly) chosen values.
|
||||||
|
*
|
||||||
|
* @return a ULID
|
||||||
|
*/
|
||||||
|
public Ulid toRfc4122() {
|
||||||
|
|
||||||
|
// set the 4 most significant bits of the 7th byte to 0, 1, 0 and 0
|
||||||
|
final long msb4 = (this.msb & 0xffffffffffff0fffL) | 0x0000000000004000L; // RFC-4122 version 4
|
||||||
|
// set the 2 most significant bits of the 9th byte to 1 and 0
|
||||||
|
final long lsb4 = (this.lsb & 0x3fffffffffffffffL) | 0x8000000000000000L; // RFC-4122 variant 2
|
||||||
|
|
||||||
|
return new Ulid(msb4, lsb4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the instant of creation.
|
||||||
|
*
|
||||||
|
* The instant of creation is extracted from the time component.
|
||||||
|
*
|
||||||
|
* @return {@link Instant}
|
||||||
|
*/
|
||||||
public Instant getInstant() {
|
public Instant getInstant() {
|
||||||
return Instant.ofEpochMilli(this.getTime());
|
return Instant.ofEpochMilli(this.getTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the time component as a number.
|
||||||
|
*
|
||||||
|
* The time component is a number between 0 and 2^48-1. It is equivalent to the
|
||||||
|
* count of milliseconds since 1970-01-01 (Unix epoch).
|
||||||
|
*
|
||||||
|
* @return a number of milliseconds.
|
||||||
|
*/
|
||||||
|
public long getTime() {
|
||||||
|
return this.msb >>> 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the random component as a byte array.
|
||||||
|
*
|
||||||
|
* The random component is an array of 10 bytes (80 bits).
|
||||||
|
*
|
||||||
|
* @return a byte array
|
||||||
|
*/
|
||||||
|
public byte[] getRandom() {
|
||||||
|
final byte[] bytes = new byte[RANDOM_BYTES_LENGTH];
|
||||||
|
System.arraycopy(this.toBytes(), TIME_BYTES_LENGTH, bytes, 0, RANDOM_BYTES_LENGTH);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the most significant bits as a number.
|
||||||
|
*
|
||||||
|
* @return a number.
|
||||||
|
*/
|
||||||
public long getMostSignificantBits() {
|
public long getMostSignificantBits() {
|
||||||
return this.msb;
|
return this.msb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the least significant bits as a number.
|
||||||
|
*
|
||||||
|
* @return a number.
|
||||||
|
*/
|
||||||
public long getLeastSignificantBits() {
|
public long getLeastSignificantBits() {
|
||||||
return this.lsb;
|
return this.lsb;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test
|
/**
|
||||||
|
* Returns a new ULID by incrementing the random component of the current ULID.
|
||||||
|
*
|
||||||
|
* Since the random component contains 80 bits:
|
||||||
|
*
|
||||||
|
* (1) This method can generate up to 1208925819614629174706176 (2^80) ULIDs per
|
||||||
|
* millisecond;
|
||||||
|
*
|
||||||
|
* (2) This method can generate monotonic increasing ULIDs 99.999999999999992%
|
||||||
|
* ((2^80 - 10^9) / (2^80)) of the time, considering an unrealistic rate of
|
||||||
|
* 1,000,000,000 ULIDs per millisecond.
|
||||||
|
*
|
||||||
|
* Due to (1) and (2), it does not throw the error message recommended by the
|
||||||
|
* specification. When an overflow occurs in the last 80 bits, the random
|
||||||
|
* component simply wraps around.
|
||||||
|
*
|
||||||
|
* @return a ULID
|
||||||
|
*/
|
||||||
public Ulid increment() {
|
public Ulid increment() {
|
||||||
|
|
||||||
long msb1 = this.msb;
|
long newMsb = this.msb;
|
||||||
long lsb1 = this.lsb + 1; // Increment the LSB
|
long newLsb = this.lsb + 1; // increment the LEAST significant bits
|
||||||
|
|
||||||
if (lsb1 == INCREMENT_OVERFLOW) {
|
if (newLsb == INCREMENT_OVERFLOW) {
|
||||||
// Increment the random bits of the MSB
|
// carrying the extra bit by incrementing the MOST significant bits
|
||||||
msb1 = (msb1 & 0xffffffffffff0000L) | ((msb1 + 1) & 0x000000000000ffffL);
|
newMsb = (newMsb & 0xffffffffffff0000L) | ((newMsb + 1) & 0x000000000000ffffL);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Ulid(msb1, lsb1);
|
return new Ulid(newMsb, newLsb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the input string is valid.
|
||||||
|
*
|
||||||
|
* The input string must be 26 characters long and must contain only characters
|
||||||
|
* from Crockford's base 32 alphabet.
|
||||||
|
*
|
||||||
|
* The first character of the input string must be between 0 and 7.
|
||||||
|
*
|
||||||
|
* @param string a string
|
||||||
|
* @return true if valid
|
||||||
|
*/
|
||||||
|
public static boolean isValid(String string) {
|
||||||
|
return string != null && isValidCharArray(string.toCharArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the ULID into a canonical string in upper case.
|
||||||
|
*
|
||||||
|
* It is the same as {@code Ulid.toUpperCase()}.
|
||||||
|
*
|
||||||
|
* @return a ULID string
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -385,14 +562,13 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: optimize
|
|
||||||
protected String toString(char[] alphabet) {
|
protected String toString(char[] alphabet) {
|
||||||
|
|
||||||
final char[] chars = new char[ULID_LENGTH];
|
final char[] chars = new char[ULID_LENGTH];
|
||||||
|
|
||||||
long time = this.msb >>> 16;
|
long time = this.msb >>> 16;
|
||||||
long random1 = ((this.msb & 0xffffL) << 24) | (this.lsb >>> 40);
|
long random0 = ((this.msb & 0xffffL) << 24) | (this.lsb >>> 40);
|
||||||
long random2 = (this.lsb & 0xffffffffffL);
|
long random1 = (this.lsb & 0xffffffffffL);
|
||||||
|
|
||||||
chars[0x00] = alphabet[(int) (time >>> 45 & 0b11111)];
|
chars[0x00] = alphabet[(int) (time >>> 45 & 0b11111)];
|
||||||
chars[0x01] = alphabet[(int) (time >>> 40 & 0b11111)];
|
chars[0x01] = alphabet[(int) (time >>> 40 & 0b11111)];
|
||||||
|
@ -405,29 +581,33 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
||||||
chars[0x08] = alphabet[(int) (time >>> 5 & 0b11111)];
|
chars[0x08] = alphabet[(int) (time >>> 5 & 0b11111)];
|
||||||
chars[0x09] = alphabet[(int) (time & 0b11111)];
|
chars[0x09] = alphabet[(int) (time & 0b11111)];
|
||||||
|
|
||||||
chars[0x0a] = alphabet[(int) (random1 >>> 35 & 0b11111)];
|
chars[0x0a] = alphabet[(int) (random0 >>> 35 & 0b11111)];
|
||||||
chars[0x0b] = alphabet[(int) (random1 >>> 30 & 0b11111)];
|
chars[0x0b] = alphabet[(int) (random0 >>> 30 & 0b11111)];
|
||||||
chars[0x0c] = alphabet[(int) (random1 >>> 25 & 0b11111)];
|
chars[0x0c] = alphabet[(int) (random0 >>> 25 & 0b11111)];
|
||||||
chars[0x0d] = alphabet[(int) (random1 >>> 20 & 0b11111)];
|
chars[0x0d] = alphabet[(int) (random0 >>> 20 & 0b11111)];
|
||||||
chars[0x0e] = alphabet[(int) (random1 >>> 15 & 0b11111)];
|
chars[0x0e] = alphabet[(int) (random0 >>> 15 & 0b11111)];
|
||||||
chars[0x0f] = alphabet[(int) (random1 >>> 10 & 0b11111)];
|
chars[0x0f] = alphabet[(int) (random0 >>> 10 & 0b11111)];
|
||||||
chars[0x10] = alphabet[(int) (random1 >>> 5 & 0b11111)];
|
chars[0x10] = alphabet[(int) (random0 >>> 5 & 0b11111)];
|
||||||
chars[0x11] = alphabet[(int) (random1 & 0b11111)];
|
chars[0x11] = alphabet[(int) (random0 & 0b11111)];
|
||||||
|
|
||||||
chars[0x12] = alphabet[(int) (random2 >>> 35 & 0b11111)];
|
chars[0x12] = alphabet[(int) (random1 >>> 35 & 0b11111)];
|
||||||
chars[0x13] = alphabet[(int) (random2 >>> 30 & 0b11111)];
|
chars[0x13] = alphabet[(int) (random1 >>> 30 & 0b11111)];
|
||||||
chars[0x14] = alphabet[(int) (random2 >>> 25 & 0b11111)];
|
chars[0x14] = alphabet[(int) (random1 >>> 25 & 0b11111)];
|
||||||
chars[0x15] = alphabet[(int) (random2 >>> 20 & 0b11111)];
|
chars[0x15] = alphabet[(int) (random1 >>> 20 & 0b11111)];
|
||||||
chars[0x16] = alphabet[(int) (random2 >>> 15 & 0b11111)];
|
chars[0x16] = alphabet[(int) (random1 >>> 15 & 0b11111)];
|
||||||
chars[0x17] = alphabet[(int) (random2 >>> 10 & 0b11111)];
|
chars[0x17] = alphabet[(int) (random1 >>> 10 & 0b11111)];
|
||||||
chars[0x18] = alphabet[(int) (random2 >>> 5 & 0b11111)];
|
chars[0x18] = alphabet[(int) (random1 >>> 5 & 0b11111)];
|
||||||
chars[0x19] = alphabet[(int) (random2 & 0b11111)];
|
chars[0x19] = alphabet[(int) (random1 & 0b11111)];
|
||||||
|
|
||||||
return new String(chars);
|
return new String(chars);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isValidString(String string) {
|
protected static char[] toCharArray(String string) {
|
||||||
return isValidArray(string == null ? null : string.toCharArray());
|
char[] chars = string == null ? null : string.toCharArray();
|
||||||
|
if (!isValidCharArray(chars)) {
|
||||||
|
throw new IllegalArgumentException(String.format("Invalid ULID: \"%s\"", string));
|
||||||
|
}
|
||||||
|
return chars;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -439,15 +619,22 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
||||||
* @param chars a char array
|
* @param chars a char array
|
||||||
* @return boolean true if valid
|
* @return boolean true if valid
|
||||||
*/
|
*/
|
||||||
protected static boolean isValidArray(final char[] chars) {
|
protected static boolean isValidCharArray(final char[] chars) {
|
||||||
|
|
||||||
if (chars == null || chars.length != ULID_LENGTH) {
|
if (chars == null || chars.length != ULID_LENGTH) {
|
||||||
return false; // null or wrong size!
|
return false; // null or wrong size!
|
||||||
}
|
}
|
||||||
|
|
||||||
// the two extra bits added by base-32 encoding must be zero
|
// The time component has 48 bits.
|
||||||
|
// The base32 encoded time component has 50 bits.
|
||||||
|
// The time component cannot be greater than than 2^48-1.
|
||||||
|
// So the 2 first bits of the base32 decoded time component must be ZERO.
|
||||||
|
// As a consequence, the 1st char of the input string must be between 0 and 7.
|
||||||
if ((ALPHABET_VALUES[chars[0]] & 0b11000) != 0) {
|
if ((ALPHABET_VALUES[chars[0]] & 0b11000) != 0) {
|
||||||
return false; // overflow!
|
// ULID specification:
|
||||||
|
// "Any attempt to decode or encode a ULID larger than this (time > 2^48-1)
|
||||||
|
// should be rejected by all implementations, to prevent overflow bugs."
|
||||||
|
return false; // time overflow!
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < chars.length; i++) {
|
for (int i = 0; i < chars.length; i++) {
|
||||||
|
@ -458,12 +645,4 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
||||||
|
|
||||||
return true; // It seems to be OK.
|
return true; // It seems to be OK.
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static char[] toCharArray(String string) {
|
|
||||||
char[] chars = string == null ? new char[0] : string.toCharArray();
|
|
||||||
if (!isValidArray(chars)) {
|
|
||||||
throw new IllegalArgumentException(String.format("Invalid ULID: \"%s\"", string));
|
|
||||||
}
|
|
||||||
return chars;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,43 +24,97 @@
|
||||||
|
|
||||||
package com.github.f4b6a3.ulid;
|
package com.github.f4b6a3.ulid;
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.creator.MonotonicUlidSpecCreator;
|
import com.github.f4b6a3.ulid.creator.MonotonicUlidFactory;
|
||||||
import com.github.f4b6a3.ulid.creator.UlidSpecCreator;
|
import com.github.f4b6a3.ulid.creator.UlidFactory;
|
||||||
|
import com.github.f4b6a3.ulid.creator.DefaultUlidFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Facade to the ULID factories.
|
||||||
|
*
|
||||||
|
* The ULID has two components:
|
||||||
|
*
|
||||||
|
* - Time component: a part of 48 bits that represent the amount of milliseconds
|
||||||
|
* since Unix Epoch, 1970-01-01.
|
||||||
|
*
|
||||||
|
* - Random component: a byte array of 80 bits that has a random value generated
|
||||||
|
* a secure random generator.
|
||||||
|
*
|
||||||
|
* The maximum ULIDs that can be generated per millisecond is 2^80.
|
||||||
|
*/
|
||||||
public final class UlidCreator {
|
public final class UlidCreator {
|
||||||
|
|
||||||
private UlidCreator() {
|
private UlidCreator() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a ULID.
|
||||||
|
*
|
||||||
|
* The random component is always reset to a new random value.
|
||||||
|
*
|
||||||
|
* @return a ULID
|
||||||
|
*/
|
||||||
public static Ulid getUlid() {
|
public static Ulid getUlid() {
|
||||||
return DefaultCreatorHolder.INSTANCE.create();
|
return DefaultFactoryHolder.INSTANCE.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a ULID with a specific time.
|
||||||
|
*
|
||||||
|
* @param time a specific time
|
||||||
|
* @return a ULID
|
||||||
|
*/
|
||||||
public static Ulid getUlid(final long time) {
|
public static Ulid getUlid(final long time) {
|
||||||
return DefaultCreatorHolder.INSTANCE.create(time);
|
return DefaultFactoryHolder.INSTANCE.create(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Monotonic ULID.
|
||||||
|
*
|
||||||
|
* The random component is reset to a new value every time the millisecond
|
||||||
|
* changes.
|
||||||
|
*
|
||||||
|
* If more than one ULID is generated within the same millisecond, the random
|
||||||
|
* component is incremented by one.
|
||||||
|
*
|
||||||
|
* @return a ULID
|
||||||
|
*/
|
||||||
public static Ulid getMonotonicUlid() {
|
public static Ulid getMonotonicUlid() {
|
||||||
return MonotonicCreatorHolder.INSTANCE.create();
|
return MonotonicFactoryHolder.INSTANCE.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Monotonic ULID with a specific time.
|
||||||
|
*
|
||||||
|
* @param time a specific time
|
||||||
|
* @return a ULID
|
||||||
|
*/
|
||||||
public static Ulid getMonotonicUlid(final long time) {
|
public static Ulid getMonotonicUlid(final long time) {
|
||||||
return MonotonicCreatorHolder.INSTANCE.create(time);
|
return MonotonicFactoryHolder.INSTANCE.create(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UlidSpecCreator getUlidSpecCreator() {
|
/**
|
||||||
return new UlidSpecCreator();
|
* Returns an instance of the Default ULID factory.
|
||||||
|
*
|
||||||
|
* @return a ULID factory
|
||||||
|
*/
|
||||||
|
public static UlidFactory getDefaultFactory() {
|
||||||
|
return new DefaultUlidFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UlidSpecCreator getMonotonicUlidSpecCreator() {
|
/**
|
||||||
return new MonotonicUlidSpecCreator();
|
* Returns an instance of the Monotonic ULID factory.
|
||||||
|
*
|
||||||
|
* @return a ULID factory
|
||||||
|
*/
|
||||||
|
public static UlidFactory getMonotonicFactory() {
|
||||||
|
return new MonotonicUlidFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DefaultCreatorHolder {
|
private static class DefaultFactoryHolder {
|
||||||
static final UlidSpecCreator INSTANCE = getUlidSpecCreator();
|
static final UlidFactory INSTANCE = getDefaultFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MonotonicCreatorHolder {
|
private static class MonotonicFactoryHolder {
|
||||||
static final UlidSpecCreator INSTANCE = getMonotonicUlidSpecCreator();
|
static final UlidFactory INSTANCE = getMonotonicFactory();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,23 +26,25 @@ package com.github.f4b6a3.ulid.creator;
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.Ulid;
|
import com.github.f4b6a3.ulid.Ulid;
|
||||||
|
|
||||||
public final class MonotonicUlidSpecCreator extends UlidSpecCreator {
|
/**
|
||||||
|
* Factory that generates default ULIDs.
|
||||||
private long lastTime;
|
*
|
||||||
private Ulid lastUlid;
|
* The random component is always reset to a new random value.
|
||||||
|
*
|
||||||
|
* The maximum ULIDs that can be generated per millisecond is 2^80.
|
||||||
|
*/
|
||||||
|
public class DefaultUlidFactory extends UlidFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a ULID.
|
||||||
|
*
|
||||||
|
* @param time a specific time
|
||||||
|
* @return a ULID
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public synchronized Ulid create(final long time) {
|
public Ulid create(final long time) {
|
||||||
|
final byte[] random = new byte[Ulid.RANDOM_BYTES_LENGTH];
|
||||||
if (time == this.lastTime) {
|
this.randomGenerator.nextBytes(random);
|
||||||
this.lastUlid = lastUlid.increment();
|
return new Ulid(time, random);
|
||||||
} else {
|
|
||||||
final byte[] random = new byte[10];
|
|
||||||
this.randomStrategy.nextBytes(random);
|
|
||||||
this.lastUlid = Ulid.of(time, random);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lastTime = time;
|
|
||||||
return this.lastUlid;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* 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.creator;
|
||||||
|
|
||||||
|
import com.github.f4b6a3.ulid.Ulid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory that generates Monotonic ULIDs.
|
||||||
|
*
|
||||||
|
* The random component is reset to a new value every time the millisecond changes.
|
||||||
|
*
|
||||||
|
* If more than one ULID is generated within the same millisecond, the random
|
||||||
|
* component is incremented by one.
|
||||||
|
*
|
||||||
|
* The maximum ULIDs that can be generated per millisecond is 2^80.
|
||||||
|
*/
|
||||||
|
public final class MonotonicUlidFactory extends UlidFactory {
|
||||||
|
|
||||||
|
private long lastTime = -1;
|
||||||
|
private Ulid lastUlid = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a ULID.
|
||||||
|
*
|
||||||
|
* @param time a specific time
|
||||||
|
* @return a ULID
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public synchronized Ulid create(final long time) {
|
||||||
|
|
||||||
|
if (time == this.lastTime) {
|
||||||
|
this.lastUlid = lastUlid.increment();
|
||||||
|
} else {
|
||||||
|
final byte[] random = new byte[Ulid.RANDOM_BYTES_LENGTH];
|
||||||
|
this.randomGenerator.nextBytes(random);
|
||||||
|
this.lastUlid = new Ulid(time, random);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastTime = time;
|
||||||
|
return new Ulid(this.lastUlid);
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,41 +27,55 @@ package com.github.f4b6a3.ulid.creator;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.Ulid;
|
import com.github.f4b6a3.ulid.Ulid;
|
||||||
import com.github.f4b6a3.ulid.strategy.DefaultRandomStrategy;
|
import com.github.f4b6a3.ulid.random.DefaultRandomGenerator;
|
||||||
import com.github.f4b6a3.ulid.strategy.RandomStrategy;
|
import com.github.f4b6a3.ulid.random.RandomGenerator;
|
||||||
|
|
||||||
public class UlidSpecCreator {
|
/**
|
||||||
|
* An abstract factory for generating ULIDs.
|
||||||
|
*
|
||||||
|
* The only method that must be implemented is {@link UlidFactory#create(long)}.
|
||||||
|
*/
|
||||||
|
public abstract class UlidFactory {
|
||||||
|
|
||||||
protected RandomStrategy randomStrategy;
|
protected RandomGenerator randomGenerator;
|
||||||
|
|
||||||
public UlidSpecCreator() {
|
public UlidFactory() {
|
||||||
this.randomStrategy = new DefaultRandomStrategy();
|
this.randomGenerator = new DefaultRandomGenerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a UUID.
|
||||||
|
*
|
||||||
|
* @return a ULID
|
||||||
|
*/
|
||||||
public Ulid create() {
|
public Ulid create() {
|
||||||
return create(System.currentTimeMillis());
|
return create(System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Ulid create(final long time) {
|
/**
|
||||||
final byte[] random = new byte[10];
|
* Returns a UUID with a specific time.
|
||||||
this.randomStrategy.nextBytes(random);
|
*
|
||||||
return Ulid.of(time, random);
|
* This method must be implemented by all subclasses.
|
||||||
}
|
*
|
||||||
|
* @param time a specific time
|
||||||
|
* @return a ULID
|
||||||
|
*/
|
||||||
|
public abstract Ulid create(final long time);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces the default random strategy with another.
|
* Replaces the default random generator with another.
|
||||||
*
|
*
|
||||||
* The default random strategy uses {@link java.security.SecureRandom}.
|
* The default random generator uses {@link java.security.SecureRandom}.
|
||||||
*
|
*
|
||||||
* See {@link Random}.
|
* See {@link Random}.
|
||||||
*
|
*
|
||||||
* @param random a random generator
|
* @param <T> the type parameter
|
||||||
* @param <T> the type parameter
|
* @param randomGenerator a random generator
|
||||||
* @return {@link AbstractRandomBasedUuidCreator}
|
* @return {@link UlidFactory}
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public synchronized <T extends UlidSpecCreator> T withRandomStrategy(RandomStrategy randomStrategy) {
|
public synchronized <T extends UlidFactory> T withRandomGenerator(RandomGenerator randomGenerator) {
|
||||||
this.randomStrategy = randomStrategy;
|
this.randomGenerator = randomGenerator;
|
||||||
return (T) this;
|
return (T) this;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -22,7 +22,7 @@
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.github.f4b6a3.ulid.strategy;
|
package com.github.f4b6a3.ulid.random;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
@ -30,7 +30,7 @@ import java.util.Random;
|
||||||
/**
|
/**
|
||||||
* It uses an instance of {@link java.security.SecureRandom}.
|
* It uses an instance of {@link java.security.SecureRandom}.
|
||||||
*/
|
*/
|
||||||
public final class DefaultRandomStrategy implements RandomStrategy {
|
public final class DefaultRandomGenerator implements RandomGenerator {
|
||||||
|
|
||||||
private static final Random SECURE_RANDOM = new SecureRandom();
|
private static final Random SECURE_RANDOM = new SecureRandom();
|
||||||
|
|
|
@ -22,9 +22,9 @@
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.github.f4b6a3.ulid.strategy;
|
package com.github.f4b6a3.ulid.random;
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface RandomStrategy {
|
public interface RandomGenerator {
|
||||||
void nextBytes(byte[] bytes);
|
void nextBytes(byte[] bytes);
|
||||||
}
|
}
|
|
@ -3,13 +3,13 @@ package com.github.f4b6a3.ulid;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.Suite;
|
import org.junit.runners.Suite;
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.creator.MonotonicUlidSpecCreatorTest;
|
import com.github.f4b6a3.ulid.creator.MonotonicUlidFactoryTest;
|
||||||
import com.github.f4b6a3.ulid.creator.UlidSpecCreatorTest;
|
import com.github.f4b6a3.ulid.creator.DefaultUlidFactoryTest;
|
||||||
|
|
||||||
@RunWith(Suite.class)
|
@RunWith(Suite.class)
|
||||||
@Suite.SuiteClasses({
|
@Suite.SuiteClasses({
|
||||||
MonotonicUlidSpecCreatorTest.class,
|
MonotonicUlidFactoryTest.class,
|
||||||
UlidSpecCreatorTest.class,
|
DefaultUlidFactoryTest.class,
|
||||||
UlidTest.class,
|
UlidTest.class,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,10 @@ package com.github.f4b6a3.ulid;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@ -13,98 +16,428 @@ import com.github.f4b6a3.ulid.Ulid;
|
||||||
|
|
||||||
public class UlidTest {
|
public class UlidTest {
|
||||||
|
|
||||||
private static final int DEFAULT_LOOP_MAX = 10_000;
|
private static final int DEFAULT_LOOP_MAX = 1_000;
|
||||||
|
|
||||||
|
protected static final long TIME_MASK = 0x0000ffffffffffffL;
|
||||||
|
|
||||||
protected static final char[] ALPHABET_CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".toCharArray();
|
protected static final char[] ALPHABET_CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".toCharArray();
|
||||||
protected static final char[] ALPHABET_JAVA = "0123456789abcdefghijklmnopqrstuv".toCharArray(); // Long.parseUnsignedLong()
|
protected static final char[] ALPHABET_JAVA = "0123456789abcdefghijklmnopqrstuv".toCharArray(); // Long.parseUnsignedLong()
|
||||||
|
|
||||||
@Test
|
private static final long VERSION_MASK = 0x000000000000f000L;
|
||||||
public void testOfAndToString() {
|
private static final long VARIANT_MASK = 0xc000000000000000L;
|
||||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
|
||||||
UUID uuid0 = UUID.randomUUID();
|
|
||||||
String string0 = toString(uuid0);
|
|
||||||
String string1 = Ulid.of(string0).toString();
|
|
||||||
assertEquals(string0, string1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOfAndToUuid() {
|
|
||||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
|
||||||
UUID uuid0 = UUID.randomUUID();
|
|
||||||
UUID uuid1 = Ulid.of(uuid0).toUuid();
|
|
||||||
assertEquals(uuid0.toString(), uuid1.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testConstructorLongs() {
|
public void testConstructorLongs() {
|
||||||
|
Random random = new Random();
|
||||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||||
Random random = new Random();
|
|
||||||
final long msb = random.nextLong();
|
final long msb = random.nextLong();
|
||||||
final long lsb = random.nextLong();
|
final long lsb = random.nextLong();
|
||||||
Ulid ulid0 = new Ulid(msb, lsb); // <-- under test
|
Ulid ulid0 = new Ulid(msb, lsb); // <-- test Ulid(long, long)
|
||||||
|
assertEquals(msb, ulid0.getMostSignificantBits());
|
||||||
assertEquals(msb, ulid0.toUuid().getMostSignificantBits());
|
assertEquals(lsb, ulid0.getLeastSignificantBits());
|
||||||
assertEquals(lsb, ulid0.toUuid().getLeastSignificantBits());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testConstructorString() {
|
public void testConstructorTimeAndRandom() {
|
||||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
Random random = new Random();
|
||||||
Random random = new Random();
|
|
||||||
final long random1 = random.nextLong();
|
|
||||||
final long random2 = random.nextLong();
|
|
||||||
Ulid ulid0 = new Ulid(random1, random2);
|
|
||||||
|
|
||||||
String string1 = toString(ulid0);
|
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||||
Ulid struct1 = Ulid.of(string1); // <-- under test
|
final long msb = random.nextLong();
|
||||||
assertEquals(ulid0, struct1);
|
final long lsb = random.nextLong();
|
||||||
|
|
||||||
|
// get the time
|
||||||
|
long time = msb >>> 16;
|
||||||
|
|
||||||
|
// get the random bytes
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(Ulid.RANDOM_BYTES_LENGTH);
|
||||||
|
buffer.put((byte) ((msb >>> 8) & 0xff));
|
||||||
|
buffer.put((byte) (msb & 0xff));
|
||||||
|
buffer.putLong(lsb);
|
||||||
|
byte[] bytes = buffer.array();
|
||||||
|
|
||||||
|
Ulid ulid0 = new Ulid(time, bytes); // <-- test Ulid(long, byte[])
|
||||||
|
assertEquals(msb, ulid0.getMostSignificantBits());
|
||||||
|
assertEquals(lsb, ulid0.getLeastSignificantBits());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
long time = 0x0000ffffffffffffL + 1; // greater than 2^48-1
|
||||||
|
byte[] bytes = new byte[Ulid.RANDOM_BYTES_LENGTH];
|
||||||
|
new Ulid(time, bytes);
|
||||||
|
fail("Should throw an exception");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
long time = 0x1000000000000000L; // negative number
|
||||||
|
byte[] bytes = new byte[Ulid.RANDOM_BYTES_LENGTH];
|
||||||
|
new Ulid(time, bytes);
|
||||||
|
fail("Should throw an exception");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
long time = 0x0000000000000000L;
|
||||||
|
byte[] bytes = null; // null random component
|
||||||
|
new Ulid(time, bytes);
|
||||||
|
fail("Should throw an exception");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
long time = 0x0000000000000000L;
|
||||||
|
byte[] bytes = new byte[Ulid.RANDOM_BYTES_LENGTH + 1]; // random component with invalid size
|
||||||
|
new Ulid(time, bytes);
|
||||||
|
fail("Should throw an exception");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// success
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testConstructorUuid() {
|
public void testFromStrings() {
|
||||||
|
Random random = new Random();
|
||||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||||
Random random = new Random();
|
|
||||||
final long msb = random.nextLong();
|
final long msb = random.nextLong();
|
||||||
final long lsb = random.nextLong();
|
final long lsb = random.nextLong();
|
||||||
final UUID uuid0 = new UUID(msb, lsb);
|
Ulid ulid0 = new Ulid(msb, lsb);
|
||||||
Ulid struct0 = Ulid.of(uuid0); // <-- under test
|
String string0 = toString(ulid0);
|
||||||
|
Ulid ulid1 = Ulid.from(string0); // <- test Ulid.from(String)
|
||||||
UUID uuid1 = toUuid(struct0);
|
assertEquals(ulid0, ulid1);
|
||||||
assertEquals(uuid0, uuid1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testToString() {
|
public void testToString() {
|
||||||
|
|
||||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||||
Random random = new Random();
|
UUID uuid0 = UUID.randomUUID();
|
||||||
final long random1 = random.nextLong();
|
String string0 = toString(uuid0);
|
||||||
final long random2 = random.nextLong();
|
String string1 = Ulid.from(uuid0).toString(); // <- test Ulid.toString()
|
||||||
Ulid ulid0 = new Ulid(random1, random2);
|
assertEquals(string0, string1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToUpperCase() {
|
||||||
|
Random random = new Random();
|
||||||
|
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||||
|
|
||||||
|
final long msb = random.nextLong();
|
||||||
|
final long lsb = random.nextLong();
|
||||||
|
Ulid ulid0 = new Ulid(msb, lsb);
|
||||||
|
|
||||||
String string1 = toString(ulid0);
|
String string1 = toString(ulid0);
|
||||||
String string2 = ulid0.toString(); // <-- under test
|
String string2 = ulid0.toUpperCase(); // <- test Ulid.toUpperCase()
|
||||||
assertEquals(string1, string2);
|
assertEquals(string1, string2);
|
||||||
|
|
||||||
|
// RFC-4122 UUID v4
|
||||||
|
UUID uuid0 = new UUID(msb, lsb);
|
||||||
|
String string3 = ulid0.toRfc4122().toUpperCase(); // <- test Ulid.toRfc4122().toUpperCase()
|
||||||
|
Ulid ulid3 = fromString(string3);
|
||||||
|
UUID uuid3 = new UUID(ulid3.getMostSignificantBits(), ulid3.getLeastSignificantBits());
|
||||||
|
assertEquals(4, uuid3.version()); // check version
|
||||||
|
assertEquals(2, uuid3.variant()); // check variant
|
||||||
|
assertEquals(uuid0.getMostSignificantBits() & ~VERSION_MASK,
|
||||||
|
uuid3.getMostSignificantBits() & ~VERSION_MASK); // check the rest of MSB
|
||||||
|
assertEquals(uuid0.getLeastSignificantBits() & ~VARIANT_MASK,
|
||||||
|
uuid3.getLeastSignificantBits() & ~VARIANT_MASK); // check the rest of LSB
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToLowerCase() {
|
||||||
|
Random random = new Random();
|
||||||
|
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||||
|
|
||||||
|
final long msb = random.nextLong();
|
||||||
|
final long lsb = random.nextLong();
|
||||||
|
Ulid ulid0 = new Ulid(msb, lsb);
|
||||||
|
|
||||||
|
String string1 = toString(ulid0).toLowerCase();
|
||||||
|
String string2 = ulid0.toLowerCase(); // <- test Ulid.toLowerCase()
|
||||||
|
assertEquals(string1, string2);
|
||||||
|
|
||||||
|
// RFC-4122 UUID v4
|
||||||
|
UUID uuid0 = new UUID(msb, lsb);
|
||||||
|
String string3 = ulid0.toRfc4122().toLowerCase(); // <- test Ulid.toRfc4122().toLowerCase()
|
||||||
|
Ulid ulid3 = fromString(string3);
|
||||||
|
UUID uuid3 = new UUID(ulid3.getMostSignificantBits(), ulid3.getLeastSignificantBits());
|
||||||
|
assertEquals(4, uuid3.version()); // check version
|
||||||
|
assertEquals(2, uuid3.variant()); // check variant
|
||||||
|
assertEquals(uuid0.getMostSignificantBits() & ~VERSION_MASK,
|
||||||
|
uuid3.getMostSignificantBits() & ~VERSION_MASK); // check the rest of MSB
|
||||||
|
assertEquals(uuid0.getLeastSignificantBits() & ~VARIANT_MASK,
|
||||||
|
uuid3.getLeastSignificantBits() & ~VARIANT_MASK); // check the rest of LSB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFromUUID() {
|
||||||
|
Random random = new Random();
|
||||||
|
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||||
|
final long msb = random.nextLong();
|
||||||
|
final long lsb = random.nextLong();
|
||||||
|
UUID uuid0 = new UUID(msb, lsb);
|
||||||
|
Ulid ulid0 = Ulid.from(uuid0); // <- test Ulid.from(UUID)
|
||||||
|
assertEquals(uuid0.getMostSignificantBits(), ulid0.getMostSignificantBits());
|
||||||
|
assertEquals(uuid0.getLeastSignificantBits(), ulid0.getLeastSignificantBits());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testToUuid() {
|
public void testToUuid() {
|
||||||
|
Random random = new Random();
|
||||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||||
Random random = new Random();
|
|
||||||
final long random1 = random.nextLong();
|
final long random1 = random.nextLong();
|
||||||
final long random2 = random.nextLong();
|
final long random2 = random.nextLong();
|
||||||
Ulid ulid0 = new Ulid(random1, random2);
|
Ulid ulid0 = new Ulid(random1, random2);
|
||||||
|
|
||||||
UUID uuid1 = toUuid(ulid0);
|
UUID uuid1 = toUuid(ulid0);
|
||||||
UUID uuid2 = ulid0.toUuid(); // <-- under test
|
UUID uuid2 = ulid0.toUuid(); // <-- test Ulid.toUuid()
|
||||||
assertEquals(uuid1, uuid2);
|
assertEquals(uuid1, uuid2);
|
||||||
|
|
||||||
|
// RFC-4122 UUID v4
|
||||||
|
UUID uuid3 = ulid0.toRfc4122().toUuid(); // <-- test Ulid.toRfc4122().toUuid()
|
||||||
|
assertEquals(4, uuid3.version()); // check version
|
||||||
|
assertEquals(2, uuid3.variant()); // check variant
|
||||||
|
assertEquals(uuid1.getMostSignificantBits() & ~VERSION_MASK,
|
||||||
|
uuid3.getMostSignificantBits() & ~VERSION_MASK); // check the rest of MSB
|
||||||
|
assertEquals(uuid1.getLeastSignificantBits() & ~VARIANT_MASK,
|
||||||
|
uuid3.getLeastSignificantBits() & ~VARIANT_MASK); // check the rest of LSB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFromBytes() {
|
||||||
|
Random random = new Random();
|
||||||
|
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||||
|
|
||||||
|
byte[] bytes0 = new byte[Ulid.ULID_BYTES_LENGTH];
|
||||||
|
random.nextBytes(bytes0);
|
||||||
|
|
||||||
|
Ulid ulid0 = Ulid.from(bytes0); // <- test Ulid.from(UUID)
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(Ulid.ULID_BYTES_LENGTH);
|
||||||
|
buffer.putLong(ulid0.getMostSignificantBits());
|
||||||
|
buffer.putLong(ulid0.getLeastSignificantBits());
|
||||||
|
byte[] bytes1 = buffer.array();
|
||||||
|
|
||||||
|
for (int j = 0; j < bytes0.length; j++) {
|
||||||
|
assertEquals(bytes0[j], bytes1[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] bytes = null;
|
||||||
|
Ulid.from(bytes);
|
||||||
|
fail("Should throw an exception");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] bytes = new byte[Ulid.ULID_BYTES_LENGTH + 1];
|
||||||
|
Ulid.from(bytes);
|
||||||
|
fail("Should throw an exception");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToBytes() {
|
||||||
|
Random random = new Random();
|
||||||
|
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||||
|
|
||||||
|
byte[] bytes1 = new byte[16];
|
||||||
|
random.nextBytes(bytes1);
|
||||||
|
Ulid ulid0 = Ulid.from(bytes1);
|
||||||
|
|
||||||
|
byte[] bytes2 = ulid0.toBytes(); // <-- test Ulid.toBytes()
|
||||||
|
for (int j = 0; j < bytes1.length; j++) {
|
||||||
|
assertEquals(bytes1[j], bytes2[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC-4122 UUID v4
|
||||||
|
byte[] bytes3 = ulid0.toRfc4122().toBytes(); // <-- test Ulid.toBytes4()
|
||||||
|
assertEquals(0x40, bytes3[6] & 0b11110000); // check version
|
||||||
|
assertEquals(bytes1[6] & 0b00001111, bytes3[6] & 0b00001111); // check the other bits of 7th byte
|
||||||
|
assertEquals(0x80, bytes3[8] & 0b11000000); // check variant
|
||||||
|
assertEquals(bytes1[8] & 0b00111111, bytes3[8] & 0b00111111); // check the other bits of 9th byte
|
||||||
|
for (int j = 0; j < bytes1.length; j++) {
|
||||||
|
if (j == 6 || j == 8)
|
||||||
|
continue;
|
||||||
|
assertEquals(bytes1[j], bytes3[j]); // check the other bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetTimeAndGetRandom() {
|
||||||
|
|
||||||
|
long time = 0;
|
||||||
|
byte[] bytes = new byte[10];
|
||||||
|
Random random = new Random();
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
|
||||||
|
time = random.nextLong() & TIME_MASK;
|
||||||
|
random.nextBytes(bytes);
|
||||||
|
Ulid ulid = new Ulid(time, bytes);
|
||||||
|
|
||||||
|
assertEquals(time, ulid.getTime()); // test Ulid.getTime()
|
||||||
|
assertEquals(Instant.ofEpochMilli(time), ulid.getInstant()); // test Ulid.getInstant()
|
||||||
|
for (int j = 0; j < bytes.length; j++) {
|
||||||
|
assertEquals(bytes[j], ulid.getRandom()[j]); // test Ulid.getRandom()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIncrement() {
|
||||||
|
|
||||||
|
long msb;
|
||||||
|
long lsb;
|
||||||
|
Ulid ulid;
|
||||||
|
|
||||||
|
final int loopMax = 100;
|
||||||
|
|
||||||
|
msb = 0x0123456789abcdefL;
|
||||||
|
lsb = 0x0123456789abcdefL;
|
||||||
|
ulid = new Ulid(msb, lsb);
|
||||||
|
for (int i = 0; i < loopMax; i++) {
|
||||||
|
ulid = ulid.increment();
|
||||||
|
}
|
||||||
|
assertEquals(msb, ulid.getMostSignificantBits());
|
||||||
|
assertEquals(msb + loopMax, ulid.getLeastSignificantBits());
|
||||||
|
|
||||||
|
msb = 0x0123456789abcdefL;
|
||||||
|
lsb = 0xffffffffffffffffL - (loopMax / 2);
|
||||||
|
ulid = new Ulid(msb, lsb);
|
||||||
|
for (int i = 0; i < loopMax; i++) {
|
||||||
|
ulid = ulid.increment();
|
||||||
|
}
|
||||||
|
assertEquals(msb + 1, ulid.getMostSignificantBits());
|
||||||
|
assertEquals((loopMax / 2) - 1, ulid.getLeastSignificantBits());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsValidString() {
|
||||||
|
|
||||||
|
String ulid = null; // Null
|
||||||
|
assertFalse("Null ULID should be invalid.", Ulid.isValid(ulid));
|
||||||
|
|
||||||
|
ulid = ""; // length: 0
|
||||||
|
assertFalse("ULID with empty string should be invalid .", Ulid.isValid(ulid));
|
||||||
|
|
||||||
|
ulid = "0123456789ABCDEFGHJKMNPQRS"; // All upper case
|
||||||
|
assertTrue("ULID in upper case should valid.", Ulid.isValid(ulid));
|
||||||
|
|
||||||
|
ulid = "0123456789abcdefghjklmnpqr"; // All lower case
|
||||||
|
assertTrue("ULID in lower case should be valid.", Ulid.isValid(ulid));
|
||||||
|
|
||||||
|
ulid = "0123456789AbCdEfGhJkMnPqRs"; // Mixed case
|
||||||
|
assertTrue("Ulid in upper and lower case should valid.", Ulid.isValid(ulid));
|
||||||
|
|
||||||
|
ulid = "0123456789ABCDEFGHJKLMNPQ"; // length: 25
|
||||||
|
assertFalse("ULID length lower than 26 should be invalid.", Ulid.isValid(ulid));
|
||||||
|
|
||||||
|
ulid = "0123456789ABCDEFGHJKMNPQZZZ"; // length: 27
|
||||||
|
assertFalse("ULID length greater than 26 should be invalid.", Ulid.isValid(ulid));
|
||||||
|
|
||||||
|
ulid = "u123456789ABCDEFGHJKMNPQRS"; // Letter u
|
||||||
|
assertFalse("ULID with 'u' or 'U' should be invalid.", Ulid.isValid(ulid));
|
||||||
|
|
||||||
|
ulid = "0123456789ABCDEFGHJKMNPQR#"; // Special char
|
||||||
|
assertFalse("ULID with special chars should be invalid.", Ulid.isValid(ulid));
|
||||||
|
|
||||||
|
ulid = "8ZZZZZZZZZABCDEFGHJKMNPQRS"; // time > (2^48)-1
|
||||||
|
assertFalse("ULID with timestamp greater than (2^48)-1 should be invalid.", Ulid.isValid(ulid));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToCharArray() {
|
||||||
|
|
||||||
|
String ulid = null; // Null
|
||||||
|
try {
|
||||||
|
Ulid.toCharArray(ulid);
|
||||||
|
fail("Null ULID should be invalid.");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
|
||||||
|
ulid = ""; // length: 0
|
||||||
|
try {
|
||||||
|
Ulid.toCharArray(ulid);
|
||||||
|
fail("Should throw an exception");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
|
||||||
|
ulid = "0123456789ABCDEFGHJKMNPQRS"; // All upper case
|
||||||
|
try {
|
||||||
|
Ulid.toCharArray(ulid);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
fail("Should not throw an exception");
|
||||||
|
}
|
||||||
|
|
||||||
|
ulid = "0123456789abcdefghjklmnpqr"; // All lower case
|
||||||
|
try {
|
||||||
|
Ulid.toCharArray(ulid);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
fail("Should not throw an exception");
|
||||||
|
}
|
||||||
|
|
||||||
|
ulid = "0123456789AbCdEfGhJkMnPqRs"; // Mixed case
|
||||||
|
try {
|
||||||
|
Ulid.toCharArray(ulid);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
fail("Should not throw an exception");
|
||||||
|
}
|
||||||
|
|
||||||
|
ulid = "0123456789ABCDEFGHJKLMNPQ"; // length: 25
|
||||||
|
try {
|
||||||
|
Ulid.toCharArray(ulid);
|
||||||
|
fail("Should throw an exception");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
|
||||||
|
ulid = "0123456789ABCDEFGHJKMNPQZZZ"; // length: 27
|
||||||
|
try {
|
||||||
|
Ulid.toCharArray(ulid);
|
||||||
|
fail("Should throw an exception");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
|
||||||
|
ulid = "u123456789ABCDEFGHJKMNPQRS"; // Letter u
|
||||||
|
try {
|
||||||
|
Ulid.toCharArray(ulid);
|
||||||
|
fail("Should throw an exception");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
|
||||||
|
ulid = "0123456789ABCDEFGHJKMNPQR@"; // Special char
|
||||||
|
try {
|
||||||
|
Ulid.toCharArray(ulid);
|
||||||
|
fail("Should throw an exception");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
|
||||||
|
ulid = "8ZZZZZZZZZABCDEFGHJKMNPQRS"; // time > (2^48)-1
|
||||||
|
try {
|
||||||
|
Ulid.toCharArray(ulid);
|
||||||
|
fail("Should throw an exception");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// success
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,9 +447,9 @@ public class UlidTest {
|
||||||
long random1 = 0;
|
long random1 = 0;
|
||||||
long random2 = 0;
|
long random2 = 0;
|
||||||
|
|
||||||
String tm = string.substring(0, 10);
|
String tm = string.substring(0, 10).toUpperCase();
|
||||||
String r1 = string.substring(10, 18);
|
String r1 = string.substring(10, 18).toUpperCase();
|
||||||
String r2 = string.substring(18, 26);
|
String r2 = string.substring(18, 26).toUpperCase();
|
||||||
|
|
||||||
tm = transliterate(tm, ALPHABET_CROCKFORD, ALPHABET_JAVA);
|
tm = transliterate(tm, ALPHABET_CROCKFORD, ALPHABET_JAVA);
|
||||||
r1 = transliterate(r1, ALPHABET_CROCKFORD, ALPHABET_JAVA);
|
r1 = transliterate(r1, ALPHABET_CROCKFORD, ALPHABET_JAVA);
|
||||||
|
@ -193,40 +526,6 @@ public class UlidTest {
|
||||||
|
|
||||||
return r1 + r2;
|
return r1 + r2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void isValidString() {
|
|
||||||
|
|
||||||
String ulid = null; // Null
|
|
||||||
assertFalse("Null ULID should be invalid.", Ulid.isValidString(ulid));
|
|
||||||
|
|
||||||
ulid = ""; // length: 0
|
|
||||||
assertFalse("ULID with empty string should be invalid .", Ulid.isValidString(ulid));
|
|
||||||
|
|
||||||
ulid = "0123456789ABCDEFGHJKMNPQRS"; // All upper case
|
|
||||||
assertTrue("ULID in upper case should valid.", Ulid.isValidString(ulid));
|
|
||||||
|
|
||||||
ulid = "0123456789abcdefghjklmnpqr"; // All lower case
|
|
||||||
assertTrue("ULID in lower case should be valid.", Ulid.isValidString(ulid));
|
|
||||||
|
|
||||||
ulid = "0123456789AbCdEfGhJkMnPqRs"; // Mixed case
|
|
||||||
assertTrue("Ulid in upper and lower case should valid.", Ulid.isValidString(ulid));
|
|
||||||
|
|
||||||
ulid = "0123456789ABCDEFGHJKLMNPQ"; // length: 25
|
|
||||||
assertFalse("ULID length lower than 26 should be invalid.", Ulid.isValidString(ulid));
|
|
||||||
|
|
||||||
ulid = "0123456789ABCDEFGHJKMNPQZZZ"; // length: 27
|
|
||||||
assertFalse("ULID length greater than 26 should be invalid.", Ulid.isValidString(ulid));
|
|
||||||
|
|
||||||
ulid = "u123456789ABCDEFGHJKMNPQRS"; // Letter u
|
|
||||||
assertFalse("ULID with 'u' or 'U' should be invalid.", Ulid.isValidString(ulid));
|
|
||||||
|
|
||||||
ulid = "#123456789ABCDEFGHJKMNPQRS"; // Special char
|
|
||||||
assertFalse("ULID with special chars should be invalid.", Ulid.isValidString(ulid));
|
|
||||||
|
|
||||||
ulid = "8ZZZZZZZZZABCDEFGHJKMNPQRS"; // timestamp > (2^48)-1
|
|
||||||
assertFalse("ULID with timestamp greater than (2^48)-1 should be invalid.", Ulid.isValidString(ulid));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String transliterate(String string, char[] alphabet1, char[] alphabet2) {
|
private static String transliterate(String string, char[] alphabet1, char[] alphabet2) {
|
||||||
char[] output = string.toCharArray();
|
char[] output = string.toCharArray();
|
||||||
|
|
|
@ -2,7 +2,7 @@ package com.github.f4b6a3.ulid;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import com.github.f4b6a3.ulid.UlidCreator;
|
import com.github.f4b6a3.ulid.UlidCreator;
|
||||||
import com.github.f4b6a3.ulid.creator.UlidSpecCreator;
|
import com.github.f4b6a3.ulid.creator.UlidFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -22,8 +22,8 @@ public class UniquenessTest {
|
||||||
|
|
||||||
private boolean verbose; // Show progress or not
|
private boolean verbose; // Show progress or not
|
||||||
|
|
||||||
// ULID Spec creator
|
// ULID Spec factory
|
||||||
private UlidSpecCreator creator;
|
private UlidFactory factory;
|
||||||
|
|
||||||
private long time = System.currentTimeMillis(); // fixed timestamp
|
private long time = System.currentTimeMillis(); // fixed timestamp
|
||||||
|
|
||||||
|
@ -32,12 +32,12 @@ public class UniquenessTest {
|
||||||
*
|
*
|
||||||
* @param threadCount
|
* @param threadCount
|
||||||
* @param requestCount
|
* @param requestCount
|
||||||
* @param creator
|
* @param factory
|
||||||
*/
|
*/
|
||||||
public UniquenessTest(int threadCount, int requestCount, UlidSpecCreator creator, boolean progress) {
|
public UniquenessTest(int threadCount, int requestCount, UlidFactory factory, boolean progress) {
|
||||||
this.threadCount = threadCount;
|
this.threadCount = threadCount;
|
||||||
this.requestCount = requestCount;
|
this.requestCount = requestCount;
|
||||||
this.creator = creator;
|
this.factory = factory;
|
||||||
this.verbose = progress;
|
this.verbose = progress;
|
||||||
this.initCache();
|
this.initCache();
|
||||||
}
|
}
|
||||||
|
@ -90,13 +90,13 @@ public class UniquenessTest {
|
||||||
|
|
||||||
for (int i = 0; i < max; i++) {
|
for (int i = 0; i < max; i++) {
|
||||||
|
|
||||||
// Request a UUID
|
// Request a ULID
|
||||||
Ulid ulid = creator.create(time);
|
Ulid ulid = factory.create(time);
|
||||||
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
// Calculate and show progress
|
if (i % (max / 100) == 0) {
|
||||||
progress = (int) ((i * 1.0 / max) * 100);
|
// Calculate and show progress
|
||||||
if (progress % 10 == 0) {
|
progress = (int) ((i * 1.0 / max) * 100);
|
||||||
System.out.println(String.format("[Thread %06d] %s %s %s%%", id, ulid, i, (int) progress));
|
System.out.println(String.format("[Thread %06d] %s %s %s%%", id, ulid, i, (int) progress));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,9 +117,9 @@ public class UniquenessTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void execute(boolean verbose, int threadCount, int requestCount) {
|
public static void execute(boolean verbose, int threadCount, int requestCount) {
|
||||||
UlidSpecCreator creator = UlidCreator.getMonotonicUlidSpecCreator();
|
UlidFactory factory = UlidCreator.getMonotonicFactory();
|
||||||
|
|
||||||
UniquenessTest test = new UniquenessTest(threadCount, requestCount, creator, verbose);
|
UniquenessTest test = new UniquenessTest(threadCount, requestCount, factory, verbose);
|
||||||
test.start();
|
test.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.openjdk.jmh.runner.RunnerException;
|
||||||
import org.openjdk.jmh.runner.options.Options;
|
import org.openjdk.jmh.runner.options.Options;
|
||||||
import org.openjdk.jmh.runner.options.OptionsBuilder;
|
import org.openjdk.jmh.runner.options.OptionsBuilder;
|
||||||
|
|
||||||
|
import com.github.f4b6a3.ulid.Ulid;
|
||||||
import com.github.f4b6a3.ulid.UlidCreator;
|
import com.github.f4b6a3.ulid.UlidCreator;
|
||||||
|
|
||||||
@Threads(1)
|
@Threads(1)
|
||||||
|
@ -42,47 +43,47 @@ public class Benchmarks {
|
||||||
@Benchmark
|
@Benchmark
|
||||||
@BenchmarkMode(Mode.Throughput)
|
@BenchmarkMode(Mode.Throughput)
|
||||||
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
||||||
public String getUlidStringThroughput() {
|
public UUID getUuid() {
|
||||||
return UlidCreator.getUlidString();
|
return UUID.randomUUID();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Benchmark
|
|
||||||
@BenchmarkMode(Mode.AverageTime)
|
|
||||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
|
||||||
public String getUlidStringAverage() {
|
|
||||||
return UlidCreator.getUlidString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Benchmark
|
@Benchmark
|
||||||
@BenchmarkMode(Mode.Throughput)
|
@BenchmarkMode(Mode.Throughput)
|
||||||
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
||||||
public UUID getUlidThroughput() {
|
public String getUuidString() {
|
||||||
return UlidCreator.getUlid();
|
return UUID.randomUUID().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Benchmark
|
@Benchmark
|
||||||
@BenchmarkMode(Mode.AverageTime)
|
@BenchmarkMode(Mode.Throughput)
|
||||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
||||||
public UUID getUlidAverage() {
|
public Ulid getUlid() {
|
||||||
return UlidCreator.getUlid();
|
return UlidCreator.getUlid();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Benchmark
|
@Benchmark
|
||||||
@BenchmarkMode(Mode.Throughput)
|
@BenchmarkMode(Mode.Throughput)
|
||||||
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
||||||
public UUID getRandomUUIDThroughput() {
|
public String getUlidString() {
|
||||||
return UUID.randomUUID();
|
return UlidCreator.getUlid().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Benchmark
|
@Benchmark
|
||||||
@BenchmarkMode(Mode.AverageTime)
|
@BenchmarkMode(Mode.Throughput)
|
||||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
||||||
public UUID getRandomUUIDAverage() {
|
public Ulid getMonotonicUlid() {
|
||||||
return UUID.randomUUID();
|
return UlidCreator.getMonotonicUlid();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
@BenchmarkMode(Mode.Throughput)
|
||||||
|
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
||||||
|
public String getMonotonicUlidString() {
|
||||||
|
return UlidCreator.getMonotonicUlid().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws RunnerException {
|
public static void main(String[] args) throws RunnerException {
|
||||||
Options opt = new OptionsBuilder().include(MyBenchmark.class.getSimpleName()).forks(1).build();
|
Options opt = new OptionsBuilder().include(Benchmarks.class.getSimpleName()).forks(1).build();
|
||||||
new Runner(opt).run();
|
new Runner(opt).run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,9 @@ import com.github.f4b6a3.ulid.UlidCreator;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
public class UlidSpecCreatorTest extends AbstractUlidSpecCreatorTest {
|
public class DefaultUlidFactoryTest extends UlidFactoryTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetUlid() {
|
public void testGetUlid() {
|
||||||
|
@ -65,7 +66,9 @@ public class UlidSpecCreatorTest extends AbstractUlidSpecCreatorTest {
|
||||||
|
|
||||||
// Instantiate and start many threads
|
// Instantiate and start many threads
|
||||||
for (int i = 0; i < THREAD_TOTAL; i++) {
|
for (int i = 0; i < THREAD_TOTAL; i++) {
|
||||||
threads[i] = new TestThread(UlidCreator.getUlidSpecCreator(), DEFAULT_LOOP_MAX);
|
Random random = new Random();
|
||||||
|
UlidFactory factory = UlidCreator.getDefaultFactory().withRandomGenerator(random::nextBytes);
|
||||||
|
threads[i] = new TestThread(factory, DEFAULT_LOOP_MAX);
|
||||||
threads[i].start();
|
threads[i].start();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,9 @@ import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
public class MonotonicUlidSpecCreatorTest extends AbstractUlidSpecCreatorTest {
|
public class MonotonicUlidFactoryTest extends UlidFactoryTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetUlid() {
|
public void testGetUlid() {
|
||||||
|
@ -76,7 +77,9 @@ public class MonotonicUlidSpecCreatorTest extends AbstractUlidSpecCreatorTest {
|
||||||
|
|
||||||
// Instantiate and start many threads
|
// Instantiate and start many threads
|
||||||
for (int i = 0; i < THREAD_TOTAL; i++) {
|
for (int i = 0; i < THREAD_TOTAL; i++) {
|
||||||
threads[i] = new TestThread(UlidCreator.getMonotonicUlidSpecCreator(), DEFAULT_LOOP_MAX);
|
Random random = new Random();
|
||||||
|
UlidFactory factory = UlidCreator.getMonotonicFactory().withRandomGenerator(random::nextBytes);
|
||||||
|
threads[i] = new TestThread(factory, DEFAULT_LOOP_MAX);
|
||||||
threads[i].start();
|
threads[i].start();
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import java.util.Random;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public abstract class AbstractUlidSpecCreatorTest {
|
public abstract class UlidFactoryTest {
|
||||||
|
|
||||||
protected static final int DEFAULT_LOOP_MAX = 10_000;
|
protected static final int DEFAULT_LOOP_MAX = 10_000;
|
||||||
|
|
||||||
|
@ -28,10 +28,10 @@ public abstract class AbstractUlidSpecCreatorTest {
|
||||||
protected static class TestThread extends Thread {
|
protected static class TestThread extends Thread {
|
||||||
|
|
||||||
public static Set<UUID> hashSet = new HashSet<>();
|
public static Set<UUID> hashSet = new HashSet<>();
|
||||||
private UlidSpecCreator creator;
|
private UlidFactory creator;
|
||||||
private int loopLimit;
|
private int loopLimit;
|
||||||
|
|
||||||
public TestThread(UlidSpecCreator creator, int loopLimit) {
|
public TestThread(UlidFactory creator, int loopLimit) {
|
||||||
this.creator = creator;
|
this.creator = creator;
|
||||||
this.loopLimit = loopLimit;
|
this.loopLimit = loopLimit;
|
||||||
}
|
}
|
Loading…
Reference in New Issue