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 # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid* 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. Nothing unreleased.
## [5.0.2] - 2022-09-17
Rewrite docs. #21
## [5.0.1] - 2022-08-21 ## [5.0.1] - 2022-08-21
Optimized comparison and hash. #20 Optimize comparison and hash. #20
## [5.0.0] - 2022-07-09 ## [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 `LICENSE`
- Added test cases - 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.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 [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 [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 encoded to [Crockford's base32](https://www.crockford.com/base32.html);
* String format is URL safe, is case insensitive, and has no hyphens. * 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 How to Use
------------------------------------------------------ ------------------------------------------------------
@ -39,7 +43,7 @@ Add these lines to your `pom.xml`.
<dependency> <dependency>
<groupId>com.github.f4b6a3</groupId> <groupId>com.github.f4b6a3</groupId>
<artifactId>ulid-creator</artifactId> <artifactId>ulid-creator</artifactId>
<version>5.0.1</version> <version>5.0.2</version>
</dependency> </dependency>
``` ```
See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator). See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator).
@ -191,9 +195,9 @@ A `UlidFactory` with `java.util.Random`:
```java ```java
// use a `java.util.Random` instance for fast generation // use a `java.util.Random` instance for fast generation
UlidFactory factory = UlidFactory.newInstance(new Random()); UlidFactory factory = UlidFactory.newInstance(new Random());
Ulid ulid = factory.create();
// use the factory // use the factory
Ulid ulid = factory.create();
``` ```
--- ---
@ -243,26 +247,26 @@ Ulid ulid = factory.create();
Benchmark 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 thrpt 5 3459,889 ± 98,257 ops/ms
UUID_randomUUID_toString thrpt 5 1177,386 ± 39,803 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 thrpt 5 4276,614 ± 11,069 ops/ms
UlidCreator_getUlid_toString thrpt 5 2526,284 ± 56,726 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 thrpt 5 32921,698 ± 1286,983 ops/ms
UlidCreator_getMonotonicUlid_toString thrpt 5 13269,201 ± 254,953 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`. 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). 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) @Fork(1)
@Threads(1) @Threads(1)
@State(Scope.Benchmark) @State(Scope.Benchmark)
@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 5, time = 1) @Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 3) @Measurement(iterations = 5, time = 3)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS) @OutputTimeUnit(TimeUnit.MILLISECONDS)
public class Throughput { 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> <name>ulid-creator</name>
<url>http://github.com/f4b6a3/ulid-creator</url> <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> <licenses>

View File

@ -29,17 +29,22 @@ import java.time.Instant;
import java.util.UUID; 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: * @see <a href="https://github.com/ulid/spec">ULID Specification</a>
*
* - Time component: a part of 48 bits that represent the amount of milliseconds
* since Unix Epoch, 1970-01-01.
*
* - Random component: a byte array of 80 bits that has a random value generated
* a secure random generator.
*
* Instances of this class are immutable.
*/ */
public final class Ulid implements Serializable, Comparable<Ulid> { public final class Ulid implements Serializable, Comparable<Ulid> {
@ -48,12 +53,30 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
private final long msb; // most significant bits private final long msb; // most significant bits
private final long lsb; // least significant bits private final long lsb; // least significant bits
/**
* Number of characters of a ULID.
*/
public static final int ULID_CHARS = 26; public static final int ULID_CHARS = 26;
/**
* Number of characters of the time component of a ULID.
*/
public static final int TIME_CHARS = 10; public static final int TIME_CHARS = 10;
/**
* Number of characters of the random component of a ULID.
*/
public static final int RANDOM_CHARS = 16; public static final int RANDOM_CHARS = 16;
/**
* Number of bytes of a ULID.
*/
public static final int ULID_BYTES = 16; public static final int ULID_BYTES = 16;
/**
* Number of bytes of the time component of a ULID.
*/
public static final int TIME_BYTES = 6; public static final int TIME_BYTES = 6;
/**
* Number of bytes of the random component of a ULID.
*/
public static final int RANDOM_BYTES = 10; public static final int RANDOM_BYTES = 10;
private static final char[] ALPHABET_UPPERCASE = // 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; private static final long INCREMENT_OVERFLOW = 0x0000000000000000L;
/** /**
* Create a new ULID. * Creates a new ULID.
* * <p>
* Useful to make copies of ULIDs. * Useful to make copies of ULIDs.
* *
* @param ulid a ULID * @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 mostSignificantBits the first 8 bytes as a long value
* @param leastSignificantBits the last 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 time the the number of milliseconds since 1970-01-01
* @param random the random component in byte array * @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) { public Ulid(long time, byte[] random) {
@ -177,7 +210,7 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
// ULID specification: // ULID specification:
// "Any attempt to decode or encode a ULID larger than this (time > 2^48-1) // "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." // 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). // The random component has 80 bits (10 bytes).
if (random == null || random.length != RANDOM_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. * Converts a byte array into a ULID.
* *
* @param bytes a byte array * @param bytes an array of 16 bytes
* @return a ULID * @return a ULID
* @throws IllegalArgumentException if bytes are null or its length is not 16
*/ */
public static Ulid from(byte[] bytes) { 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. * Converts a canonical string into a ULID.
* * <p>
* The input string must be 26 characters long and must contain only characters * The input string must be 26 characters long and must contain only characters
* from Crockford's base 32 alphabet. * from Crockford's base 32 alphabet.
* * <p>
* The first character of the input string must be between 0 and 7. * The first character of the input string must be between 0 and 7.
* *
* @param string a canonical string * @param string a canonical string
* @return a ULID * @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) { public static Ulid from(String string) {
@ -306,7 +342,9 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
/** /**
* Convert the ULID into a UUID. * Convert the ULID into a UUID.
* * <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()}. * If you need a RFC-4122 UUIDv4 do this: {@code Ulid.toRfc4122().toUuid()}.
* *
* @return a UUID. * @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. * Converts the ULID into a canonical string in upper case.
* * <p>
* The output string is 26 characters long and contains only characters from * 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 * For lower case string, use the shorthand {@code Ulid#toLowerCase()}, instead
* of {@code Ulid#toString()#toLowerCase()}. * of {@code Ulid#toString()#toLowerCase()}.
* *
* See: https://www.crockford.com/base32.html
*
* @return a ULID string * @return a ULID string
* @see <a href="https://www.crockford.com/base32.html">Crockford's Base 32</a>
*/ */
@Override @Override
public String toString() { public String toString() {
@ -365,16 +402,15 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
/** /**
* Converts the ULID into a canonical string in lower case. * Converts the ULID into a canonical string in lower case.
* * <p>
* The output string is 26 characters long and contains only characters from * 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 * It is a shorthand at least twice as fast as
* {@code Ulid.toString().toLowerCase()}. * {@code Ulid.toString().toLowerCase()}.
* *
* See: https://www.crockford.com/base32.html
*
* @return a string * @return a string
* @see <a href="https://www.crockford.com/base32.html">Crockford's Base 32</a>
*/ */
public String toLowerCase() { public String toLowerCase() {
return toString(ALPHABET_LOWERCASE); return toString(ALPHABET_LOWERCASE);
@ -382,30 +418,16 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
/** /**
* Converts the ULID into into another ULID that is compatible with UUIDv4. * 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. * The bytes of the returned ULID are compliant with the RFC-4122 version 4.
* * <p>
* If you need a RFC-4122 UUIDv4 do this: {@code Ulid.toRfc4122().toUuid()}. * If you need a RFC-4122 UUIDv4 do this: {@code Ulid.toRfc4122().toUuid()}.
* * <p>
* Read: https://tools.ietf.org/html/rfc4122 * <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.
* ### 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 * @return a ULID
* @see <a href="https://www.rfc-editor.org/rfc/rfc4122">RFC-4122</a>
*/ */
public Ulid toRfc4122() { public Ulid toRfc4122() {
@ -419,10 +441,10 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
/** /**
* Returns the instant of creation. * Returns the instant of creation.
* * <p>
* The instant of creation is extracted from the time component. * The instant of creation is extracted from the time component.
* *
* @return {@link Instant} * @return the {@link Instant} of creation
*/ */
public Instant getInstant() { public Instant getInstant() {
return Instant.ofEpochMilli(this.getTime()); return Instant.ofEpochMilli(this.getTime());
@ -430,11 +452,12 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
/** /**
* Returns the instant of creation. * Returns the instant of creation.
* * <p>
* The instant of creation is extracted from the time component. * The instant of creation is extracted from the time component.
* *
* @param string a canonical string * @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) { public static Instant getInstant(String string) {
return Instant.ofEpochMilli(getTime(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. * 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 * 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). * count of milliseconds since 1970-01-01 (Unix epoch).
* *
* @return a number of milliseconds. * @return a number of milliseconds
*/ */
public long getTime() { public long getTime() {
return this.msb >>> 16; return this.msb >>> 16;
@ -454,12 +477,13 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
/** /**
* Returns the time component as a number. * 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 * 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). * count of milliseconds since 1970-01-01 (Unix epoch).
* *
* @param string a canonical string * @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) { 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. * Returns the random component as a byte array.
* * <p>
* The random component is an array of 10 bytes (80 bits). * The random component is an array of 10 bytes (80 bits).
* *
* @return a byte array * @return a byte array
@ -509,11 +533,12 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
/** /**
* Returns the random component as a byte array. * Returns the random component as a byte array.
* * <p>
* The random component is an array of 10 bytes (80 bits). * The random component is an array of 10 bytes (80 bits).
* *
* @param string a canonical string * @param string a canonical string
* @return a byte array * @return a byte array
* @throws IllegalArgumentException if the input string is invalid
*/ */
public static byte[] getRandom(String string) { 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. * Returns a new ULID by incrementing the random component of the current ULID.
* * <p>
* Since the random component contains 80 bits: * Since the random component contains 80 bits:
* * <ul>
* (1) This method can generate up to 1208925819614629174706176 (2^80) ULIDs per * <li>(1) This method can generate up to 1208925819614629174706176 (2^80) ULIDs
* millisecond; * per millisecond;
* * <li>(2) This method can generate monotonic increasing ULIDs
* (2) This method can generate monotonic increasing ULIDs 99.999999999999992% * 99.999999999999992% ((2^80 - 10^9) / (2^80)) of the time within a single
* ((2^80 - 10^9) / (2^80)) of the time within a single millisecond interval, * millisecond interval, considering an unrealistic rate of 1,000,000,000 ULIDs
* considering an unrealistic rate of 1,000,000,000 ULIDs per millisecond. * per millisecond.
* * </ul>
* <p>
* Due to (1) and (2), it does not throw the error message recommended by the * 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 * 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 * @return a ULID
*/ */
@ -607,39 +633,56 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
/** /**
* Checks if the input string is valid. * Checks if the input string is valid.
* * <p>
* The input string must be 26 characters long and must contain only characters * The input string must be 26 characters long and must contain only characters
* from Crockford's base 32 alphabet. * from Crockford's base 32 alphabet.
* * <p>
* The first character of the input string must be between 0 and 7. * The first character of the input string must be between 0 and 7.
* *
* @param string a string * @param string a canonical string
* @return true if valid * @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) { public static boolean isValid(String string) {
return string != null && isValidCharArray(string.toCharArray()); return string != null && isValidCharArray(string.toCharArray());
} }
/**
* Returns a hash code value for the ULID.
*/
@Override @Override
public int hashCode() { public int hashCode() {
final long bits = msb ^ lsb; final long bits = msb ^ lsb;
return (int) (bits ^ (bits >>> 32)); return (int) (bits ^ (bits >>> 32));
} }
/**
* Checks if some other ULID is equal to this one.
*/
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object other) {
if (obj == null) if (other == null)
return false; return false;
if (obj.getClass() != Ulid.class) if (other.getClass() != Ulid.class)
return false; return false;
Ulid that = (Ulid) obj; Ulid that = (Ulid) other;
if (lsb != that.lsb) if (lsb != that.lsb)
return false; return false;
if (msb != that.msb) else if (msb != that.msb)
return false; return false;
return true; 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 @Override
public int compareTo(Ulid that) { public int compareTo(Ulid that) {
@ -665,7 +708,7 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
return 0; return 0;
} }
protected String toString(char[] alphabet) { String toString(char[] alphabet) {
final char[] chars = new char[ULID_CHARS]; final char[] chars = new char[ULID_CHARS];
@ -705,7 +748,7 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
return new String(chars); return new String(chars);
} }
protected static char[] toCharArray(String string) { static char[] toCharArray(String string) {
char[] chars = string == null ? null : string.toCharArray(); char[] chars = string == null ? null : string.toCharArray();
if (!isValidCharArray(chars)) { if (!isValidCharArray(chars)) {
throw new IllegalArgumentException(String.format("Invalid ULID: \"%s\"", string)); throw new IllegalArgumentException(String.format("Invalid ULID: \"%s\"", string));
@ -713,18 +756,15 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
return chars; return chars;
} }
/** /*
* Checks if the string is a valid ULID. * 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. * alphabet.
* *
* The first character of the input string must be between 0 and 7. * 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) { if (chars == null || chars.length != ULID_CHARS) {
return false; // null or wrong size! return false; // null or wrong size!

View File

@ -25,17 +25,10 @@
package com.github.f4b6a3.ulid; package com.github.f4b6a3.ulid;
/** /**
* A class for generating ULIDs. * A class that generates ULIDs.
* * <p>
* The ULID has two components: * Both types of ULID can be easily created by this generator, i.e. monotonic
* * and non-monotonic.
* - 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.
*/ */
public final class UlidCreator { public final class UlidCreator {
@ -45,8 +38,6 @@ public final class UlidCreator {
/** /**
* Returns a ULID. * Returns a ULID.
* *
* The random component is always reset to a new random value.
*
* @return a ULID * @return a ULID
*/ */
public static Ulid getUlid() { public static Ulid getUlid() {
@ -56,9 +47,7 @@ public final class UlidCreator {
/** /**
* Returns a ULID with a given time. * Returns a ULID with a given time.
* *
* The time must be the number of milliseconds since 1970-01-01 (Unix epoch). * @param time a number of milliseconds since 1970-01-01 (Unix epoch).
*
* @param time a given time
* @return a ULID * @return a ULID
*/ */
public static Ulid getUlid(final long time) { public static Ulid getUlid(final long time) {
@ -68,11 +57,6 @@ public final class UlidCreator {
/** /**
* Returns a Monotonic ULID. * 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 * @return a ULID
*/ */
public static Ulid getMonotonicUlid() { public static Ulid getMonotonicUlid() {
@ -82,14 +66,7 @@ public final class UlidCreator {
/** /**
* Returns a Monotonic ULID with a given time. * Returns a Monotonic ULID with a given time.
* *
* The time must be the number of milliseconds since 1970-01-01 (Unix epoch). * @param time a 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
* @return a ULID * @return a ULID
*/ */
public static Ulid getMonotonicUlid(final long time) { public static Ulid getMonotonicUlid(final long time) {

View File

@ -32,14 +32,19 @@ import java.util.function.LongFunction;
import java.util.function.LongSupplier; import java.util.function.LongSupplier;
/** /**
* Factory that generates ULIDs. * A class that actually generates ULIDs.
* * <p>
* If the factory is not monotonic, the random component always changes. * This class is used by {@link UlidCreator}.
* * <p>
* If the factory is monotonic, the random component changes whenever the * You can use this class if you need to use a specific random generator
* millisecond changes. If more than one ULID is generated within the same * strategy. However, most people just need {@link UlidCreator}.
* millisecond, the random component is incremented by one. * <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. * The maximum ULIDs that can be generated per millisecond is 2^80.
*/ */
public final class UlidFactory { public final class UlidFactory {
@ -47,8 +52,15 @@ public final class UlidFactory {
private final Clock clock; // for tests private final Clock clock; // for tests
private final LongFunction<Ulid> ulidFunction; private final LongFunction<Ulid> ulidFunction;
// ******************************
// Constructors
// ******************************
/**
* Default constructor.
*/
public UlidFactory() { public UlidFactory() {
this(new UlidFunction()); this(new UlidFunction(IRandom.newInstance()));
} }
private UlidFactory(LongFunction<Ulid> ulidFunction) { private UlidFactory(LongFunction<Ulid> ulidFunction) {
@ -62,13 +74,13 @@ public final class UlidFactory {
/** /**
* Returns a new factory. * Returns a new factory.
* * <p>
* It is equivalent to {@code new UlidFactory()}. * It is equivalent to {@code new UlidFactory()}.
* *
* @return {@link UlidFactory} * @return {@link UlidFactory}
*/ */
public static UlidFactory newInstance() { 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} * @return {@link UlidFactory}
*/ */
public static UlidFactory newInstance(Random random) { public static UlidFactory newInstance(Random random) {
return new UlidFactory(new UlidFunction(random)); return new UlidFactory(new UlidFunction(IRandom.newInstance(random)));
} }
/** /**
* Returns a new factory. * Returns a new factory.
* * <p>
* The given random function must return a long value. * The given random function must return a long value.
* *
* @param randomFunction a random function that returns a long value * @param randomFunction a random function that returns a long value
* @return {@link UlidFactory} * @return {@link UlidFactory}
*/ */
public static UlidFactory newInstance(LongSupplier randomFunction) { public static UlidFactory newInstance(LongSupplier randomFunction) {
return new UlidFactory(new UlidFunction(randomFunction)); return new UlidFactory(new UlidFunction(IRandom.newInstance(randomFunction)));
} }
/** /**
* Returns a new factory. * Returns a new factory.
* * <p>
* The given random function must return a byte array. * The given random function must return a byte array.
* *
* @param randomFunction a random function that returns a byte array * @param randomFunction a random function that returns a byte array
* @return {@link UlidFactory} * @return {@link UlidFactory}
*/ */
public static UlidFactory newInstance(IntFunction<byte[]> randomFunction) { 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} * @return {@link UlidFactory}
*/ */
public static UlidFactory newMonotonicInstance() { 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} * @return {@link UlidFactory}
*/ */
public static UlidFactory newMonotonicInstance(Random random) { 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. * Returns a new monotonic factory.
* * <p>
* The given random function must return a long value. * The given random function must return a long value.
* *
* @param randomFunction a random function that returns a long value * @param randomFunction a random function that returns a long value
* @return {@link UlidFactory} * @return {@link UlidFactory}
*/ */
public static UlidFactory newMonotonicInstance(LongSupplier randomFunction) { 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. * Returns a new monotonic factory.
* * <p>
* The given random function must return a byte array. * The given random function must return a byte array.
* *
* @param randomFunction a random function that returns a byte array * @param randomFunction a random function that returns a byte array
* @return {@link UlidFactory} * @return {@link UlidFactory}
*/ */
public static UlidFactory newMonotonicInstance(IntFunction<byte[]> randomFunction) { 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. * Returns a new monotonic factory.
* * <p>
* The given random function must return a long value. * The given random function must return a long value.
* *
* @param randomFunction a random function that returns a long value * @param randomFunction a random function that returns a long value
* @param clock a custom clock instance for tests * @param clock a custom clock instance for tests
* @return {@link UlidFactory} * @return {@link UlidFactory}
*/ */
protected static UlidFactory newMonotonicInstance(LongSupplier randomFunction, Clock clock) { static UlidFactory newMonotonicInstance(LongSupplier randomFunction, Clock clock) {
return new UlidFactory(new MonotonicFunction(randomFunction), clock); return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction)), clock);
} }
/** /**
* Returns a new monotonic factory. * Returns a new monotonic factory.
* * <p>
* The given random function must return a byte array. * The given random function must return a byte array.
* *
* @param randomFunction a random function that returns a byte array * @param randomFunction a random function that returns a byte array
* @param clock a custom clock instance for tests * @param clock a custom clock instance for tests
* @return {@link UlidFactory} * @return {@link UlidFactory}
*/ */
protected static UlidFactory newMonotonicInstance(IntFunction<byte[]> randomFunction, Clock clock) { static UlidFactory newMonotonicInstance(IntFunction<byte[]> randomFunction, Clock clock) {
return new UlidFactory(new MonotonicFunction(randomFunction), clock); return new UlidFactory(new MonotonicFunction(IRandom.newInstance(randomFunction)), clock);
} }
// ******************************
// Public methods
// ******************************
/** /**
* Returns a UUID. * Returns a UUID.
* *
@ -186,36 +202,26 @@ public final class UlidFactory {
/** /**
* Returns a UUID with a specific time. * Returns a UUID with a specific time.
* *
* The time must be the number of milliseconds since 1970-01-01 (Unix epoch). * @param time a number of milliseconds since 1970-01-01 (Unix epoch).
*
* @param time a given time
* @return a ULID * @return a ULID
*/ */
public synchronized Ulid create(final long time) { public synchronized Ulid create(final long time) {
return this.ulidFunction.apply(time); return this.ulidFunction.apply(time);
} }
// ******************************
// Package-private inner classes
// ******************************
/** /**
* Function that creates ULIDs. * Function that creates ULIDs.
*/ */
protected static final class UlidFunction implements LongFunction<Ulid> { static final class UlidFunction implements LongFunction<Ulid> {
private final IRandom random; private final IRandom random;
public UlidFunction() { public UlidFunction(IRandom random) {
this.random = new ByteRandom(); this.random = random;
}
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);
} }
@Override @Override
@ -233,9 +239,8 @@ public final class UlidFactory {
/** /**
* Function that creates Monotonic ULIDs. * 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 Ulid lastUlid;
private final IRandom random; private final IRandom random;
@ -245,40 +250,23 @@ public final class UlidFactory {
// system clock jumps back by 1 second due to leap second. // system clock jumps back by 1 second due to leap second.
protected static final int CLOCK_DRIFT_TOLERANCE = 10_000; protected static final int CLOCK_DRIFT_TOLERANCE = 10_000;
public MonotonicFunction() { public MonotonicFunction(IRandom random) {
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) {
this.random = random; this.random = random;
// initialize internal state // initialize internal state
this.lastTime = Clock.systemUTC().millis(); this.lastUlid = new Ulid(0L, this.random.nextBytes(Ulid.RANDOM_BYTES));
this.lastUlid = new Ulid(lastTime, random.nextBytes(Ulid.RANDOM_BYTES));
} }
@Override @Override
public synchronized Ulid apply(final long time) { 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 // 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. // backwards after a small system clock adjustment or after a leap second.
// Drift tolerance = (previous_time - 10s) < current_time <= previous_time // Drift tolerance = (previous_time - 10s) < current_time <= previous_time
if ((time > this.lastTime - CLOCK_DRIFT_TOLERANCE) && (time <= this.lastTime)) { if ((time > lastTime - CLOCK_DRIFT_TOLERANCE) && (time <= lastTime)) {
this.lastUlid = lastUlid.increment(); this.lastUlid = this.lastUlid.increment();
} else { } else {
this.lastTime = time;
if (this.random instanceof ByteRandom) { if (this.random instanceof ByteRandom) {
this.lastUlid = new Ulid(time, this.random.nextBytes(Ulid.RANDOM_BYTES)); this.lastUlid = new Ulid(time, this.random.nextBytes(Ulid.RANDOM_BYTES));
} else { } else {
@ -292,12 +280,16 @@ public final class UlidFactory {
} }
} }
protected static interface IRandom { static interface IRandom {
public long nextLong(); public long nextLong();
public byte[] nextBytes(int length); public byte[] nextBytes(int length);
static IRandom newInstance() {
return new ByteRandom();
}
static IRandom newInstance(Random random) { static IRandom newInstance(Random random) {
if (random == null) { if (random == null) {
return new ByteRandom(); return new ByteRandom();
@ -309,9 +301,17 @@ public final class UlidFactory {
} }
} }
} }
static IRandom newInstance(LongSupplier randomFunction) {
return new LongRandom(randomFunction);
} }
protected static class LongRandom implements IRandom { static IRandom newInstance(IntFunction<byte[]> randomFunction) {
return new ByteRandom(randomFunction);
}
}
static class LongRandom implements IRandom {
private final LongSupplier randomFunction; private final LongSupplier randomFunction;
@ -344,20 +344,20 @@ public final class UlidFactory {
shift = Long.SIZE; shift = Long.SIZE;
random = randomFunction.getAsLong(); random = randomFunction.getAsLong();
} }
shift -= Byte.SIZE; // 56, 48, 42... shift -= Byte.SIZE; // 56, 48, 40...
bytes[i] = (byte) (random >>> shift); bytes[i] = (byte) (random >>> shift);
} }
return bytes; return bytes;
} }
protected static LongSupplier newRandomFunction(Random random) { static LongSupplier newRandomFunction(Random random) {
final Random entropy = random != null ? random : new SecureRandom(); final Random entropy = random != null ? random : new SecureRandom();
return entropy::nextLong; return entropy::nextLong;
} }
} }
protected static class ByteRandom implements IRandom { static class ByteRandom implements IRandom {
private final IntFunction<byte[]> randomFunction; private final IntFunction<byte[]> randomFunction;
@ -388,7 +388,7 @@ public final class UlidFactory {
return this.randomFunction.apply(length); 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(); final Random entropy = random != null ? random : new SecureRandom();
return (final int length) -> { return (final int length) -> {
final byte[] bytes = new byte[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! 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) { private void checkOrdering(Ulid[] list) {
Ulid[] other = Arrays.copyOf(list, list.length); Ulid[] other = Arrays.copyOf(list, list.length);
Arrays.sort(other); Arrays.sort(other);