diff --git a/README.md b/README.md index fab8d53..ee42bc9 100644 --- a/README.md +++ b/README.md @@ -12,36 +12,18 @@ Create a ULID: String ulid = UlidCreator.getUlid(); ``` -Create a fast ULID: - -```java -String ulid = UlidCreator.getFastUlid(); -``` - Create a ULID as GUID object: ```java UUID ulid = UlidCreator.getGuid(); ``` -Create a fast ULID as GUID object: - -```java -UUID ulid = UlidCreator.getFastGuid(); -``` - Create a ULID as byte sequence: ```java byte[] ulid = UlidCreator.getBytes(); ``` -Create a fast ULID as byte sequence: - -```java -byte[] ulid = UlidCreator.getFastBytes(); -``` - ### Maven dependency Add these lines to your `pom.xml`. @@ -51,7 +33,7 @@ Add these lines to your `pom.xml`. com.github.f4b6a3 ulid-creator - 1.0.1 + 1.0.2 ``` See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator) and [mvnrepository.com](https://mvnrepository.com/artifact/com.github.f4b6a3/ulid-creator). @@ -139,17 +121,19 @@ These are some examples of using the `GuidCreator` to create ULIDs: ```java -// with the default random generator (java.security.SecureRandom) -String ulid = UlidCreator.getGuidCreator().createUlid(); - -// with java random generator (java.util.Random) +// with fixed timestamp strategy (for test cases) String ulid = UlidCreator.getGuidCreator() - .withRandomGenerator(new Random()) - .createUlid(); + .withTimestampStrategy(new FixedTimestampStretegy()) + .createUlid(); + +// with your custom timestamp strategy +String ulid = UlidCreator.getGuidCreator() + .withTimestampStrategy(new MyCustomTimestampStrategy()) + .createUlid(); -// with fast random generator (Xorshift128Plus) +// with your custom random number generator String ulid = UlidCreator.getGuidCreator() - .withFastRandomGenerator() + .withRandomGenerator(new MyCustomRandom()) .createUlid(); // with fast random generator (Xorshift128Plus with salt) @@ -158,5 +142,11 @@ Random random = new Xorshift128PlusRandom(salt); String ulid = UlidCreator.getGuidCreator() .withRandomGenerator(random) .createUlid(); + +// with fast random generator (the same as above) +String ulid = UlidCreator.getGuidCreator() + .withFastRandomGenerator() + .createUlid(); + ``` diff --git a/src/main/java/com/github/f4b6a3/ulid/util/UlidUtil.java b/src/main/java/com/github/f4b6a3/ulid/util/UlidUtil.java index 00d0e6c..208b4b8 100644 --- a/src/main/java/com/github/f4b6a3/ulid/util/UlidUtil.java +++ b/src/main/java/com/github/f4b6a3/ulid/util/UlidUtil.java @@ -46,8 +46,23 @@ public class UlidUtil { * @return a ULID */ public static String fromUuidToUlid(UUID uuid) { - byte[] bytes = fromUuidToBytes(uuid); - return fromBytesToUlid(bytes); + + final long msb = uuid.getMostSignificantBits(); + final long lsb = uuid.getLeastSignificantBits(); + + // Extract timestamp component + final long timeNumber = (msb >>> 16); + String timestampComponent = leftPad(Base32Util.toBase32Crockford(timeNumber)); + + // Extract randomness component + byte[] randBytes = new byte[10]; + randBytes[0] = (byte) (msb >>> 8); + randBytes[1] = (byte) (msb); + byte[] lsbBytes = ByteUtil.toBytes(lsb); + System.arraycopy(lsbBytes, 0, randBytes, 2, 8); + String randomnessComponent = Base32Util.toBase32Crockford(randBytes); + + return timestampComponent + randomnessComponent; } /** @@ -59,9 +74,24 @@ public class UlidUtil { * a ULID * @return a UUID if valid */ - public static UUID fromUlidToUuid(String ulid) { - byte[] bytes = fromUlidToBytes(ulid); - return fromBytesToUuid(bytes); + public static UUID fromUlidToUuid(final String ulid) { + + UlidUtil.validate(ulid); + + // Extract timestamp component + final String timestampComponent = ulid.substring(0, 10); + final long timeNumber = Base32Util.fromBase32CrockfordAsLong(timestampComponent); + + // Extract randomness component + final String randomnessComponent = ulid.substring(10, 26); + byte[] randBytes = Base32Util.fromBase32Crockford(randomnessComponent); + byte[] lsbBytes = new byte[8]; + System.arraycopy(randBytes, 2, lsbBytes, 0, 8); + + final long msb = (timeNumber << 16) | ((randBytes[0] << 8) & 0x0000ff00L) | ((randBytes[1]) & 0x000000ffL); + final long lsb = ByteUtil.toNumber(lsbBytes); + + return new UUID(msb, lsb); } /** @@ -71,11 +101,11 @@ public class UlidUtil { * a UUID * @return an array of bytes */ - public static byte[] fromUuidToBytes(UUID uuid) { - long msb = uuid.getMostSignificantBits(); - long lsb = uuid.getLeastSignificantBits(); - byte[] msbBytes = ByteUtil.toBytes(msb); - byte[] lsbBytes = ByteUtil.toBytes(lsb); + public static byte[] fromUuidToBytes(final UUID uuid) { + final long msb = uuid.getMostSignificantBits(); + final long lsb = uuid.getLeastSignificantBits(); + final byte[] msbBytes = ByteUtil.toBytes(msb); + final byte[] lsbBytes = ByteUtil.toBytes(lsb); return ByteUtil.concat(msbBytes, lsbBytes); } @@ -87,10 +117,12 @@ public class UlidUtil { * @return a UUID */ public static UUID fromBytesToUuid(byte[] bytes) { - byte[] msbBytes = ByteUtil.copy(bytes, 0, 8); - byte[] lsbBytes = ByteUtil.copy(bytes, 8, 16); - long msb = ByteUtil.toNumber(msbBytes); - long lsb = ByteUtil.toNumber(lsbBytes); + byte[] msbBytes = new byte[8]; + System.arraycopy(bytes, 0, msbBytes, 0, 8); + byte[] lsbBytes = new byte[8]; + System.arraycopy(bytes, 8, lsbBytes, 0, 8); + final long msb = ByteUtil.toNumber(msbBytes); + final long lsb = ByteUtil.toNumber(lsbBytes); return new UUID(msb, lsb); } @@ -105,12 +137,12 @@ public class UlidUtil { byte[] timeBytes = new byte[6]; System.arraycopy(bytes, 0, timeBytes, 0, 6); - long timeNumber = ByteUtil.toNumber(timeBytes); - String timestampComponent = leftPad(Base32Util.toBase32Crockford(timeNumber)); + final long timeNumber = ByteUtil.toNumber(timeBytes); + final String timestampComponent = leftPad(Base32Util.toBase32Crockford(timeNumber)); byte[] randBytes = new byte[10]; System.arraycopy(bytes, 6, randBytes, 0, 10); - String randomnessComponent = Base32Util.toBase32Crockford(randBytes); + final String randomnessComponent = Base32Util.toBase32Crockford(randBytes); return timestampComponent + randomnessComponent; } @@ -122,16 +154,16 @@ public class UlidUtil { * a ULID string * @return an array of bytes */ - public static byte[] fromUlidToBytes(String ulid) { + public static byte[] fromUlidToBytes(final String ulid) { UlidUtil.validate(ulid); byte[] bytes = new byte[16]; - String timestampComponent = ulid.substring(0, 10); - long timeNumber = Base32Util.fromBase32CrockfordAsLong(timestampComponent); + final String timestampComponent = ulid.substring(0, 10); + final long timeNumber = Base32Util.fromBase32CrockfordAsLong(timestampComponent); byte[] timeBytes = ByteUtil.toBytes(timeNumber); System.arraycopy(timeBytes, 2, bytes, 0, 6); - String randomnessComponent = ulid.substring(10, 26); + final String randomnessComponent = ulid.substring(10, 26); byte[] randBytes = Base32Util.fromBase32Crockford(randomnessComponent); System.arraycopy(randBytes, 0, bytes, 6, 10); diff --git a/src/test/java/com/github/f4b6a3/Benchmarks.java b/src/test/java/com/github/f4b6a3/Benchmarks.java new file mode 100644 index 0000000..2bc9647 --- /dev/null +++ b/src/test/java/com/github/f4b6a3/Benchmarks.java @@ -0,0 +1,89 @@ +package com.github.f4b6a3; + +// Add theese dependencies to pom.xml: +// +// +// org.openjdk.jmh +// jmh-core +// 1.23 +// +// +// org.openjdk.jmh +// jmh-generator-annprocess +// 1.23 +// + +/* + +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import com.github.f4b6a3.ulid.UlidCreator; + +@Threads(1) +@State(Scope.Thread) +@Warmup(iterations = 5) +@Measurement(iterations = 5) +public class Benchmarks { + + @Benchmark + @BenchmarkMode(Mode.Throughput) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public String getUlidThroughput() { + return UlidCreator.getUlid(); + } + + @Benchmark + @BenchmarkMode(Mode.AverageTime) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public String getUlidAverage() { + return UlidCreator.getUlid(); + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public UUID getGuidThroughput() { + return UlidCreator.getGuid(); + } + + @Benchmark + @BenchmarkMode(Mode.AverageTime) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public UUID getGuidAverage() { + return UlidCreator.getGuid(); + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public UUID getRandomUUIDThroughput() { + return UUID.randomUUID(); + } + + @Benchmark + @BenchmarkMode(Mode.AverageTime) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public UUID getRandomUUIDAverage() { + return UUID.randomUUID(); + } + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder().include(MyBenchmark.class.getSimpleName()).forks(1).build(); + new Runner(opt).run(); + } +} +*/ diff --git a/src/test/java/com/github/f4b6a3/demo/DemoTest.java b/src/test/java/com/github/f4b6a3/demo/DemoTest.java index 2a5b274..3cccdf1 100644 --- a/src/test/java/com/github/f4b6a3/demo/DemoTest.java +++ b/src/test/java/com/github/f4b6a3/demo/DemoTest.java @@ -7,7 +7,7 @@ public class DemoTest { private static final String HORIZONTAL_LINE = "----------------------------------------"; public static void printList() { - int max = 1_000; + int max = 100; System.out.println(HORIZONTAL_LINE); System.out.println("### ULID"); diff --git a/src/test/java/com/github/f4b6a3/ulid/util/UlidUtilTest.java b/src/test/java/com/github/f4b6a3/ulid/util/UlidUtilTest.java index e50f0f3..d94e21b 100644 --- a/src/test/java/com/github/f4b6a3/ulid/util/UlidUtilTest.java +++ b/src/test/java/com/github/f4b6a3/ulid/util/UlidUtilTest.java @@ -182,8 +182,8 @@ public class UlidUtilTest { for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - UUID uuid = UlidCreator.getFastGuid(); - String ulid = UlidUtil.fromUuidToUlid(uuid); + UUID uuid1 = UlidCreator.getGuid(); + String ulid = UlidUtil.fromUuidToUlid(uuid1); assertTrue("ULID is null", ulid != null); assertTrue("ULID is empty", !ulid.isEmpty()); @@ -191,12 +191,57 @@ public class UlidUtilTest { assertTrue("ULID is not valid", UlidUtil.isValid(ulid, /* strict */ true)); - UUID result = UlidUtil.fromUlidToUuid(ulid); - assertEquals("Result ULID is different from original ULID", uuid, result); + UUID uuid2 = UlidUtil.fromUlidToUuid(ulid); + assertEquals("Result ULID is different from original ULID", uuid1, uuid2); } } + @Test + public void testToAndFromBytes() { + for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { + String ulid1 = UlidCreator.getUlid(); + byte[] bytes = UlidUtil.fromUlidToBytes(ulid1); + String ulid2 = UlidUtil.fromBytesToUlid(bytes); + + // Check ULID 1 + assertTrue(ulid1 != null); + assertTrue(!ulid1.isEmpty()); + assertTrue(ulid1.length() == ULID_LENGTH); + assertTrue(UlidUtil.isValid(ulid1, /* strict */ true)); + + // Check ULID 2 + assertTrue(ulid2 != null); + assertTrue(!ulid2.isEmpty()); + assertTrue(ulid2.length() == ULID_LENGTH); + assertTrue(UlidUtil.isValid(ulid2, /* strict */ true)); + + assertEquals(ulid1, ulid2); + } + } + + @Test + public void testFromUuidToBytes() { + for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { + UUID uuid1 = UlidCreator.getGuid(); + byte[] bytes = UlidUtil.fromUuidToBytes(uuid1); + long msb = ByteUtil.toNumber(ByteUtil.copy(bytes, 0, 8)); + long lsb = ByteUtil.toNumber(ByteUtil.copy(bytes, 8, 16)); + UUID uuid2 = new UUID(msb, lsb); + assertEquals(uuid1, uuid2); + } + } + + @Test + public void testFromBytesToUuid() { + for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { + UUID uuid1 = UlidCreator.getGuid(); + byte[] bytes = UlidUtil.fromUuidToBytes(uuid1); + UUID uuid2 = UlidUtil.fromBytesToUuid(bytes); + assertEquals(uuid1, uuid2); + } + } + private String leftPad(String unpadded) { return "0000000000".substring(unpadded.length()) + unpadded; }