Rewrite docs #21
This commit is contained in:
parent
aad72e2f11
commit
fbe1338ce9
|
@ -41,6 +41,11 @@ buildNumber.properties
|
|||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
.idea
|
||||
*.iml
|
||||
|
||||
notes/*
|
||||
|
||||
docs/javadoc/*
|
||||
|
||||
|
||||
|
|
|
@ -6,9 +6,13 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
Nothing unreleased.
|
||||
|
||||
## [5.0.2] - 2022-09-17
|
||||
|
||||
Rewrite docs. #21
|
||||
|
||||
## [5.0.1] - 2022-08-21
|
||||
|
||||
Optimized comparison and hash. #20
|
||||
Optimize comparison and hash. #20
|
||||
|
||||
## [5.0.0] - 2022-07-09
|
||||
|
||||
|
@ -294,7 +298,8 @@ Project created as an alternative Java implementation of [ULID spec](https://git
|
|||
- Added `LICENSE`
|
||||
- Added test cases
|
||||
|
||||
[unreleased]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-5.0.1...HEAD
|
||||
[unreleased]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-5.0.2...HEAD
|
||||
[5.0.2]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-5.0.0...ulid-creator-5.0.2
|
||||
[5.0.1]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-5.0.0...ulid-creator-5.0.1
|
||||
[5.0.0]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-4.2.1...ulid-creator-5.0.0
|
||||
[4.2.1]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-4.2.0...ulid-creator-4.2.1
|
||||
|
|
31
README.md
31
README.md
|
@ -13,7 +13,11 @@ In summary:
|
|||
* String format is encoded to [Crockford's base32](https://www.crockford.com/base32.html);
|
||||
* String format is URL safe, is case insensitive, and has no hyphens.
|
||||
|
||||
This library contains a good amount of [unit tests](https://github.com/f4b6a3/ulid-creator/tree/master/src/test/java/com/github/f4b6a3/ulid). It also has a [micro benchmark](https://github.com/f4b6a3/ulid-creator/tree/master/benchmark) for you to check if the performance is good enough.
|
||||
This project contains a [micro benchmark](https://github.com/f4b6a3/ulid-creator/tree/master/benchmark) and a good amount of [unit tests](https://github.com/f4b6a3/ulid-creator/tree/master/src/test/java/com/github/f4b6a3/ulid).
|
||||
|
||||
The jar file can be downloaded directly from [maven.org](https://repo1.maven.org/maven2/com/github/f4b6a3/ulid-creator/).
|
||||
|
||||
Read the [Javadocs](https://javadoc.io/doc/com.github.f4b6a3/ulid-creator).
|
||||
|
||||
How to Use
|
||||
------------------------------------------------------
|
||||
|
@ -39,7 +43,7 @@ Add these lines to your `pom.xml`.
|
|||
<dependency>
|
||||
<groupId>com.github.f4b6a3</groupId>
|
||||
<artifactId>ulid-creator</artifactId>
|
||||
<version>5.0.1</version>
|
||||
<version>5.0.2</version>
|
||||
</dependency>
|
||||
```
|
||||
See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator).
|
||||
|
@ -191,9 +195,9 @@ A `UlidFactory` with `java.util.Random`:
|
|||
```java
|
||||
// use a `java.util.Random` instance for fast generation
|
||||
UlidFactory factory = UlidFactory.newInstance(new Random());
|
||||
Ulid ulid = factory.create();
|
||||
|
||||
// use the factory
|
||||
Ulid ulid = factory.create();
|
||||
```
|
||||
|
||||
---
|
||||
|
@ -243,26 +247,26 @@ Ulid ulid = factory.create();
|
|||
Benchmark
|
||||
------------------------------------------------------
|
||||
|
||||
This section shows benchmarks comparing `UlidCreator` to `java.util.UUID`.
|
||||
This section shows benchmarks comparing `UlidCreator` to `UUID.randomUUID()`.
|
||||
|
||||
```
|
||||
--------------------------------------------------------------------------------
|
||||
THROUGHPUT (operations/msec) Mode Cnt Score Error Units
|
||||
THROUGHPUT (operations/msec) Mode Cnt Score Error Units
|
||||
--------------------------------------------------------------------------------
|
||||
UUID_randomUUID thrpt 5 2060,570 ± 37,242 ops/ms
|
||||
UUID_randomUUID_toString thrpt 5 1177,386 ± 39,803 ops/ms
|
||||
UUID_randomUUID thrpt 5 3459,889 ± 98,257 ops/ms
|
||||
UUID_randomUUID_toString thrpt 5 3148,298 ± 159,507 ops/ms
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
UlidCreator_getUlid thrpt 5 2740,609 ± 86,350 ops/ms
|
||||
UlidCreator_getUlid_toString thrpt 5 2526,284 ± 56,726 ops/ms
|
||||
UlidCreator_getUlid thrpt 5 4276,614 ± 11,069 ops/ms
|
||||
UlidCreator_getUlid_toString thrpt 5 3645,088 ± 85,478 ops/ms
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
UlidCreator_getMonotonicUlid thrpt 5 19373,150 ± 192,402 ops/ms
|
||||
UlidCreator_getMonotonicUlid_toString thrpt 5 13269,201 ± 254,953 ops/ms
|
||||
UlidCreator_getMonotonicUlid thrpt 5 32921,698 ± 1286,983 ops/ms
|
||||
UlidCreator_getMonotonicUlid_toString thrpt 5 18541,252 ± 710,281 ops/ms
|
||||
--------------------------------------------------------------------------------
|
||||
Total time: 00:08:01
|
||||
Total time: 00:02:01
|
||||
--------------------------------------------------------------------------------
|
||||
```
|
||||
|
||||
System: JVM 8, Ubuntu 20.04, CPU i5-3330, 8G RAM.
|
||||
System: CPU i7-8565U, 16G RAM, Ubuntu 22.04, JVM 11, rng-tools installed.
|
||||
|
||||
To execute the benchmark, run `./benchmark/run.sh`.
|
||||
|
||||
|
@ -279,3 +283,4 @@ License
|
|||
------------------------------------------------------
|
||||
|
||||
This library is Open Source software released under the [MIT license](https://opensource.org/licenses/MIT).
|
||||
|
||||
|
|
|
@ -21,9 +21,9 @@ import com.github.f4b6a3.ulid.UlidCreator;
|
|||
@Fork(1)
|
||||
@Threads(1)
|
||||
@State(Scope.Benchmark)
|
||||
@BenchmarkMode(Mode.Throughput)
|
||||
@Warmup(iterations = 5, time = 1)
|
||||
@Measurement(iterations = 5, time = 3)
|
||||
@BenchmarkMode(Mode.Throughput)
|
||||
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
||||
public class Throughput {
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
To generate the javadocs, run the script `doc/run.sh`.
|
||||
|
||||
The docs are generated inside of `docs/javadoc`.
|
||||
|
||||
It can be useful to read offline.
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/bash
|
||||
|
||||
# find the script folder
|
||||
SCRIPT_DIR=$(dirname "$0")
|
||||
|
||||
# go to the parent folder
|
||||
cd "${SCRIPT_DIR}/.."
|
||||
|
||||
# clear old docs
|
||||
rm -rf docs/javadoc
|
||||
|
||||
# generate new docs
|
||||
find src/main/java/com/github/f4b6a3/ulid/ -name "*.java" | xargs javadoc -d docs/javadoc
|
||||
|
2
pom.xml
2
pom.xml
|
@ -8,7 +8,7 @@
|
|||
|
||||
<name>ulid-creator</name>
|
||||
<url>http://github.com/f4b6a3/ulid-creator</url>
|
||||
<description>A Java library for generating and handling ULIDs (Universally Unique Lexicographically Sortable Identifier).</description>
|
||||
<description>A Java library for generating ULIDs (Universally Unique Lexicographically Sortable Identifier).</description>
|
||||
|
||||
|
||||
<licenses>
|
||||
|
|
|
@ -29,17 +29,22 @@ import java.time.Instant;
|
|||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* This class represents a ULID.
|
||||
* A class that represents ULIDs.
|
||||
* <p>
|
||||
* ULID is 128-bit value that has two components:
|
||||
* <ul>
|
||||
* <li><b>Time component</b>: a number of milliseconds since 1970-01-01 (Unix
|
||||
* epoch).
|
||||
* <li><b>Random component</b>: a sequence of 80 random bits generated by a
|
||||
* secure random generator.
|
||||
* </ul>
|
||||
* <p>
|
||||
* ULID has 128-bit compatibility with {@link UUID}. Like a UUID, a ULID can
|
||||
* also be stored as a 16-byte array.
|
||||
* <p>
|
||||
* Instances of this class are <b>immutable</b>.
|
||||
*
|
||||
* 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.
|
||||
* @see <a href="https://github.com/ulid/spec">ULID Specification</a>
|
||||
*/
|
||||
public final class Ulid implements Serializable, Comparable<Ulid> {
|
||||
|
||||
|
@ -48,12 +53,30 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
|||
private final long msb; // most significant bits
|
||||
private final long lsb; // least significant bits
|
||||
|
||||
/**
|
||||
* Number of characters of a ULID.
|
||||
*/
|
||||
public static final int ULID_CHARS = 26;
|
||||
/**
|
||||
* Number of characters of the time component of a ULID.
|
||||
*/
|
||||
public static final int TIME_CHARS = 10;
|
||||
/**
|
||||
* Number of characters of the random component of a ULID.
|
||||
*/
|
||||
public static final int RANDOM_CHARS = 16;
|
||||
|
||||
/**
|
||||
* Number of bytes of a ULID.
|
||||
*/
|
||||
public static final int ULID_BYTES = 16;
|
||||
/**
|
||||
* Number of bytes of the time component of a ULID.
|
||||
*/
|
||||
public static final int TIME_BYTES = 6;
|
||||
/**
|
||||
* Number of bytes of the random component of a ULID.
|
||||
*/
|
||||
public static final int RANDOM_BYTES = 10;
|
||||
|
||||
private static final char[] ALPHABET_UPPERCASE = //
|
||||
|
@ -142,8 +165,8 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
|||
private static final long INCREMENT_OVERFLOW = 0x0000000000000000L;
|
||||
|
||||
/**
|
||||
* Create a new ULID.
|
||||
*
|
||||
* Creates a new ULID.
|
||||
* <p>
|
||||
* Useful to make copies of ULIDs.
|
||||
*
|
||||
* @param ulid a ULID
|
||||
|
@ -154,7 +177,10 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Create a new ULID.
|
||||
* Creates a new ULID.
|
||||
* <p>
|
||||
* If you want to make a copy of a {@link UUID}, use {@link Ulid#from(UUID)}
|
||||
* instead.
|
||||
*
|
||||
* @param mostSignificantBits the first 8 bytes as a long value
|
||||
* @param leastSignificantBits the last 8 bytes as a long value
|
||||
|
@ -165,10 +191,17 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Create a new ULID.
|
||||
* Creates a new ULID.
|
||||
* <p>
|
||||
* Time parameter is the number of milliseconds since 1970-01-01 (Unix epoch).
|
||||
* It must be a positive number not larger than 2^48-1.
|
||||
* <p>
|
||||
* Random parameter must be an array of 10 bytes.
|
||||
*
|
||||
* @param time the time component in milliseconds since 1970-01-01
|
||||
* @param random the random component in byte array
|
||||
* @param time the the number of milliseconds since 1970-01-01
|
||||
* @param random an array of 10 bytes
|
||||
* @throws IllegalArgumentException if time is negative or larger than 2^48-1
|
||||
* @throws IllegalArgumentException if random is null or its length is not 10
|
||||
*/
|
||||
public Ulid(long time, byte[] random) {
|
||||
|
||||
|
@ -177,7 +210,7 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
|||
// 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!
|
||||
throw new IllegalArgumentException("Invalid time value"); // overflow or negative time!
|
||||
}
|
||||
// The random component has 80 bits (10 bytes).
|
||||
if (random == null || random.length != RANDOM_BYTES) {
|
||||
|
@ -217,8 +250,9 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
|||
/**
|
||||
* Converts a byte array into a ULID.
|
||||
*
|
||||
* @param bytes a byte array
|
||||
* @param bytes an array of 16 bytes
|
||||
* @return a ULID
|
||||
* @throws IllegalArgumentException if bytes are null or its length is not 16
|
||||
*/
|
||||
public static Ulid from(byte[] bytes) {
|
||||
|
||||
|
@ -252,14 +286,16 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
|||
|
||||
/**
|
||||
* Converts a canonical string into a ULID.
|
||||
*
|
||||
* <p>
|
||||
* The input string must be 26 characters long and must contain only characters
|
||||
* from Crockford's base 32 alphabet.
|
||||
*
|
||||
* <p>
|
||||
* The first character of the input string must be between 0 and 7.
|
||||
*
|
||||
* @param string a canonical string
|
||||
* @return a ULID
|
||||
* @throws IllegalArgumentException if the input string is invalid
|
||||
* @see <a href="https://www.crockford.com/base32.html">Crockford's Base 32</a>
|
||||
*/
|
||||
public static Ulid from(String string) {
|
||||
|
||||
|
@ -306,8 +342,10 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
|||
|
||||
/**
|
||||
* Convert the ULID into a UUID.
|
||||
*
|
||||
* If you need a RFC-4122 UUID v4 do this: {@code Ulid.toRfc4122().toUuid()}.
|
||||
* <p>
|
||||
* A ULID has 128-bit compatibility with a {@link UUID}.
|
||||
* <p>
|
||||
* If you need a RFC-4122 UUIDv4 do this: {@code Ulid.toRfc4122().toUuid()}.
|
||||
*
|
||||
* @return a UUID.
|
||||
*/
|
||||
|
@ -347,16 +385,15 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
|||
|
||||
/**
|
||||
* Converts the ULID into a canonical string in upper case.
|
||||
*
|
||||
* <p>
|
||||
* The output string is 26 characters long and contains only characters from
|
||||
* Crockford's base 32 alphabet.
|
||||
*
|
||||
* Crockford's Base 32 alphabet.
|
||||
* <p>
|
||||
* For lower case string, use the shorthand {@code Ulid#toLowerCase()}, instead
|
||||
* of {@code Ulid#toString()#toLowerCase()}.
|
||||
*
|
||||
* See: https://www.crockford.com/base32.html
|
||||
*
|
||||
* @return a ULID string
|
||||
* @see <a href="https://www.crockford.com/base32.html">Crockford's Base 32</a>
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
@ -365,47 +402,32 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
|||
|
||||
/**
|
||||
* Converts the ULID into a canonical string in lower case.
|
||||
*
|
||||
* <p>
|
||||
* The output string is 26 characters long and contains only characters from
|
||||
* Crockford's base 32 alphabet.
|
||||
*
|
||||
* Crockford's Base 32 alphabet.
|
||||
* <p>
|
||||
* It is a shorthand at least twice as fast as
|
||||
* {@code Ulid.toString().toLowerCase()}.
|
||||
*
|
||||
* See: https://www.crockford.com/base32.html
|
||||
*
|
||||
* @return a string
|
||||
* @see <a href="https://www.crockford.com/base32.html">Crockford's Base 32</a>
|
||||
*/
|
||||
public String toLowerCase() {
|
||||
return toString(ALPHABET_LOWERCASE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the ULID into into another ULID that is compatible with UUID v4.
|
||||
*
|
||||
* Converts the ULID into into another ULID that is compatible with UUIDv4.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* If you need a RFC-4122 UUIDv4 do this: {@code Ulid.toRfc4122().toUuid()}.
|
||||
* <p>
|
||||
* <b>Note:</b> If you use this method, you can not get the original ULID, since
|
||||
* it changes 6 bits of it to generate a UUIDv4.
|
||||
*
|
||||
* @return a ULID
|
||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4122">RFC-4122</a>
|
||||
*/
|
||||
public Ulid toRfc4122() {
|
||||
|
||||
|
@ -419,10 +441,10 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
|||
|
||||
/**
|
||||
* Returns the instant of creation.
|
||||
*
|
||||
* <p>
|
||||
* The instant of creation is extracted from the time component.
|
||||
*
|
||||
* @return {@link Instant}
|
||||
* @return the {@link Instant} of creation
|
||||
*/
|
||||
public Instant getInstant() {
|
||||
return Instant.ofEpochMilli(this.getTime());
|
||||
|
@ -430,11 +452,12 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
|||
|
||||
/**
|
||||
* Returns the instant of creation.
|
||||
*
|
||||
* <p>
|
||||
* The instant of creation is extracted from the time component.
|
||||
*
|
||||
* @param string a canonical string
|
||||
* @return {@link Instant}
|
||||
* @return the {@link Instant} of creation
|
||||
* @throws IllegalArgumentException if the input string is invalid
|
||||
*/
|
||||
public static Instant getInstant(String string) {
|
||||
return Instant.ofEpochMilli(getTime(string));
|
||||
|
@ -442,11 +465,11 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
|||
|
||||
/**
|
||||
* Returns the time component as a number.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* @return a number of milliseconds
|
||||
*/
|
||||
public long getTime() {
|
||||
return this.msb >>> 16;
|
||||
|
@ -454,12 +477,13 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
|||
|
||||
/**
|
||||
* Returns the time component as a number.
|
||||
*
|
||||
* <p>
|
||||
* 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).
|
||||
*
|
||||
* @param string a canonical string
|
||||
* @return a number of milliseconds.
|
||||
* @return a number of milliseconds
|
||||
* @throws IllegalArgumentException if the input string is invalid
|
||||
*/
|
||||
public static long getTime(String string) {
|
||||
|
||||
|
@ -483,7 +507,7 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
|||
|
||||
/**
|
||||
* Returns the random component as a byte array.
|
||||
*
|
||||
* <p>
|
||||
* The random component is an array of 10 bytes (80 bits).
|
||||
*
|
||||
* @return a byte array
|
||||
|
@ -509,11 +533,12 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
|||
|
||||
/**
|
||||
* Returns the random component as a byte array.
|
||||
*
|
||||
* <p>
|
||||
* The random component is an array of 10 bytes (80 bits).
|
||||
*
|
||||
* @param string a canonical string
|
||||
* @return a byte array
|
||||
* @throws IllegalArgumentException if the input string is invalid
|
||||
*/
|
||||
public static byte[] getRandom(String string) {
|
||||
|
||||
|
@ -577,19 +602,20 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
|||
|
||||
/**
|
||||
* Returns a new ULID by incrementing the random component of the current ULID.
|
||||
*
|
||||
* <p>
|
||||
* 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 within a single millisecond interval,
|
||||
* considering an unrealistic rate of 1,000,000,000 ULIDs per millisecond.
|
||||
*
|
||||
* <ul>
|
||||
* <li>(1) This method can generate up to 1208925819614629174706176 (2^80) ULIDs
|
||||
* per millisecond;
|
||||
* <li>(2) This method can generate monotonic increasing ULIDs
|
||||
* 99.999999999999992% ((2^80 - 10^9) / (2^80)) of the time within a single
|
||||
* millisecond interval, considering an unrealistic rate of 1,000,000,000 ULIDs
|
||||
* per millisecond.
|
||||
* </ul>
|
||||
* <p>
|
||||
* Due to (1) and (2), it does not throw the error message recommended by the
|
||||
* specification. When an overflow occurs in the random 80 bits, the time
|
||||
* component is simply incremented.
|
||||
* component is simply incremented to <b>maintain monotonicity</b>.
|
||||
*
|
||||
* @return a ULID
|
||||
*/
|
||||
|
@ -607,39 +633,56 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
|||
|
||||
/**
|
||||
* Checks if the input string is valid.
|
||||
*
|
||||
* <p>
|
||||
* The input string must be 26 characters long and must contain only characters
|
||||
* from Crockford's base 32 alphabet.
|
||||
*
|
||||
* <p>
|
||||
* The first character of the input string must be between 0 and 7.
|
||||
*
|
||||
* @param string a string
|
||||
* @return true if valid
|
||||
* @param string a canonical string
|
||||
* @return true if the input string is valid
|
||||
* @see <a href="https://www.crockford.com/base32.html">Crockford's Base 32</a>
|
||||
*/
|
||||
public static boolean isValid(String string) {
|
||||
return string != null && isValidCharArray(string.toCharArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hash code value for the ULID.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final long bits = msb ^ lsb;
|
||||
return (int) (bits ^ (bits >>> 32));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if some other ULID is equal to this one.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null)
|
||||
public boolean equals(Object other) {
|
||||
if (other == null)
|
||||
return false;
|
||||
if (obj.getClass() != Ulid.class)
|
||||
if (other.getClass() != Ulid.class)
|
||||
return false;
|
||||
Ulid that = (Ulid) obj;
|
||||
Ulid that = (Ulid) other;
|
||||
if (lsb != that.lsb)
|
||||
return false;
|
||||
if (msb != that.msb)
|
||||
else if (msb != that.msb)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two ULIDs as unsigned 128-bit integers.
|
||||
* <p>
|
||||
* The first of two ULIDs is greater than the second if the most significant
|
||||
* byte in which they differ is greater for the first UUID.
|
||||
*
|
||||
* @param that a ULID to be compared with
|
||||
* @return -1, 0 or 1 as {@code this} is less than, equal to, or greater than
|
||||
* {@code that}
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(Ulid that) {
|
||||
|
||||
|
@ -665,7 +708,7 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
|||
return 0;
|
||||
}
|
||||
|
||||
protected String toString(char[] alphabet) {
|
||||
String toString(char[] alphabet) {
|
||||
|
||||
final char[] chars = new char[ULID_CHARS];
|
||||
|
||||
|
@ -705,7 +748,7 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
|||
return new String(chars);
|
||||
}
|
||||
|
||||
protected static char[] toCharArray(String string) {
|
||||
static char[] toCharArray(String string) {
|
||||
char[] chars = string == null ? null : string.toCharArray();
|
||||
if (!isValidCharArray(chars)) {
|
||||
throw new IllegalArgumentException(String.format("Invalid ULID: \"%s\"", string));
|
||||
|
@ -713,18 +756,15 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
|||
return chars;
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* Checks if the string is a valid ULID.
|
||||
*
|
||||
* A valid ULID string is a sequence of 26 characters from Crockford's base 32
|
||||
* A valid ULID string is a sequence of 26 characters from Crockford's Base 32
|
||||
* alphabet.
|
||||
*
|
||||
* The first character of the input string must be between 0 and 7.
|
||||
*
|
||||
* @param chars a char array
|
||||
* @return boolean true if valid
|
||||
*/
|
||||
protected static boolean isValidCharArray(final char[] chars) {
|
||||
static boolean isValidCharArray(final char[] chars) {
|
||||
|
||||
if (chars == null || chars.length != ULID_CHARS) {
|
||||
return false; // null or wrong size!
|
||||
|
|
|
@ -25,17 +25,10 @@
|
|||
package com.github.f4b6a3.ulid;
|
||||
|
||||
/**
|
||||
* A class for generating ULIDs.
|
||||
*
|
||||
* The ULID has two components:
|
||||
*
|
||||
* - Time component: a part of 48 bits that represent the number 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.
|
||||
* A class that generates ULIDs.
|
||||
* <p>
|
||||
* Both types of ULID can be easily created by this generator, i.e. monotonic
|
||||
* and non-monotonic.
|
||||
*/
|
||||
public final class UlidCreator {
|
||||
|
||||
|
@ -45,8 +38,6 @@ public final class UlidCreator {
|
|||
/**
|
||||
* Returns a ULID.
|
||||
*
|
||||
* The random component is always reset to a new random value.
|
||||
*
|
||||
* @return a ULID
|
||||
*/
|
||||
public static Ulid getUlid() {
|
||||
|
@ -56,9 +47,7 @@ public final class UlidCreator {
|
|||
/**
|
||||
* Returns a ULID with a given time.
|
||||
*
|
||||
* The time must be the number of milliseconds since 1970-01-01 (Unix epoch).
|
||||
*
|
||||
* @param time a given time
|
||||
* @param time a number of milliseconds since 1970-01-01 (Unix epoch).
|
||||
* @return a ULID
|
||||
*/
|
||||
public static Ulid getUlid(final long time) {
|
||||
|
@ -68,11 +57,6 @@ public final class UlidCreator {
|
|||
/**
|
||||
* Returns a Monotonic ULID.
|
||||
*
|
||||
* The random component is reset to a new value whenever the time changes.
|
||||
*
|
||||
* If more than one ULID is generated within the same time, the random component
|
||||
* is incremented by one.
|
||||
*
|
||||
* @return a ULID
|
||||
*/
|
||||
public static Ulid getMonotonicUlid() {
|
||||
|
@ -82,14 +66,7 @@ public final class UlidCreator {
|
|||
/**
|
||||
* Returns a Monotonic ULID with a given time.
|
||||
*
|
||||
* The time must be the number of milliseconds since 1970-01-01 (Unix epoch).
|
||||
*
|
||||
* The random component is reset to a new value whenever the time changes.
|
||||
*
|
||||
* If more than one ULID is generated within the same time, the random component
|
||||
* is incremented by one.
|
||||
*
|
||||
* @param time a given time
|
||||
* @param time a number of milliseconds since 1970-01-01 (Unix epoch).
|
||||
* @return a ULID
|
||||
*/
|
||||
public static Ulid getMonotonicUlid(final long time) {
|
||||
|
|
|
@ -32,14 +32,19 @@ import java.util.function.LongFunction;
|
|||
import java.util.function.LongSupplier;
|
||||
|
||||
/**
|
||||
* Factory that generates ULIDs.
|
||||
*
|
||||
* If the factory is not monotonic, the random component always changes.
|
||||
*
|
||||
* If the factory is monotonic, the random component changes whenever the
|
||||
* millisecond changes. If more than one ULID is generated within the same
|
||||
* millisecond, the random component is incremented by one.
|
||||
*
|
||||
* A class that actually generates ULIDs.
|
||||
* <p>
|
||||
* This class is used by {@link UlidCreator}.
|
||||
* <p>
|
||||
* You can use this class if you need to use a specific random generator
|
||||
* strategy. However, most people just need {@link UlidCreator}.
|
||||
* <p>
|
||||
* Instances of this class can behave in one of two ways: monotonic or
|
||||
* non-monotonic (default).
|
||||
* <p>
|
||||
* If the factory is monotonic, the random component is incremented by 1 If more
|
||||
* than one ULID is generated within the same millisecond.
|
||||
* <p>
|
||||
* The maximum ULIDs that can be generated per millisecond is 2^80.
|
||||
*/
|
||||
public final class UlidFactory {
|
||||
|
@ -47,8 +52,15 @@ public final class UlidFactory {
|
|||
private final Clock clock; // for tests
|
||||
private final LongFunction<Ulid> ulidFunction;
|
||||
|
||||
// ******************************
|
||||
// Constructors
|
||||
// ******************************
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public UlidFactory() {
|
||||
this(new UlidFunction());
|
||||
this(new UlidFunction(IRandom.newInstance()));
|
||||
}
|
||||
|
||||
private UlidFactory(LongFunction<Ulid> ulidFunction) {
|
||||
|
@ -62,13 +74,13 @@ public final class UlidFactory {
|
|||
|
||||
/**
|
||||
* Returns a new factory.
|
||||
*
|
||||
* <p>
|
||||
* It is equivalent to {@code new UlidFactory()}.
|
||||
*
|
||||
* @return {@link UlidFactory}
|
||||
*/
|
||||
public static UlidFactory newInstance() {
|
||||
return new UlidFactory(new UlidFunction());
|
||||
return new UlidFactory(new UlidFunction(IRandom.newInstance()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -78,31 +90,31 @@ public final class UlidFactory {
|
|||
* @return {@link UlidFactory}
|
||||
*/
|
||||
public static UlidFactory newInstance(Random random) {
|
||||
return new UlidFactory(new UlidFunction(random));
|
||||
return new UlidFactory(new UlidFunction(IRandom.newInstance(random)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new factory.
|
||||
*
|
||||
* <p>
|
||||
* The given random function must return a long value.
|
||||
*
|
||||
* @param randomFunction a random function that returns a long value
|
||||
* @return {@link UlidFactory}
|
||||
*/
|
||||
public static UlidFactory newInstance(LongSupplier randomFunction) {
|
||||
return new UlidFactory(new UlidFunction(randomFunction));
|
||||
return new UlidFactory(new UlidFunction(IRandom.newInstance(randomFunction)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new factory.
|
||||
*
|
||||
* <p>
|
||||
* The given random function must return a byte array.
|
||||
*
|
||||
* @param randomFunction a random function that returns a byte array
|
||||
* @return {@link UlidFactory}
|
||||
*/
|
||||
public static UlidFactory newInstance(IntFunction<byte[]> randomFunction) {
|
||||
return new UlidFactory(new UlidFunction(randomFunction));
|
||||
return new UlidFactory(new UlidFunction(IRandom.newInstance(randomFunction)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -111,7 +123,7 @@ public final class UlidFactory {
|
|||
* @return {@link UlidFactory}
|
||||
*/
|
||||
public static UlidFactory newMonotonicInstance() {
|
||||
return new UlidFactory(new MonotonicFunction());
|
||||
return new UlidFactory(new MonotonicFunction(IRandom.newInstance()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -121,59 +133,63 @@ public final class UlidFactory {
|
|||
* @return {@link UlidFactory}
|
||||
*/
|
||||
public static UlidFactory newMonotonicInstance(Random random) {
|
||||
return new UlidFactory(new MonotonicFunction(random));
|
||||
return new UlidFactory(new MonotonicFunction(IRandom.newInstance(random)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new monotonic factory.
|
||||
*
|
||||
* <p>
|
||||
* The given random function must return a long value.
|
||||
*
|
||||
* @param randomFunction a random function that returns a long value
|
||||
* @return {@link UlidFactory}
|
||||
*/
|
||||
public static UlidFactory newMonotonicInstance(LongSupplier randomFunction) {
|
||||
return new UlidFactory(new MonotonicFunction(randomFunction));
|
||||
return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new monotonic factory.
|
||||
*
|
||||
* <p>
|
||||
* The given random function must return a byte array.
|
||||
*
|
||||
* @param randomFunction a random function that returns a byte array
|
||||
* @return {@link UlidFactory}
|
||||
*/
|
||||
public static UlidFactory newMonotonicInstance(IntFunction<byte[]> randomFunction) {
|
||||
return new UlidFactory(new MonotonicFunction(randomFunction));
|
||||
return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new monotonic factory.
|
||||
*
|
||||
* <p>
|
||||
* The given random function must return a long value.
|
||||
*
|
||||
* @param randomFunction a random function that returns a long value
|
||||
* @param clock a custom clock instance for tests
|
||||
* @return {@link UlidFactory}
|
||||
*/
|
||||
protected static UlidFactory newMonotonicInstance(LongSupplier randomFunction, Clock clock) {
|
||||
return new UlidFactory(new MonotonicFunction(randomFunction), clock);
|
||||
static UlidFactory newMonotonicInstance(LongSupplier randomFunction, Clock clock) {
|
||||
return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction)), clock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new monotonic factory.
|
||||
*
|
||||
* <p>
|
||||
* The given random function must return a byte array.
|
||||
*
|
||||
* @param randomFunction a random function that returns a byte array
|
||||
* @param clock a custom clock instance for tests
|
||||
* @return {@link UlidFactory}
|
||||
*/
|
||||
protected static UlidFactory newMonotonicInstance(IntFunction<byte[]> randomFunction, Clock clock) {
|
||||
return new UlidFactory(new MonotonicFunction(randomFunction), clock);
|
||||
static UlidFactory newMonotonicInstance(IntFunction<byte[]> randomFunction, Clock clock) {
|
||||
return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction)), clock);
|
||||
}
|
||||
|
||||
// ******************************
|
||||
// Public methods
|
||||
// ******************************
|
||||
|
||||
/**
|
||||
* Returns a UUID.
|
||||
*
|
||||
|
@ -186,36 +202,26 @@ public final class UlidFactory {
|
|||
/**
|
||||
* Returns a UUID with a specific time.
|
||||
*
|
||||
* The time must be the number of milliseconds since 1970-01-01 (Unix epoch).
|
||||
*
|
||||
* @param time a given time
|
||||
* @param time a number of milliseconds since 1970-01-01 (Unix epoch).
|
||||
* @return a ULID
|
||||
*/
|
||||
public synchronized Ulid create(final long time) {
|
||||
return this.ulidFunction.apply(time);
|
||||
}
|
||||
|
||||
// ******************************
|
||||
// Package-private inner classes
|
||||
// ******************************
|
||||
|
||||
/**
|
||||
* Function that creates ULIDs.
|
||||
*/
|
||||
protected static final class UlidFunction implements LongFunction<Ulid> {
|
||||
static final class UlidFunction implements LongFunction<Ulid> {
|
||||
|
||||
private final IRandom random;
|
||||
|
||||
public UlidFunction() {
|
||||
this.random = new ByteRandom();
|
||||
}
|
||||
|
||||
public UlidFunction(Random random) {
|
||||
this.random = IRandom.newInstance(random);
|
||||
}
|
||||
|
||||
public UlidFunction(IntFunction<byte[]> randomFunction) {
|
||||
this.random = new ByteRandom(randomFunction);
|
||||
}
|
||||
|
||||
public UlidFunction(LongSupplier randomFunction) {
|
||||
this.random = new LongRandom(randomFunction);
|
||||
public UlidFunction(IRandom random) {
|
||||
this.random = random;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -233,9 +239,8 @@ public final class UlidFactory {
|
|||
/**
|
||||
* Function that creates Monotonic ULIDs.
|
||||
*/
|
||||
protected static final class MonotonicFunction implements LongFunction<Ulid> {
|
||||
static final class MonotonicFunction implements LongFunction<Ulid> {
|
||||
|
||||
private long lastTime;
|
||||
private Ulid lastUlid;
|
||||
|
||||
private final IRandom random;
|
||||
|
@ -245,40 +250,23 @@ public final class UlidFactory {
|
|||
// system clock jumps back by 1 second due to leap second.
|
||||
protected static final int CLOCK_DRIFT_TOLERANCE = 10_000;
|
||||
|
||||
public MonotonicFunction() {
|
||||
this(new ByteRandom());
|
||||
}
|
||||
|
||||
public MonotonicFunction(Random random) {
|
||||
this(IRandom.newInstance(random));
|
||||
}
|
||||
|
||||
public MonotonicFunction(IntFunction<byte[]> randomFunction) {
|
||||
this(new ByteRandom(randomFunction));
|
||||
}
|
||||
|
||||
public MonotonicFunction(LongSupplier randomFunction) {
|
||||
this(new LongRandom(randomFunction));
|
||||
}
|
||||
|
||||
private MonotonicFunction(IRandom random) {
|
||||
public MonotonicFunction(IRandom random) {
|
||||
this.random = random;
|
||||
|
||||
// initialize internal state
|
||||
this.lastTime = Clock.systemUTC().millis();
|
||||
this.lastUlid = new Ulid(lastTime, random.nextBytes(Ulid.RANDOM_BYTES));
|
||||
this.lastUlid = new Ulid(0L, this.random.nextBytes(Ulid.RANDOM_BYTES));
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Ulid apply(final long time) {
|
||||
|
||||
final long lastTime = lastUlid.getTime();
|
||||
|
||||
// Check if the current time is the same as the previous time or has moved
|
||||
// backwards after a small system clock adjustment or after a leap second.
|
||||
// Drift tolerance = (previous_time - 10s) < current_time <= previous_time
|
||||
if ((time > this.lastTime - CLOCK_DRIFT_TOLERANCE) && (time <= this.lastTime)) {
|
||||
this.lastUlid = lastUlid.increment();
|
||||
if ((time > lastTime - CLOCK_DRIFT_TOLERANCE) && (time <= lastTime)) {
|
||||
this.lastUlid = this.lastUlid.increment();
|
||||
} else {
|
||||
this.lastTime = time;
|
||||
if (this.random instanceof ByteRandom) {
|
||||
this.lastUlid = new Ulid(time, this.random.nextBytes(Ulid.RANDOM_BYTES));
|
||||
} else {
|
||||
|
@ -292,12 +280,16 @@ public final class UlidFactory {
|
|||
}
|
||||
}
|
||||
|
||||
protected static interface IRandom {
|
||||
static interface IRandom {
|
||||
|
||||
public long nextLong();
|
||||
|
||||
public byte[] nextBytes(int length);
|
||||
|
||||
static IRandom newInstance() {
|
||||
return new ByteRandom();
|
||||
}
|
||||
|
||||
static IRandom newInstance(Random random) {
|
||||
if (random == null) {
|
||||
return new ByteRandom();
|
||||
|
@ -309,9 +301,17 @@ public final class UlidFactory {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
static IRandom newInstance(LongSupplier randomFunction) {
|
||||
return new LongRandom(randomFunction);
|
||||
}
|
||||
|
||||
static IRandom newInstance(IntFunction<byte[]> randomFunction) {
|
||||
return new ByteRandom(randomFunction);
|
||||
}
|
||||
}
|
||||
|
||||
protected static class LongRandom implements IRandom {
|
||||
static class LongRandom implements IRandom {
|
||||
|
||||
private final LongSupplier randomFunction;
|
||||
|
||||
|
@ -344,20 +344,20 @@ public final class UlidFactory {
|
|||
shift = Long.SIZE;
|
||||
random = randomFunction.getAsLong();
|
||||
}
|
||||
shift -= Byte.SIZE; // 56, 48, 42...
|
||||
shift -= Byte.SIZE; // 56, 48, 40...
|
||||
bytes[i] = (byte) (random >>> shift);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
protected static LongSupplier newRandomFunction(Random random) {
|
||||
static LongSupplier newRandomFunction(Random random) {
|
||||
final Random entropy = random != null ? random : new SecureRandom();
|
||||
return entropy::nextLong;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class ByteRandom implements IRandom {
|
||||
static class ByteRandom implements IRandom {
|
||||
|
||||
private final IntFunction<byte[]> randomFunction;
|
||||
|
||||
|
@ -388,7 +388,7 @@ public final class UlidFactory {
|
|||
return this.randomFunction.apply(length);
|
||||
}
|
||||
|
||||
protected static IntFunction<byte[]> newRandomFunction(Random random) {
|
||||
static IntFunction<byte[]> newRandomFunction(Random random) {
|
||||
final Random entropy = random != null ? random : new SecureRandom();
|
||||
return (final int length) -> {
|
||||
final byte[] bytes = new byte[length];
|
||||
|
|
|
@ -123,6 +123,60 @@ public class UlidFactoryMonotonicTest extends UlidFactoryTest {
|
|||
assertEquals(ms1, ms2); // LEAP SECOND! DON'T MOVE BACKWARDS!
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMonotonicUlidAfterRandomBitsOverflowFollowedByTimeBitsIncrement() {
|
||||
|
||||
long time = Instant.parse("2021-12-31T23:59:59.999Z").toEpochMilli();
|
||||
long times[] = { time, time, time + 1, time + 2 };
|
||||
|
||||
Clock clock = new Clock() {
|
||||
private int i;
|
||||
|
||||
@Override
|
||||
public long millis() {
|
||||
return times[i++ % times.length];
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZoneId getZone() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Clock withZone(ZoneId zone) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant instant() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
LongSupplier randomSupplier = () -> 0xffffffffffffffffL;
|
||||
UlidFactory factory = UlidFactory.newMonotonicInstance(randomSupplier, clock);
|
||||
|
||||
Ulid ulid1 = factory.create();
|
||||
Ulid ulid2 = factory.create(); // time bits should be incremented here
|
||||
Ulid ulid3 = factory.create();
|
||||
Ulid ulid4 = factory.create();
|
||||
|
||||
assertEquals(ulid1.getTime(), time);
|
||||
assertEquals(ulid2.getTime(), time + 1); // check if time bits increment occurred
|
||||
assertEquals(ulid3.getTime(), time + 1);
|
||||
assertEquals(ulid4.getTime(), time + 2);
|
||||
|
||||
assertEquals(ulid1.getMostSignificantBits() & 0xffffL, 0xffffL);
|
||||
assertEquals(ulid2.getMostSignificantBits() & 0xffffL, 0x0000L);
|
||||
assertEquals(ulid3.getMostSignificantBits() & 0xffffL, 0x0000L);
|
||||
assertEquals(ulid4.getMostSignificantBits() & 0xffffL, 0xffffL);
|
||||
|
||||
assertEquals(ulid1.getLeastSignificantBits(), 0xffffffffffffffffL);
|
||||
assertEquals(ulid2.getLeastSignificantBits(), 0x0000000000000000L);
|
||||
assertEquals(ulid3.getLeastSignificantBits(), 0x0000000000000001L);
|
||||
assertEquals(ulid4.getLeastSignificantBits(), 0xffffffffffffffffL);
|
||||
}
|
||||
|
||||
private void checkOrdering(Ulid[] list) {
|
||||
Ulid[] other = Arrays.copyOf(list, list.length);
|
||||
Arrays.sort(other);
|
||||
|
|
Loading…
Reference in New Issue