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
					
				
							
								
								
									
										226
									
								
								README.md
								
								
								
								
							
							
						
						
									
										226
									
								
								README.md
								
								
								
								
							| 
						 | 
				
			
			@ -1,13 +1,15 @@
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
# ULID Creator
 | 
			
		||||
 | 
			
		||||
A Java library for generating ULIDs.
 | 
			
		||||
A Java library for generating [ULIDs](https://github.com/ulid/spec).
 | 
			
		||||
 | 
			
		||||
* Generated in lexicographical order;
 | 
			
		||||
* Can be stored as a UUID/GUID;
 | 
			
		||||
* 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 URL safe, case insensitive and accepts hyphens.
 | 
			
		||||
* String format is URL safe and case insensitive.
 | 
			
		||||
 | 
			
		||||
How to Use
 | 
			
		||||
------------------------------------------------------
 | 
			
		||||
| 
						 | 
				
			
			@ -15,13 +17,13 @@ How to Use
 | 
			
		|||
Create a ULID:
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
UUID ulid = UlidCreator.getUlid(); // 01706d6c-6aad-c795-370c-98d0be881bba
 | 
			
		||||
Ulid ulid = UlidCreator.getUlid();
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Create a ULID string:
 | 
			
		||||
Create a Monotonic ULID:
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
String ulid = UlidCreator.getUlidString(); // 01E1PPRTMSQ34W7JR5YSND6B8Z
 | 
			
		||||
Ulid ulid = UlidCreator.getMonotonicUlid();
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Maven dependency
 | 
			
		||||
| 
						 | 
				
			
			@ -33,7 +35,7 @@ Add these lines to your `pom.xml`.
 | 
			
		|||
<dependency>
 | 
			
		||||
  <groupId>com.github.f4b6a3</groupId>
 | 
			
		||||
  <artifactId>ulid-creator</artifactId>
 | 
			
		||||
  <version>2.3.3</version>
 | 
			
		||||
  <version>3.0.0</version>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator).
 | 
			
		||||
| 
						 | 
				
			
			@ -43,116 +45,162 @@ Implementation
 | 
			
		|||
 | 
			
		||||
### 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.
 | 
			
		||||
 | 
			
		||||
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`.
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
// GUID based on ULID spec
 | 
			
		||||
UUID ulid = UlidCreator.getUlid();
 | 
			
		||||
// Generate a ULID
 | 
			
		||||
Ulid ulid = UlidCreator.getUlid();
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
// GUID based on ULID spec
 | 
			
		||||
// Compatible with RFC-4122 UUID v4
 | 
			
		||||
UUID ulid = UlidCreator.getUlid4();
 | 
			
		||||
// Generate a ULID with a specific time
 | 
			
		||||
Ulid ulid = UlidCreator.getUlid(1234567890);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Sequence of GUIDs based on ULID spec:
 | 
			
		||||
Sequence of ULIDs:
 | 
			
		||||
 | 
			
		||||
```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
 | 
			
		||||
01EX8Y21KBH49ZZCA7KSKH6X1C
 | 
			
		||||
01EX8Y21KBJTFK0JV5J20QPQNR
 | 
			
		||||
01EX8Y21KBG2CS1V6WQCTVM7K6
 | 
			
		||||
01EX8Y21KB8HPZNBP3PTW7HVEY
 | 
			
		||||
01EX8Y21KB3HZV38VAPTPAG1TY
 | 
			
		||||
01EX8Y21KB9FTEJHPAGAKYG9Z8
 | 
			
		||||
01EX8Y21KBQGKGH2SVPQAYEFFC
 | 
			
		||||
01EX8Y21KBY17J9WR9KQR8SE7H
 | 
			
		||||
01EX8Y21KCVHYSJGVK4HBXDMR9 < millisecond changed
 | 
			
		||||
01EX8Y21KC668W3PEDEAGDHMVG
 | 
			
		||||
01EX8Y21KC53D2S5ADQ2EST327
 | 
			
		||||
01EX8Y21KCPQ3TENMTY1S7HV56
 | 
			
		||||
01EX8Y21KC3755QF9STQEV05EB
 | 
			
		||||
01EX8Y21KC5ZSHK908GMDK69WE
 | 
			
		||||
01EX8Y21KCSGJS8S1FVS06B3SX
 | 
			
		||||
01EX8Y21KC6ZBWQ0JBV337R1CN
 | 
			
		||||
         ^ look
 | 
			
		||||
 | 
			
		||||
|------------|---------------------|
 | 
			
		||||
  millisecs        randomness
 | 
			
		||||
|---------|--------------|
 | 
			
		||||
    time      random
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### ULID string
 | 
			
		||||
### Monotonic ULID
 | 
			
		||||
 | 
			
		||||
