From fdd33556a73cee5c01d14ed464c111fd10731193 Mon Sep 17 00:00:00 2001 From: Fabio Lima Date: Sat, 17 Oct 2020 21:50:37 -0300 Subject: [PATCH] [#3] Remove overrun exception Remove UlidCreatorException // extremely unlikely to occur overrun Add UlidCreator.toString() // from UUID to crockford base 32 Change DefaultRandomStrategy // no need for thread local SecureRandom Update test cases Update README.md Update javadoc Test coverage: 94.2% --- README.md | 60 ++++++++++--------- .../com/github/f4b6a3/ulid/UlidCreator.java | 19 ++++++ .../f4b6a3/ulid/creator/UlidSpecCreator.java | 10 ---- .../ulid/exception/UlidCreatorException.java | 34 ----------- .../random/DefaultRandomStrategy.java | 6 +- .../f4b6a3/ulid/util/UlidConverter.java | 8 +-- .../github/f4b6a3/ulid/UniquenessTest.java | 9 +-- .../ulid/creator/UlidSpecCreatorTest.java | 23 +++---- 8 files changed, 66 insertions(+), 103 deletions(-) delete mode 100644 src/main/java/com/github/f4b6a3/ulid/exception/UlidCreatorException.java diff --git a/README.md b/README.md index c5fbca6..301e9ba 100644 --- a/README.md +++ b/README.md @@ -3,22 +3,27 @@ A Java library for generating ULIDs. +* Generated in lexicographical order; +* Can be stored as a UUID/GUID; +* Can be stored as a string of 26 chars; +* String format is encoded to [Crockford's base32](https://www.crockford.com/base32.html); +* String format is URL safe, case insensitive and accepts hyphens. + How to Use ------------------------------------------------------ -Create a ULID as GUID: +Create a ULID: ```java -UUID ulid = UlidCreator.getUlid(); +UUID ulid = UlidCreator.getUlid(); // 01706d6c-6aad-c795-370c-98d0be881bba ``` -Create a ULID as string: +Create a ULID string: ```java -String ulid = UlidCreator.getUlidString(); +String ulid = UlidCreator.getUlidString(); // 01E1PPRTMSQ34W7JR5YSND6B8Z ``` - ### Maven dependency Add these lines to your `pom.xml`. @@ -28,7 +33,7 @@ Add these lines to your `pom.xml`. com.github.f4b6a3 ulid-creator - 2.0.2 + 2.1.0 ``` See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator). @@ -36,20 +41,20 @@ See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b Implementation ------------------------------------------------------ -### ULID as GUID +### ULID The GUIDs in this library are based on the [ULID specification](https://github.com/ulid/spec). The first 48 bits represent the count of milliseconds since Unix Epoch, 1 January 1970. The remaining 60 bits are generated by a secure random number generator. Every time the timestamp changes the random part is reset to a new random value. If the current timestamp is equal to the previous one, the random bits are incremented by 1. -The default random number generator is a thread safe `java.security.SecureRandom`, but it's possible to use any RNG that extends `java.util.Random`. +The default random number generator is `java.security.SecureRandom`. ```java // GUID based on ULID spec UUID ulid = UlidCreator.getUlid(); ``` -Examples of GUIDs based on ULID spec: +Sequence of GUIDs based on ULID spec: ```text 01706d6c-6aac-80bd-7ff5-f660c2dd58ea @@ -74,18 +79,18 @@ Examples of GUIDs based on ULID spec: millisecs randomness ``` -### ULID as string +### ULID string -The ULID is a 26 char sequence. See the [ULID specification](https://github.com/ulid/spec) for more information. +The ULID string is a sequence of 26 chars. See the [ULID specification](https://github.com/ulid/spec) for more information. See the section on GUIDs to know how the 128 bits are generated in this library. ```java -// ULIDs +// String based on ULID spec String ulid = UlidCreator.getUlidString(); ``` -Examples of ULIDs: +Sequence of Strings based on ULID spec: ```text 01E1PPRTMSQ34W7JR5YSND6B8T @@ -107,36 +112,35 @@ Examples of ULIDs: ^ look ^ look |---------|--------------| - milli randomness + millisecs randomness ``` -#### How use the `UlidSpecCreator` directly +### How use the `UlidSpecCreator` directly -These are some examples of using the `UlidSpecCreator` to create ULIDs strings: +These are some examples of using the `UlidSpecCreator` to create ULID strings: ```java // with your custom timestamp strategy TimestampStrategy customStrategy = new CustomTimestampStrategy(); -String ulid = UlidCreator.getUlidSpecCreator() - .withTimestampStrategy(customStrategy) - .createString(); +UlidSpecCreator creator = UlidCreator.getUlidSpecCreator() + .withTimestampStrategy(customStrategy); +String ulid = creator.createString(); ``` ```java // with your custom random strategy that wraps any random generator RandomStrategy customStrategy = new CustomRandomStrategy(); -String ulid = UlidCreator.getUlidSpecCreator() - .withRandomStrategy(customStrategy) - .createString(); +UlidSpecCreator creator = UlidCreator.getUlidSpecCreator() + .withRandomStrategy(customStrategy); +String ulid = creator.createString(); ``` ```java // with `java.util.Random` number generator Random random = new Random(); -String ulid = UlidCreator.getUlidSpecCreator() - .withRandomGenerator(random) - .createString(); +UlidSpecCreator creator = UlidCreator.getUlidSpecCreator() + .withRandomGenerator(random); +String ulid = creator.createString(); ``` - Benchmark ------------------------------------------------------ @@ -150,7 +154,7 @@ Throughput.Java_RandomBased thrpt 5 2234,199 ± 2,844 ops/ms Throughput.UlidCreator_Ulid thrpt 5 19155,742 ± 22,195 ops/ms Throughput.UlidCreator_UlidString thrpt 5 4946,479 ± 22,800 ops/ms --------------------------------------------------------------------------- -Total time: 00:06:41 +Total time: 00:04:01 --------------------------------------------------------------------------- ``` @@ -162,7 +166,7 @@ AverageTime.Java_RandomBased avgt 5 449,641 ± 0,994 ns/op AverageTime.UlidCreator_Ulid avgt 5 52,199 ± 0,185 ns/op AverageTime.UlidCreator_UlidString avgt 5 202,014 ± 2,111 ns/op ---------------------------------------------------------------------- -Total time: 00:06:41 +Total time: 00:04:01 ---------------------------------------------------------------------- ``` diff --git a/src/main/java/com/github/f4b6a3/ulid/UlidCreator.java b/src/main/java/com/github/f4b6a3/ulid/UlidCreator.java index 7bf9f01..706b53a 100644 --- a/src/main/java/com/github/f4b6a3/ulid/UlidCreator.java +++ b/src/main/java/com/github/f4b6a3/ulid/UlidCreator.java @@ -27,6 +27,7 @@ package com.github.f4b6a3.ulid; import java.util.UUID; import com.github.f4b6a3.ulid.creator.UlidSpecCreator; +import com.github.f4b6a3.ulid.exception.InvalidUlidException; import com.github.f4b6a3.ulid.util.UlidConverter; /** @@ -42,13 +43,31 @@ public final class UlidCreator { /** * Returns a ULID as GUID from a string. * + * The input string must be encoded to Crockford's base32, following the ULID + * specification. + * + * An exception is thrown if the ULID string is invalid. + * * @param ulid a ULID string * @return a UUID + * @throws InvalidUlidException if invalid */ public static UUID fromString(String ulid) { return UlidConverter.fromString(ulid); } + /** + * Convert a UUID to ULID string + * + * The returning string is encoded to Crockford's base32. + * + * @param uuid a UUID + * @return a ULID string + */ + public static String toString(UUID ulid) { + return UlidConverter.toString(ulid); + } + /** * Returns a ULID as GUID. * diff --git a/src/main/java/com/github/f4b6a3/ulid/creator/UlidSpecCreator.java b/src/main/java/com/github/f4b6a3/ulid/creator/UlidSpecCreator.java index 06c76e3..fd807c3 100644 --- a/src/main/java/com/github/f4b6a3/ulid/creator/UlidSpecCreator.java +++ b/src/main/java/com/github/f4b6a3/ulid/creator/UlidSpecCreator.java @@ -30,7 +30,6 @@ import java.util.UUID; import com.github.f4b6a3.ulid.strategy.RandomStrategy; import com.github.f4b6a3.ulid.strategy.random.DefaultRandomStrategy; import com.github.f4b6a3.ulid.strategy.random.OtherRandomStrategy; -import com.github.f4b6a3.ulid.exception.UlidCreatorException; import com.github.f4b6a3.ulid.strategy.TimestampStrategy; import com.github.f4b6a3.ulid.strategy.timestamp.DefaultTimestampStrategy; import com.github.f4b6a3.ulid.util.UlidConverter; @@ -120,9 +119,6 @@ public class UlidSpecCreator { * overflow with less, the generation will fail. * * @return {@link UUID} a GUID value - * - * @throws UlidCreatorException an overrun exception if too many requests are - * made within the same millisecond. */ public synchronized UUID create() { @@ -188,18 +184,12 @@ public class UlidSpecCreator { /** * Increment the random part of the GUID. - * - * An exception is thrown when more than 2^80 increment operations are made, - * although it's extremely unlikely to occur. - * - * @throws UlidCreatorException if an overrun happens. */ protected synchronized void increment() { if (++this.random2 >= this.randomMax2) { this.random2 = this.random2 & HALF_RANDOM_COMPONENT; if ((++this.random1 >= this.randomMax1)) { this.reset(); - throw new UlidCreatorException(OVERRUN_MESSAGE); } } } diff --git a/src/main/java/com/github/f4b6a3/ulid/exception/UlidCreatorException.java b/src/main/java/com/github/f4b6a3/ulid/exception/UlidCreatorException.java deleted file mode 100644 index 1cdbfaf..0000000 --- a/src/main/java/com/github/f4b6a3/ulid/exception/UlidCreatorException.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020 Fabio Lima - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package com.github.f4b6a3.ulid.exception; - -public final class UlidCreatorException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - public UlidCreatorException(String message) { - super(message); - } -} diff --git a/src/main/java/com/github/f4b6a3/ulid/strategy/random/DefaultRandomStrategy.java b/src/main/java/com/github/f4b6a3/ulid/strategy/random/DefaultRandomStrategy.java index 987f2d0..5f54dc2 100644 --- a/src/main/java/com/github/f4b6a3/ulid/strategy/random/DefaultRandomStrategy.java +++ b/src/main/java/com/github/f4b6a3/ulid/strategy/random/DefaultRandomStrategy.java @@ -30,14 +30,14 @@ import java.util.Random; import com.github.f4b6a3.ulid.strategy.RandomStrategy; /** - * It uses a thread local instance of {@link java.security.SecureRandom}. + * It uses an instance of {@link java.security.SecureRandom}. */ public final class DefaultRandomStrategy implements RandomStrategy { - protected static final ThreadLocal THREAD_LOCAL_RANDOM = ThreadLocal.withInitial(SecureRandom::new); + private static final Random SECURE_RANDOM = new SecureRandom(); @Override public void nextBytes(byte[] bytes) { - THREAD_LOCAL_RANDOM.get().nextBytes(bytes); + SECURE_RANDOM.nextBytes(bytes); } } diff --git a/src/main/java/com/github/f4b6a3/ulid/util/UlidConverter.java b/src/main/java/com/github/f4b6a3/ulid/util/UlidConverter.java index 91a59cb..3174f45 100644 --- a/src/main/java/com/github/f4b6a3/ulid/util/UlidConverter.java +++ b/src/main/java/com/github/f4b6a3/ulid/util/UlidConverter.java @@ -40,13 +40,13 @@ public final class UlidConverter { * * The returning string is encoded to Crockford's base32. * - * @param uuid a UUID + * @param ulid a UUID * @return a ULID */ - public static String toString(UUID uuid) { + public static String toString(UUID ulid) { - final long msb = uuid.getMostSignificantBits(); - final long lsb = uuid.getLeastSignificantBits(); + final long msb = ulid.getMostSignificantBits(); + final long lsb = ulid.getLeastSignificantBits(); final long time = ((msb & 0xffffffffffff0000L) >>> 16); final long random1 = ((msb & 0x000000000000ffffL) << 24) | ((lsb & 0xffffff0000000000L) >>> 40); diff --git a/src/test/java/com/github/f4b6a3/ulid/UniquenessTest.java b/src/test/java/com/github/f4b6a3/ulid/UniquenessTest.java index 0cfba13..bb69320 100644 --- a/src/test/java/com/github/f4b6a3/ulid/UniquenessTest.java +++ b/src/test/java/com/github/f4b6a3/ulid/UniquenessTest.java @@ -5,7 +5,6 @@ import java.util.UUID; import com.github.f4b6a3.ulid.UlidCreator; import com.github.f4b6a3.ulid.creator.UlidSpecCreator; -import com.github.f4b6a3.ulid.exception.UlidCreatorException; import com.github.f4b6a3.ulid.strategy.timestamp.FixedTimestampStretegy; /** @@ -93,13 +92,7 @@ public class UniquenessTest { for (int i = 0; i < max; i++) { // Request a UUID - UUID uuid = null; - try { - uuid = creator.create(); - } catch (UlidCreatorException e) { - // Ignore the overrun exception and try again - uuid = creator.create(); - } + UUID uuid = creator.create(); if (verbose) { // Calculate and show progress diff --git a/src/test/java/com/github/f4b6a3/ulid/creator/UlidSpecCreatorTest.java b/src/test/java/com/github/f4b6a3/ulid/creator/UlidSpecCreatorTest.java index e1ecec4..affe83a 100644 --- a/src/test/java/com/github/f4b6a3/ulid/creator/UlidSpecCreatorTest.java +++ b/src/test/java/com/github/f4b6a3/ulid/creator/UlidSpecCreatorTest.java @@ -9,7 +9,6 @@ import java.util.UUID; import org.junit.Test; import com.github.f4b6a3.ulid.UlidCreator; -import com.github.f4b6a3.ulid.exception.UlidCreatorException; import com.github.f4b6a3.ulid.strategy.timestamp.FixedTimestampStretegy; import static org.junit.Assert.*; @@ -126,7 +125,7 @@ public class UlidSpecCreatorTest { } @Test - public void testShouldThrowOverflowException1() { + public void testIncrementRandomComponentMaximum1() { long random1 = 0x000000ffffffffffL; long random2 = 0x000000ffffffffffL; @@ -157,16 +156,12 @@ public class UlidSpecCreatorTest { BigInteger bigint2 = new BigInteger(concat2, 16); assertEquals(bigint1.add(BigInteger.valueOf(DEFAULT_LOOP_MAX)), bigint2); - try { - uuid = creator.create(); - fail("It should throw an overflow exception."); - } catch (UlidCreatorException e) { - // success - } + // This line resets the random component + uuid = creator.create(); } @Test - public void testShouldThrowOverflowException2() { + public void testIncrementRandomComponentMaximum2() { long random1 = (RANDOM.nextLong() & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT); long random2 = (RANDOM.nextLong() & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT); @@ -204,13 +199,9 @@ public class UlidSpecCreatorTest { String concat2 = (Long.toHexString(hi2) + Long.toHexString(lo2)); BigInteger bigint2 = new BigInteger(concat2, 16); assertEquals(bigint1.add(BigInteger.valueOf(DEFAULT_LOOP_MAX)), bigint2); - - try { - creator.create(); - fail("It should throw an overflow exception."); - } catch (UlidCreatorException e) { - // success - } + + // This line resets the random component + creator.create(); } @Test