Rewrite docs #21

This commit is contained in:
Fabio Lima 2022-09-17 09:45:57 -03:00
parent aad72e2f11
commit fbe1338ce9
11 changed files with 321 additions and 215 deletions

7
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

6
docs/README.md Normal file
View File

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

14
docs/run.sh Executable file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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