The ULID string is a sequence of 26 chars. See the [ULID specification](https://github.com/ulid/spec) for more information.
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
See the section on GUIDs to know how the 128 bits are generated in this library.
 | 
			
		||||
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
 | 
			
		||||
// String based on ULID spec
 | 
			
		||||
String ulid = UlidCreator.getUlidString();
 | 
			
		||||
// Generate a Monotonic ULID
 | 
			
		||||
Ulid ulid = UlidCreator.getMonotonicUlid();
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
// String based on ULID spec
 | 
			
		||||
// Compatible with RFC-4122 UUID v4
 | 
			
		||||
String ulid = UlidCreator.getUlidString4();
 | 
			
		||||
// Generate a Monotonic ULID with a specific time
 | 
			
		||||
Ulid ulid = UlidCreator.getMonotonicUlid(1234567890);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Sequence of Strings based on ULID spec:
 | 
			
		||||
Sequence of Monotonic ULIDs:
 | 
			
		||||
 | 
			
		||||
```text
 | 
			
		||||
01E1PPRTMSQ34W7JR5YSND6B8T
 | 
			
		||||
01E1PPRTMSQ34W7JR5YSND6B8V
 | 
			
		||||
01E1PPRTMSQ34W7JR5YSND6B8W
 | 
			
		||||
01E1PPRTMSQ34W7JR5YSND6B8X
 | 
			
		||||
01E1PPRTMSQ34W7JR5YSND6B8Y
 | 
			
		||||
01E1PPRTMSQ34W7JR5YSND6B8Z
 | 
			
		||||
01E1PPRTMSQ34W7JR5YSND6B90
 | 
			
		||||
01E1PPRTMSQ34W7JR5YSND6B91
 | 
			
		||||
01E1PPRTMTYMX8G17TWSJJZMEE < millisecond changed
 | 
			
		||||
01E1PPRTMTYMX8G17TWSJJZMEF
 | 
			
		||||
01E1PPRTMTYMX8G17TWSJJZMEG
 | 
			
		||||
01E1PPRTMTYMX8G17TWSJJZMEH
 | 
			
		||||
01E1PPRTMTYMX8G17TWSJJZMEJ
 | 
			
		||||
01E1PPRTMTYMX8G17TWSJJZMEK
 | 
			
		||||
01E1PPRTMTYMX8G17TWSJJZMEM
 | 
			
		||||
01E1PPRTMTYMX8G17TWSJJZMEN
 | 
			
		||||
01EX8Y7M8MDVX3M3EQG69EEMJW
 | 
			
		||||
01EX8Y7M8MDVX3M3EQG69EEMJX
 | 
			
		||||
01EX8Y7M8MDVX3M3EQG69EEMJY
 | 
			
		||||
01EX8Y7M8MDVX3M3EQG69EEMJZ
 | 
			
		||||
01EX8Y7M8MDVX3M3EQG69EEMK0
 | 
			
		||||
01EX8Y7M8MDVX3M3EQG69EEMK1
 | 
			
		||||
01EX8Y7M8MDVX3M3EQG69EEMK2
 | 
			
		||||
01EX8Y7M8MDVX3M3EQG69EEMK3
 | 
			
		||||
01EX8Y7M8N1G30CYF2PJR23J2J < millisecond changed
 | 
			
		||||
01EX8Y7M8N1G30CYF2PJR23J2K
 | 
			
		||||
01EX8Y7M8N1G30CYF2PJR23J2M
 | 
			
		||||
01EX8Y7M8N1G30CYF2PJR23J2N
 | 
			
		||||
01EX8Y7M8N1G30CYF2PJR23J2P
 | 
			
		||||
01EX8Y7M8N1G30CYF2PJR23J2Q
 | 
			
		||||
01EX8Y7M8N1G30CYF2PJR23J2R
 | 
			
		||||
01EX8Y7M8N1G30CYF2PJR23J2S
 | 
			
		||||
         ^ look          ^ look
 | 
			
		||||
 | 
			
		||||
|---------|--------------|
 | 
			
		||||
 millisecs   randomness
 | 
			
		||||
    time      random
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### How use the `UlidSpecCreator` directly
 | 
			
		||||
### Other usage examples
 | 
			
		||||
 | 
			
		||||
These are some examples of using the `UlidSpecCreator` to create ULID strings:
 | 
			
		||||
Create a ULID from a canonical string (26 chars):
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
// with your custom timestamp strategy
 | 
			
		||||
TimestampStrategy customStrategy = new CustomTimestampStrategy();
 | 
			
		||||
UlidSpecCreator creator = UlidCreator.getUlidSpecCreator()
 | 
			
		||||
	.withTimestampStrategy(customStrategy);
 | 
			
		||||
String ulid = creator.createString();
 | 
			
		||||
Ulid ulid = Ulid.from("0123456789ABCDEFGHJKMNPQRS");
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Convert a ULID into a canonical string in upper case:
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
// with your custom random strategy that wraps any random generator
 | 
			
		||||
RandomStrategy customStrategy = new CustomRandomStrategy();
 | 
			
		||||
UlidSpecCreator creator = UlidCreator.getUlidSpecCreator()
 | 
			
		||||
	.withRandomStrategy(customStrategy);
 | 
			
		||||
String ulid = creator.createString();
 | 
			
		||||
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
 | 
			
		||||
// with `java.util.Random` number generator
 | 
			
		||||
Random random = new Random();
 | 
			
		||||
UlidSpecCreator creator = UlidCreator.getUlidSpecCreator()
 | 
			
		||||
    .withRandomGenerator(random);
 | 
			
		||||
String ulid = creator.createString();
 | 
			
		||||
UlidFactory factory = UlidCreator.getDefaultFactory().withRandomGenerator(random::nextBytes);
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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.UlidCreator_UlidString       thrpt    5  12223,501 ±  89,836  ops/ms
 | 
			
		||||
Throughput.UlidCreator01_toString       thrpt    5  29487,382 ± 627,808  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)
 | 
			
		||||
 | 
			
		||||
Links for generators
 | 
			
		||||
Other generators
 | 
			
		||||
-------------------------------------------
 | 
			
		||||
* [UUID Creator](https://github.com/f4b6a3/uuid-creator)
 | 
			
		||||
* [ULID Creator](https://github.com/f4b6a3/ulid-creator)
 | 
			
		||||
* [TSID Creator](https://github.com/f4b6a3/tsid-creator)
 | 
			
		||||
* [UUID Creator](https://github.com/f4b6a3/uuid-creator): for generating UUIDs
 | 
			
		||||
* [TSID Creator](https://github.com/f4b6a3/tsid-creator): for generating Time Sortable IDs
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								pom.xml
								
								
								
								
							
							
						
						
									
										2
									
								
								pom.xml
								
								
								
								
							| 
						 | 
				
			
			@ -3,7 +3,7 @@
 | 
			
		|||
 | 
			
		||||
	<groupId>com.github.f4b6a3</groupId>
 | 
			
		||||
	<artifactId>ulid-creator</artifactId>
 | 
			
		||||
	<version>2.3.4-SNAPSHOT</version>
 | 
			
		||||
	<version>3.0.1-SNAPSHOT</version>
 | 
			
		||||
	<packaging>jar</packaging>
 | 
			
		||||
 | 
			
		||||
	<name>ulid-creator</name>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,13 +30,23 @@ import java.util.UUID;
 | 
			
		|||
 | 
			
		||||
/**
 | 
			
		||||
 * 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> {
 | 
			
		||||
 | 
			
		||||
	private final long msb;
 | 
			
		||||
	private final long lsb;
 | 
			
		||||
	private static final long serialVersionUID = 2625269413446854731L;
 | 
			
		||||
 | 
			
		||||
	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 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 RANDOM_BYTES_LENGTH = 10;
 | 
			
		||||
 | 
			
		||||
	// 0xffffffffffffffffL + 1 = 0x0000000000000000L
 | 
			
		||||
	private static final long INCREMENT_OVERFLOW = 0x0000000000000000L;
 | 
			
		||||
 | 
			
		||||
	protected static final char[] ALPHABET_UPPERCASE = //
 | 
			
		||||
	private static final char[] ALPHABET_UPPERCASE = //
 | 
			
		||||
			{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', //
 | 
			
		||||
					'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', //
 | 
			
		||||
					'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', //
 | 
			
		||||
					'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', //
 | 
			
		||||
					'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 {
 | 
			
		||||
		for (int i = 0; i < ALPHABET_VALUES.length; i++) {
 | 
			
		||||
			ALPHABET_VALUES[i] = -1;
 | 
			
		||||
| 
						 | 
				
			
			@ -129,139 +136,190 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
 | 
			
		|||
		ALPHABET_VALUES['O'] = 0x00;
 | 
			
		||||
		ALPHABET_VALUES['I'] = 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) {
 | 
			
		||||
		this.msb = mostSignificantBits;
 | 
			
		||||
		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) {
 | 
			
		||||
 | 
			
		||||
		// The time component has 48 bits.
 | 
			
		||||
		if ((time & 0xffff000000000000L) != 0) {
 | 
			
		||||
			// 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."
 | 
			
		||||
			throw new IllegalArgumentException("Invalid time value"); // time overflow!
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	public static Ulid of(UUID uuid) {
 | 
			
		||||
		return new Ulid(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: test
 | 
			
		||||
	public static Ulid of(byte[] bytes) {
 | 
			
		||||
 | 
			
		||||
		if (bytes == null || bytes.length != ULID_BYTES_LENGTH) {
 | 
			
		||||
			throw new IllegalArgumentException("Invalid ULID bytes");
 | 
			
		||||
		// The random component has 80 bits (10 bytes).
 | 
			
		||||
		if (random == null || random.length != RANDOM_BYTES_LENGTH) {
 | 
			
		||||
			throw new IllegalArgumentException("Invalid random bytes"); // null or wrong length!
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		long long0 = 0;
 | 
			
		||||
		long long1 = 0;
 | 
			
		||||
 | 
			
		||||
		long0 |= (bytes[0x0] & 0xffL) << 56;
 | 
			
		||||
		long0 |= (bytes[0x1] & 0xffL) << 48;
 | 
			
		||||
		long0 |= (bytes[0x2] & 0xffL) << 40;
 | 
			
		||||
		long0 |= (bytes[0x3] & 0xffL) << 32;
 | 
			
		||||
		long0 |= (bytes[0x4] & 0xffL) << 24;
 | 
			
		||||
		long0 |= (bytes[0x5] & 0xffL) << 16;
 | 
			
		||||
		long0 |= (bytes[0x6] & 0xffL) << 8;
 | 
			
		||||
		long0 |= (bytes[0x7] & 0xffL);
 | 
			
		||||
		long0 |= time << 16;
 | 
			
		||||
		long0 |= (long) (random[0x0] & 0xff) << 8;
 | 
			
		||||
		long0 |= (long) (random[0x1] & 0xff);
 | 
			
		||||
 | 
			
		||||
		long1 |= (bytes[0x8] & 0xffL) << 56;
 | 
			
		||||
		long1 |= (bytes[0x9] & 0xffL) << 48;
 | 
			
		||||
		long1 |= (bytes[0xa] & 0xffL) << 40;
 | 
			
		||||
		long1 |= (bytes[0xb] & 0xffL) << 32;
 | 
			
		||||
		long1 |= (bytes[0xc] & 0xffL) << 24;
 | 
			
		||||
		long1 |= (bytes[0xd] & 0xffL) << 16;
 | 
			
		||||
		long1 |= (bytes[0xe] & 0xffL) << 8;
 | 
			
		||||
		long1 |= (bytes[0xf] & 0xffL);
 | 
			
		||||
		long1 |= (long) (random[0x2] & 0xff) << 56;
 | 
			
		||||
		long1 |= (long) (random[0x3] & 0xff) << 48;
 | 
			
		||||
		long1 |= (long) (random[0x4] & 0xff) << 40;
 | 
			
		||||
		long1 |= (long) (random[0x5] & 0xff) << 32;
 | 
			
		||||
		long1 |= (long) (random[0x6] & 0xff) << 24;
 | 
			
		||||
		long1 |= (long) (random[0x7] & 0xff) << 16;
 | 
			
		||||
		long1 |= (long) (random[0x8] & 0xff) << 8;
 | 
			
		||||
		long1 |= (long) (random[0x9] & 0xff);
 | 
			
		||||
 | 
			
		||||
		return new Ulid(long0, long1);
 | 
			
		||||
		this.msb = long0;
 | 
			
		||||
		this.lsb = long1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: optimize
 | 
			
		||||
	public static Ulid of(String string) {
 | 
			
		||||
 | 
			
		||||
		final char[] chars = toCharArray(string);
 | 
			
		||||
 | 
			
		||||
		long tm = 0;
 | 
			
		||||
		long r1 = 0;
 | 
			
		||||
		long r2 = 0;
 | 
			
		||||
 | 
			
		||||
		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);
 | 
			
		||||
	/**
 | 
			
		||||
	 * Converts a UUID into a ULID.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param uuid a UUID
 | 
			
		||||
	 * @return a ULID
 | 
			
		||||
	 */
 | 
			
		||||
	public static Ulid from(UUID uuid) {
 | 
			
		||||
		return new Ulid(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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) {
 | 
			
		||||
			throw new IllegalArgumentException("Invalid time value");
 | 
			
		||||
		}
 | 
			
		||||
		if (random == null || random.length != RANDOM_BYTES_LENGTH) {
 | 
			
		||||
			throw new IllegalArgumentException("Invalid random bytes");
 | 
			
		||||
		if (bytes == null || bytes.length != ULID_BYTES_LENGTH) {
 | 
			
		||||
			throw new IllegalArgumentException("Invalid ULID bytes"); // null or wrong length!
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		long msb = 0;
 | 
			
		||||
		long lsb = 0;
 | 
			
		||||
 | 
			
		||||
		msb |= time << 16;
 | 
			
		||||
		msb |= (long) (random[0x0] & 0xff) << 8;
 | 
			
		||||
		msb |= (long) (random[0x1] & 0xff);
 | 
			
		||||
		msb |= (bytes[0x0] & 0xffL) << 56;
 | 
			
		||||
		msb |= (bytes[0x1] & 0xffL) << 48;
 | 
			
		||||
		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 |= (long) (random[0x3] & 0xff) << 48;
 | 
			
		||||
		lsb |= (long) (random[0x4] & 0xff) << 40;
 | 
			
		||||
		lsb |= (long) (random[0x5] & 0xff) << 32;
 | 
			
		||||
		lsb |= (long) (random[0x6] & 0xff) << 24;
 | 
			
		||||
		lsb |= (long) (random[0x7] & 0xff) << 16;
 | 
			
		||||
		lsb |= (long) (random[0x8] & 0xff) << 8;
 | 
			
		||||
		lsb |= (long) (random[0x9] & 0xff);
 | 
			
		||||
		lsb |= (bytes[0x8] & 0xffL) << 56;
 | 
			
		||||
		lsb |= (bytes[0x9] & 0xffL) << 48;
 | 
			
		||||
		lsb |= (bytes[0xa] & 0xffL) << 40;
 | 
			
		||||
		lsb |= (bytes[0xb] & 0xffL) << 32;
 | 
			
		||||
		lsb |= (bytes[0xc] & 0xffL) << 24;
 | 
			
		||||
		lsb |= (bytes[0xd] & 0xffL) << 16;
 | 
			
		||||
		lsb |= (bytes[0xe] & 0xffL) << 8;
 | 
			
		||||
		lsb |= (bytes[0xf] & 0xffL);
 | 
			
		||||
 | 
			
		||||
		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() {
 | 
			
		||||
		return new UUID(this.msb, this.lsb);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: test
 | 
			
		||||
	public UUID toUuid4() {
 | 
			
		||||
		final long msb4 = (this.msb & 0xffffffffffff0fffL) | 0x0000000000004000L; // apply version 4
 | 
			
		||||
		final long lsb4 = (this.lsb & 0x3fffffffffffffffL) | 0x8000000000000000L; // apply variant RFC-4122
 | 
			
		||||
		return new UUID(msb4, lsb4);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: test
 | 
			
		||||
	/**
 | 
			
		||||
	 * Convert the ULID into a byte array.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @return an byte array.
 | 
			
		||||
	 */
 | 
			
		||||
	public byte[] toBytes() {
 | 
			
		||||
 | 
			
		||||
		final byte[] bytes = new byte[ULID_BYTES_LENGTH];
 | 
			
		||||
| 
						 | 
				
			
			@ -287,64 +345,183 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
 | 
			
		|||
		return bytes;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: test
 | 
			
		||||
	public byte[] toBytes4() {
 | 
			
		||||
		return Ulid.of(this.toUuid4()).toBytes();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public String toString() {
 | 
			
		||||
		return this.toUpperCase();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: test
 | 
			
		||||
	/**
 | 
			
		||||
	 * Converts the ULID into a canonical string in upper case.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * The output string is 26 characters long and contains only characters from
 | 
			
		||||
	 * Crockford's base 32 alphabet.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * See: https://www.crockford.com/base32.html
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @return a string
 | 
			
		||||
	 */
 | 
			
		||||
	public String toUpperCase() {
 | 
			
		||||
		return toString(ALPHABET_UPPERCASE);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: test
 | 
			
		||||
	public String toUpperCase4() {
 | 
			
		||||
		return Ulid.of(this.toUuid4()).toUpperCase();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: test
 | 
			
		||||
	/**
 | 
			
		||||
	 * Converts the ULID into a canonical string in lower case.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * The output string is 26 characters long and contains only characters from
 | 
			
		||||
	 * Crockford's base 32 alphabet.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * 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() {
 | 
			
		||||
		return toString(ALPHABET_LOWERCASE);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: test
 | 
			
		||||
	public String toLowerCase4() {
 | 
			
		||||
		return Ulid.of(this.toUuid4()).toLowerCase();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public long getTime() {
 | 
			
		||||
		return this.msb >>> 16;
 | 
			
		||||
	/**
 | 
			
		||||
	 * Converts the ULID into into another ULID that is compatible with UUID v4.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * The bytes of the returned ULID are compliant with the RFC-4122 version 4.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * If you need a RFC-4122 UUID v4 do this: {@code Ulid.toRfc4122().toUuid()}.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * 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() {
 | 
			
		||||
		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() {
 | 
			
		||||
		return this.msb;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Returns the least significant bits as a number.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @return a number.
 | 
			
		||||
	 */
 | 
			
		||||
	public long getLeastSignificantBits() {
 | 
			
		||||
		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() {
 | 
			
		||||
 | 
			
		||||
		long msb1 = this.msb;
 | 
			
		||||
		long lsb1 = this.lsb + 1; // Increment the LSB
 | 
			
		||||
		long newMsb = this.msb;
 | 
			
		||||
		long newLsb = this.lsb + 1; // increment the LEAST significant bits
 | 
			
		||||
 | 
			
		||||
		if (lsb1 == INCREMENT_OVERFLOW) {
 | 
			
		||||
			// Increment the random bits of the MSB
 | 
			
		||||
			msb1 = (msb1 & 0xffffffffffff0000L) | ((msb1 + 1) & 0x000000000000ffffL);
 | 
			
		||||
		if (newLsb == INCREMENT_OVERFLOW) {
 | 
			
		||||
			// carrying the extra bit by incrementing the MOST significant bits
 | 
			
		||||
			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
 | 
			
		||||
| 
						 | 
				
			
			@ -385,14 +562,13 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
 | 
			
		|||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: optimize
 | 
			
		||||
	protected String toString(char[] alphabet) {
 | 
			
		||||
 | 
			
		||||
		final char[] chars = new char[ULID_LENGTH];
 | 
			
		||||
 | 
			
		||||
		long time = this.msb >>> 16;
 | 
			
		||||
		long random1 = ((this.msb & 0xffffL) << 24) | (this.lsb >>> 40);
 | 
			
		||||
		long random2 = (this.lsb & 0xffffffffffL);
 | 
			
		||||
		long random0 = ((this.msb & 0xffffL) << 24) | (this.lsb >>> 40);
 | 
			
		||||
		long random1 = (this.lsb & 0xffffffffffL);
 | 
			
		||||
 | 
			
		||||
		chars[0x00] = alphabet[(int) (time >>> 45 & 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[0x09] = alphabet[(int) (time & 0b11111)];
 | 
			
		||||
 | 
			
		||||
		chars[0x0a] = alphabet[(int) (random1 >>> 35 & 0b11111)];
 | 
			
		||||
		chars[0x0b] = alphabet[(int) (random1 >>> 30 & 0b11111)];
 | 
			
		||||
		chars[0x0c] = alphabet[(int) (random1 >>> 25 & 0b11111)];
 | 
			
		||||
		chars[0x0d] = alphabet[(int) (random1 >>> 20 & 0b11111)];
 | 
			
		||||
		chars[0x0e] = alphabet[(int) (random1 >>> 15 & 0b11111)];
 | 
			
		||||
		chars[0x0f] = alphabet[(int) (random1 >>> 10 & 0b11111)];
 | 
			
		||||
		chars[0x10] = alphabet[(int) (random1 >>> 5 & 0b11111)];
 | 
			
		||||
		chars[0x11] = alphabet[(int) (random1 & 0b11111)];
 | 
			
		||||
		chars[0x0a] = alphabet[(int) (random0 >>> 35 & 0b11111)];
 | 
			
		||||
		chars[0x0b] = alphabet[(int) (random0 >>> 30 & 0b11111)];
 | 
			
		||||
		chars[0x0c] = alphabet[(int) (random0 >>> 25 & 0b11111)];
 | 
			
		||||
		chars[0x0d] = alphabet[(int) (random0 >>> 20 & 0b11111)];
 | 
			
		||||
		chars[0x0e] = alphabet[(int) (random0 >>> 15 & 0b11111)];
 | 
			
		||||
		chars[0x0f] = alphabet[(int) (random0 >>> 10 & 0b11111)];
 | 
			
		||||
		chars[0x10] = alphabet[(int) (random0 >>> 5 & 0b11111)];
 | 
			
		||||
		chars[0x11] = alphabet[(int) (random0 & 0b11111)];
 | 
			
		||||
 | 
			
		||||
		chars[0x12] = alphabet[(int) (random2 >>> 35 & 0b11111)];
 | 
			
		||||
		chars[0x13] = alphabet[(int) (random2 >>> 30 & 0b11111)];
 | 
			
		||||
		chars[0x14] = alphabet[(int) (random2 >>> 25 & 0b11111)];
 | 
			
		||||
		chars[0x15] = alphabet[(int) (random2 >>> 20 & 0b11111)];
 | 
			
		||||
		chars[0x16] = alphabet[(int) (random2 >>> 15 & 0b11111)];
 | 
			
		||||
		chars[0x17] = alphabet[(int) (random2 >>> 10 & 0b11111)];
 | 
			
		||||
		chars[0x18] = alphabet[(int) (random2 >>> 5 & 0b11111)];
 | 
			
		||||
		chars[0x19] = alphabet[(int) (random2 & 0b11111)];
 | 
			
		||||
		chars[0x12] = alphabet[(int) (random1 >>> 35 & 0b11111)];
 | 
			
		||||
		chars[0x13] = alphabet[(int) (random1 >>> 30 & 0b11111)];
 | 
			
		||||
		chars[0x14] = alphabet[(int) (random1 >>> 25 & 0b11111)];
 | 
			
		||||
		chars[0x15] = alphabet[(int) (random1 >>> 20 & 0b11111)];
 | 
			
		||||
		chars[0x16] = alphabet[(int) (random1 >>> 15 & 0b11111)];
 | 
			
		||||
		chars[0x17] = alphabet[(int) (random1 >>> 10 & 0b11111)];
 | 
			
		||||
		chars[0x18] = alphabet[(int) (random1 >>> 5 & 0b11111)];
 | 
			
		||||
		chars[0x19] = alphabet[(int) (random1 & 0b11111)];
 | 
			
		||||
 | 
			
		||||
		return new String(chars);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static boolean isValidString(String string) {
 | 
			
		||||
		return isValidArray(string == null ? null : string.toCharArray());
 | 
			
		||||
	protected static char[] toCharArray(String string) {
 | 
			
		||||
		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
 | 
			
		||||
	 * @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) {
 | 
			
		||||
			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) {
 | 
			
		||||
			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++) {
 | 
			
		||||
| 
						 | 
				
			
			@ -458,12 +645,4 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
 | 
			
		|||
 | 
			
		||||
		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;
 | 
			
		||||
 | 
			
		||||
import com.github.f4b6a3.ulid.creator.MonotonicUlidSpecCreator;
 | 
			
		||||
import com.github.f4b6a3.ulid.creator.UlidSpecCreator;
 | 
			
		||||
import com.github.f4b6a3.ulid.creator.MonotonicUlidFactory;
 | 
			
		||||
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 {
 | 
			
		||||
 | 
			
		||||
	private UlidCreator() {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Returns a ULID.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * The random component is always reset to a new random value.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @return a ULID
 | 
			
		||||
	 */
 | 
			
		||||
	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) {
 | 
			
		||||
		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() {
 | 
			
		||||
		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) {
 | 
			
		||||
		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 {
 | 
			
		||||
		static final UlidSpecCreator INSTANCE = getUlidSpecCreator();
 | 
			
		||||
	private static class DefaultFactoryHolder {
 | 
			
		||||
		static final UlidFactory INSTANCE = getDefaultFactory();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static class MonotonicCreatorHolder {
 | 
			
		||||
		static final UlidSpecCreator INSTANCE = getMonotonicUlidSpecCreator();
 | 
			
		||||
	private static class MonotonicFactoryHolder {
 | 
			
		||||
		static final UlidFactory INSTANCE = getMonotonicFactory();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,23 +26,25 @@ package com.github.f4b6a3.ulid.creator;
 | 
			
		|||
 | 
			
		||||
import com.github.f4b6a3.ulid.Ulid;
 | 
			
		||||
 | 
			
		||||
public final class MonotonicUlidSpecCreator extends UlidSpecCreator {
 | 
			
		||||
 | 
			
		||||
	private long lastTime;
 | 
			
		||||
	private Ulid lastUlid;
 | 
			
		||||
/**
 | 
			
		||||
 * Factory that generates default ULIDs.
 | 
			
		||||
 * 
 | 
			
		||||
 * 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
 | 
			
		||||
	public synchronized Ulid create(final long time) {
 | 
			
		||||
		
 | 
			
		||||
		if (time == this.lastTime) {
 | 
			
		||||
			this.lastUlid = lastUlid.increment();
 | 
			
		||||
		} else {
 | 
			
		||||
			final byte[] random = new byte[10];
 | 
			
		||||
			this.randomStrategy.nextBytes(random);
 | 
			
		||||
			this.lastUlid = Ulid.of(time, random);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this.lastTime = time;
 | 
			
		||||
		return this.lastUlid;
 | 
			
		||||
	public Ulid create(final long time) {
 | 
			
		||||
		final byte[] random = new byte[Ulid.RANDOM_BYTES_LENGTH];
 | 
			
		||||
		this.randomGenerator.nextBytes(random);
 | 
			
		||||
		return new Ulid(time, random);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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 com.github.f4b6a3.ulid.Ulid;
 | 
			
		||||
import com.github.f4b6a3.ulid.strategy.DefaultRandomStrategy;
 | 
			
		||||
import com.github.f4b6a3.ulid.strategy.RandomStrategy;
 | 
			
		||||
import com.github.f4b6a3.ulid.random.DefaultRandomGenerator;
 | 
			
		||||
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() {
 | 
			
		||||
		this.randomStrategy = new DefaultRandomStrategy();
 | 
			
		||||
	public UlidFactory() {
 | 
			
		||||
		this.randomGenerator = new DefaultRandomGenerator();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Returns a UUID.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @return a ULID
 | 
			
		||||
	 */
 | 
			
		||||
	public Ulid create() {
 | 
			
		||||
		return create(System.currentTimeMillis());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public Ulid create(final long time) {
 | 
			
		||||
		final byte[] random = new byte[10];
 | 
			
		||||
		this.randomStrategy.nextBytes(random);
 | 
			
		||||
		return Ulid.of(time, random);
 | 
			
		||||
	}
 | 
			
		||||
	/**
 | 
			
		||||
	 * Returns a UUID with a specific time.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * 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}.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param random a random generator
 | 
			
		||||
	 * @param <T>             the type parameter
 | 
			
		||||
	 * @return {@link AbstractRandomBasedUuidCreator}
 | 
			
		||||
	 * @param randomGenerator a random generator
 | 
			
		||||
	 * @return {@link UlidFactory}
 | 
			
		||||
	 */
 | 
			
		||||
	@SuppressWarnings("unchecked")
 | 
			
		||||
	public synchronized <T extends UlidSpecCreator> T withRandomStrategy(RandomStrategy randomStrategy) {
 | 
			
		||||
		this.randomStrategy = randomStrategy;
 | 
			
		||||
	public synchronized <T extends UlidFactory> T withRandomGenerator(RandomGenerator randomGenerator) {
 | 
			
		||||
		this.randomGenerator = randomGenerator;
 | 
			
		||||
		return (T) this;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -22,7 +22,7 @@
 | 
			
		|||
 * SOFTWARE.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.github.f4b6a3.ulid.strategy;
 | 
			
		||||
package com.github.f4b6a3.ulid.random;
 | 
			
		||||
 | 
			
		||||
import java.security.SecureRandom;
 | 
			
		||||
import java.util.Random;
 | 
			
		||||
| 
						 | 
				
			
			@ -30,7 +30,7 @@ import java.util.Random;
 | 
			
		|||
/**
 | 
			
		||||
 * 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();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -22,9 +22,9 @@
 | 
			
		|||
 * SOFTWARE.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.github.f4b6a3.ulid.strategy;
 | 
			
		||||
package com.github.f4b6a3.ulid.random;
 | 
			
		||||
 | 
			
		||||
@FunctionalInterface
 | 
			
		||||
public interface RandomStrategy {
 | 
			
		||||
public interface RandomGenerator {
 | 
			
		||||
	void nextBytes(byte[] bytes);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3,13 +3,13 @@ package com.github.f4b6a3.ulid;
 | 
			
		|||
import org.junit.runner.RunWith;
 | 
			
		||||
import org.junit.runners.Suite;
 | 
			
		||||
 | 
			
		||||
import com.github.f4b6a3.ulid.creator.MonotonicUlidSpecCreatorTest;
 | 
			
		||||
import com.github.f4b6a3.ulid.creator.UlidSpecCreatorTest;
 | 
			
		||||
import com.github.f4b6a3.ulid.creator.MonotonicUlidFactoryTest;
 | 
			
		||||
import com.github.f4b6a3.ulid.creator.DefaultUlidFactoryTest;
 | 
			
		||||
 | 
			
		||||
@RunWith(Suite.class)
 | 
			
		||||
@Suite.SuiteClasses({
 | 
			
		||||
	MonotonicUlidSpecCreatorTest.class,
 | 
			
		||||
	UlidSpecCreatorTest.class,
 | 
			
		||||
	MonotonicUlidFactoryTest.class,
 | 
			
		||||
	DefaultUlidFactoryTest.class,
 | 
			
		||||
	UlidTest.class,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,10 @@ package com.github.f4b6a3.ulid;
 | 
			
		|||
import static org.junit.Assert.assertEquals;
 | 
			
		||||
import static org.junit.Assert.assertFalse;
 | 
			
		||||
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.UUID;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -13,98 +16,428 @@ import com.github.f4b6a3.ulid.Ulid;
 | 
			
		|||
 | 
			
		||||
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_JAVA = "0123456789abcdefghijklmnopqrstuv".toCharArray(); // Long.parseUnsignedLong()
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void testOfAndToString() {
 | 
			
		||||
		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());
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	private static final long VERSION_MASK = 0x000000000000f000L;
 | 
			
		||||
	private static final long VARIANT_MASK = 0xc000000000000000L;
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void testConstructorLongs() {
 | 
			
		||||
		for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
 | 
			
		||||
		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); // <-- under test
 | 
			
		||||
 | 
			
		||||
			assertEquals(msb, ulid0.toUuid().getMostSignificantBits());
 | 
			
		||||
			assertEquals(lsb, ulid0.toUuid().getLeastSignificantBits());
 | 
			
		||||
			Ulid ulid0 = new Ulid(msb, lsb); // <-- test Ulid(long, long)
 | 
			
		||||
			assertEquals(msb, ulid0.getMostSignificantBits());
 | 
			
		||||
			assertEquals(lsb, ulid0.getLeastSignificantBits());
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void testConstructorString() {
 | 
			
		||||
		for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
 | 
			
		||||
	public void testConstructorTimeAndRandom() {
 | 
			
		||||
		Random random = new Random();
 | 
			
		||||
			final long random1 = random.nextLong();
 | 
			
		||||
			final long random2 = random.nextLong();
 | 
			
		||||
			Ulid ulid0 = new Ulid(random1, random2);
 | 
			
		||||
 | 
			
		||||
			String string1 = toString(ulid0);
 | 
			
		||||
			Ulid struct1 = Ulid.of(string1); // <-- under test
 | 
			
		||||
			assertEquals(ulid0, struct1);
 | 
			
		||||
		for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
 | 
			
		||||
			final long msb = random.nextLong();
 | 
			
		||||
			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
 | 
			
		||||
	public void testConstructorUuid() {
 | 
			
		||||
		for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
 | 
			
		||||
	public void testFromStrings() {
 | 
			
		||||
		Random random = new Random();
 | 
			
		||||
		for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
 | 
			
		||||
			final long msb = random.nextLong();
 | 
			
		||||
			final long lsb = random.nextLong();
 | 
			
		||||
			final UUID uuid0 = new UUID(msb, lsb);
 | 
			
		||||
			Ulid struct0 = Ulid.of(uuid0); // <-- under test
 | 
			
		||||
 | 
			
		||||
			UUID uuid1 = toUuid(struct0);
 | 
			
		||||
			assertEquals(uuid0, uuid1);
 | 
			
		||||
			Ulid ulid0 = new Ulid(msb, lsb);
 | 
			
		||||
			String string0 = toString(ulid0);
 | 
			
		||||
			Ulid ulid1 = Ulid.from(string0); // <- test Ulid.from(String)
 | 
			
		||||
			assertEquals(ulid0, ulid1);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void testToString() {
 | 
			
		||||
 | 
			
		||||
		for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
 | 
			
		||||
			UUID uuid0 = UUID.randomUUID();
 | 
			
		||||
			String string0 = toString(uuid0);
 | 
			
		||||
			String string1 = Ulid.from(uuid0).toString(); // <- test Ulid.toString()
 | 
			
		||||
			assertEquals(string0, string1);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void testToUpperCase() {
 | 
			
		||||
		Random random = new Random();
 | 
			
		||||
			final long random1 = random.nextLong();
 | 
			
		||||
			final long random2 = random.nextLong();
 | 
			
		||||
			Ulid ulid0 = new Ulid(random1, random2);
 | 
			
		||||
		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 string2 = ulid0.toString(); // <-- under test
 | 
			
		||||
			String string2 = ulid0.toUpperCase(); // <- test Ulid.toUpperCase()
 | 
			
		||||
			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
 | 
			
		||||
	public void testToUuid() {
 | 
			
		||||
 | 
			
		||||
		for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
 | 
			
		||||
		Random random = new Random();
 | 
			
		||||
		for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
 | 
			
		||||
 | 
			
		||||
			final long random1 = random.nextLong();
 | 
			
		||||
			final long random2 = random.nextLong();
 | 
			
		||||
			Ulid ulid0 = new Ulid(random1, random2);
 | 
			
		||||
 | 
			
		||||
			UUID uuid1 = toUuid(ulid0);
 | 
			
		||||
			UUID uuid2 = ulid0.toUuid(); // <-- under test
 | 
			
		||||
			UUID uuid2 = ulid0.toUuid(); // <-- test Ulid.toUuid()
 | 
			
		||||
			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 random2 = 0;
 | 
			
		||||
 | 
			
		||||
		String tm = string.substring(0, 10);
 | 
			
		||||
		String r1 = string.substring(10, 18);
 | 
			
		||||
		String r2 = string.substring(18, 26);
 | 
			
		||||
		String tm = string.substring(0, 10).toUpperCase();
 | 
			
		||||
		String r1 = string.substring(10, 18).toUpperCase();
 | 
			
		||||
		String r2 = string.substring(18, 26).toUpperCase();
 | 
			
		||||
 | 
			
		||||
		tm = transliterate(tm, ALPHABET_CROCKFORD, ALPHABET_JAVA);
 | 
			
		||||
		r1 = transliterate(r1, ALPHABET_CROCKFORD, ALPHABET_JAVA);
 | 
			
		||||
| 
						 | 
				
			
			@ -194,40 +527,6 @@ public class UlidTest {
 | 
			
		|||
		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) {
 | 
			
		||||
		char[] output = string.toCharArray();
 | 
			
		||||
		for (int i = 0; i < output.length; i++) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,7 @@ package com.github.f4b6a3.ulid;
 | 
			
		|||
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
	// ULID Spec creator
 | 
			
		||||
	private UlidSpecCreator creator;
 | 
			
		||||
	// ULID Spec factory
 | 
			
		||||
	private UlidFactory factory;
 | 
			
		||||
 | 
			
		||||
	private long time = System.currentTimeMillis(); // fixed timestamp
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -32,12 +32,12 @@ public class UniquenessTest {
 | 
			
		|||
	 * 
 | 
			
		||||
	 * @param threadCount
 | 
			
		||||
	 * @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.requestCount = requestCount;
 | 
			
		||||
		this.creator = creator;
 | 
			
		||||
		this.factory = factory;
 | 
			
		||||
		this.verbose = progress;
 | 
			
		||||
		this.initCache();
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -90,13 +90,13 @@ public class UniquenessTest {
 | 
			
		|||
 | 
			
		||||
			for (int i = 0; i < max; i++) {
 | 
			
		||||
 | 
			
		||||
				// Request a UUID
 | 
			
		||||
				Ulid ulid = creator.create(time);
 | 
			
		||||
				// Request a ULID
 | 
			
		||||
				Ulid ulid = factory.create(time);
 | 
			
		||||
 | 
			
		||||
				if (verbose) {
 | 
			
		||||
					if (i % (max / 100) == 0) {
 | 
			
		||||
						// Calculate and show progress
 | 
			
		||||
						progress = (int) ((i * 1.0 / max) * 100);
 | 
			
		||||
					if (progress % 10 == 0) {
 | 
			
		||||
						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) {
 | 
			
		||||
		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();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,6 +31,7 @@ import org.openjdk.jmh.runner.RunnerException;
 | 
			
		|||
import org.openjdk.jmh.runner.options.Options;
 | 
			
		||||
import org.openjdk.jmh.runner.options.OptionsBuilder;
 | 
			
		||||
 | 
			
		||||
import com.github.f4b6a3.ulid.Ulid;
 | 
			
		||||
import com.github.f4b6a3.ulid.UlidCreator;
 | 
			
		||||
 | 
			
		||||
@Threads(1)
 | 
			
		||||
| 
						 | 
				
			
			@ -42,47 +43,47 @@ public class Benchmarks {
 | 
			
		|||
	@Benchmark
 | 
			
		||||
	@BenchmarkMode(Mode.Throughput)
 | 
			
		||||
	@OutputTimeUnit(TimeUnit.MILLISECONDS)
 | 
			
		||||
	public String getUlidStringThroughput() {
 | 
			
		||||
		return UlidCreator.getUlidString();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Benchmark
 | 
			
		||||
	@BenchmarkMode(Mode.AverageTime)
 | 
			
		||||
	@OutputTimeUnit(TimeUnit.NANOSECONDS)
 | 
			
		||||
	public String getUlidStringAverage() {
 | 
			
		||||
		return UlidCreator.getUlidString();
 | 
			
		||||
	public UUID getUuid() {
 | 
			
		||||
		return UUID.randomUUID();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Benchmark
 | 
			
		||||
	@BenchmarkMode(Mode.Throughput)
 | 
			
		||||
	@OutputTimeUnit(TimeUnit.MILLISECONDS)
 | 
			
		||||
	public UUID getUlidThroughput() {
 | 
			
		||||
		return UlidCreator.getUlid();
 | 
			
		||||
	public String getUuidString() {
 | 
			
		||||
		return UUID.randomUUID().toString();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Benchmark
 | 
			
		||||
	@BenchmarkMode(Mode.AverageTime)
 | 
			
		||||
	@OutputTimeUnit(TimeUnit.NANOSECONDS)
 | 
			
		||||
	public UUID getUlidAverage() {
 | 
			
		||||
	@BenchmarkMode(Mode.Throughput)
 | 
			
		||||
	@OutputTimeUnit(TimeUnit.MILLISECONDS)
 | 
			
		||||
	public Ulid getUlid() {
 | 
			
		||||
		return UlidCreator.getUlid();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Benchmark
 | 
			
		||||
	@BenchmarkMode(Mode.Throughput)
 | 
			
		||||
	@OutputTimeUnit(TimeUnit.MILLISECONDS)
 | 
			
		||||
	public UUID getRandomUUIDThroughput() {
 | 
			
		||||
		return UUID.randomUUID();
 | 
			
		||||
	public String getUlidString() {
 | 
			
		||||
		return UlidCreator.getUlid().toString();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Benchmark
 | 
			
		||||
	@BenchmarkMode(Mode.AverageTime)
 | 
			
		||||
	@OutputTimeUnit(TimeUnit.NANOSECONDS)
 | 
			
		||||
	public UUID getRandomUUIDAverage() {
 | 
			
		||||
		return UUID.randomUUID();
 | 
			
		||||
	@BenchmarkMode(Mode.Throughput)
 | 
			
		||||
	@OutputTimeUnit(TimeUnit.MILLISECONDS)
 | 
			
		||||
	public Ulid getMonotonicUlid() {
 | 
			
		||||
		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 {
 | 
			
		||||
		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();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,8 +8,9 @@ import com.github.f4b6a3.ulid.UlidCreator;
 | 
			
		|||
import static org.junit.Assert.*;
 | 
			
		||||
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.Random;
 | 
			
		||||
 | 
			
		||||
public class UlidSpecCreatorTest extends AbstractUlidSpecCreatorTest {
 | 
			
		||||
public class DefaultUlidFactoryTest extends UlidFactoryTest {
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void testGetUlid() {
 | 
			
		||||
| 
						 | 
				
			
			@ -65,7 +66,9 @@ public class UlidSpecCreatorTest extends AbstractUlidSpecCreatorTest {
 | 
			
		|||
 | 
			
		||||
		// Instantiate and start many threads
 | 
			
		||||
		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();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -9,8 +9,9 @@ import static org.junit.Assert.*;
 | 
			
		|||
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.Random;
 | 
			
		||||
 | 
			
		||||
public class MonotonicUlidSpecCreatorTest extends AbstractUlidSpecCreatorTest {
 | 
			
		||||
public class MonotonicUlidFactoryTest extends UlidFactoryTest {
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void testGetUlid() {
 | 
			
		||||
| 
						 | 
				
			
			@ -76,7 +77,9 @@ public class MonotonicUlidSpecCreatorTest extends AbstractUlidSpecCreatorTest {
 | 
			
		|||
 | 
			
		||||
		// Instantiate and start many threads
 | 
			
		||||
		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();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -5,7 +5,7 @@ import java.util.Random;
 | 
			
		|||
import java.util.Set;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
public abstract class AbstractUlidSpecCreatorTest {
 | 
			
		||||
public abstract class UlidFactoryTest {
 | 
			
		||||
 | 
			
		||||
	protected static final int DEFAULT_LOOP_MAX = 10_000;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -28,10 +28,10 @@ public abstract class AbstractUlidSpecCreatorTest {
 | 
			
		|||
	protected static class TestThread extends Thread {
 | 
			
		||||
 | 
			
		||||
		public static Set<UUID> hashSet = new HashSet<>();
 | 
			
		||||
		private UlidSpecCreator creator;
 | 
			
		||||
		private UlidFactory creator;
 | 
			
		||||
		private int loopLimit;
 | 
			
		||||
 | 
			
		||||
		public TestThread(UlidSpecCreator creator, int loopLimit) {
 | 
			
		||||
		public TestThread(UlidFactory creator, int loopLimit) {
 | 
			
		||||
			this.creator = creator;
 | 
			
		||||
			this.loopLimit = loopLimit;
 | 
			
		||||
		}
 | 
			
		||||
		Loading…
	
		Reference in New Issue