Version 2.0.0
Rename UlidBasedGuidCreator to UlidSpecCreator Add method UlidCreator.fromString() Add RandomStrategy for UlidSpecCreator Add DefaultRandomStrategy using thread local SecureRandom Add OtherRandomStrategy for any instance of Random Add tests cases Optimize UlidConverter.fromString() Optimize UlidConverter.toString() Optimize UlidValidator.isValid() Optimize UlidSpecCreator Update README.md Update tests cases
This commit is contained in:
parent
a79edd90e5
commit
327aa7bc6b
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 f4b6a3
|
||||
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
|
||||
|
|
140
README.md
140
README.md
|
@ -12,7 +12,7 @@ Create a ULID as GUID:
|
|||
UUID ulid = UlidCreator.getUlid();
|
||||
```
|
||||
|
||||
Create a ULID string:
|
||||
Create a ULID as string:
|
||||
|
||||
```java
|
||||
String ulid = UlidCreator.getUlidString();
|
||||
|
@ -28,7 +28,7 @@ Add these lines to your `pom.xml`.
|
|||
<dependency>
|
||||
<groupId>com.github.f4b6a3</groupId>
|
||||
<artifactId>ulid-creator</artifactId>
|
||||
<version>1.1.1</version>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
```
|
||||
See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator).
|
||||
|
@ -36,7 +36,45 @@ See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b
|
|||
Implementation
|
||||
------------------------------------------------------
|
||||
|
||||
### ULID string
|
||||
### ULID as GUID
|
||||
|
||||
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`.
|
||||
|
||||
```java
|
||||
// GUID based on ULID spec
|
||||
UUID ulid = UlidCreator.getUlid();
|
||||
```
|
||||
|
||||
Examples of GUIDs based on ULID spec:
|
||||
|
||||
```text
|
||||
01706d6c-6aac-80bd-7ff5-f660c2dd58ea
|
||||
01706d6c-6aac-80bd-7ff5-f660c2dd58eb
|
||||
01706d6c-6aac-80bd-7ff5-f660c2dd58ec
|
||||
01706d6c-6aac-80bd-7ff5-f660c2dd58ed
|
||||
01706d6c-6aac-80bd-7ff5-f660c2dd58ee
|
||||
01706d6c-6aac-80bd-7ff5-f660c2dd58ef
|
||||
01706d6c-6aac-80bd-7ff5-f660c2dd58f0
|
||||
01706d6c-6aac-80bd-7ff5-f660c2dd58f1
|
||||
01706d6c-6aad-c795-370c-98d0be881bb8 < millisecond changed
|
||||
01706d6c-6aad-c795-370c-98d0be881bb9
|
||||
01706d6c-6aad-c795-370c-98d0be881bba
|
||||
01706d6c-6aad-c795-370c-98d0be881bbb
|
||||
01706d6c-6aad-c795-370c-98d0be881bbc
|
||||
01706d6c-6aad-c795-370c-98d0be881bbd
|
||||
01706d6c-6aad-c795-370c-98d0be881bbe
|
||||
01706d6c-6aad-c795-370c-98d0be881bbf
|
||||
^ look ^ look
|
||||
|
||||
|------------|---------------------|
|
||||
millisecs randomness
|
||||
```
|
||||
|
||||
### ULID as string
|
||||
|
||||
The ULID is a 26 char sequence. See the [ULID specification](https://github.com/ulid/spec) for more information.
|
||||
|
||||
|
@ -72,66 +110,68 @@ Examples of ULIDs:
|
|||
milli randomness
|
||||
```
|
||||
|
||||
### Ulid-based GUID
|
||||
#### How use the `UlidSpecCreator` directly
|
||||
|
||||
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 `java.security.SecureRandom`, but it's possible to use any RNG that extends `java.util.Random`.
|
||||
|
||||
```java
|
||||
// GUID based on ULID spec
|
||||
UUID ulid = UlidCreator.getUlid();
|
||||
```
|
||||
|
||||
Examples of GUIDs based on ULID spec:
|
||||
|
||||
```text
|
||||
01706d6c-6aac-80bd-7ff5-f660c2dd58ea
|
||||
01706d6c-6aac-80bd-7ff5-f660c2dd58eb
|
||||
01706d6c-6aac-80bd-7ff5-f660c2dd58ec
|
||||
01706d6c-6aac-80bd-7ff5-f660c2dd58ed
|
||||
01706d6c-6aac-80bd-7ff5-f660c2dd58ee
|
||||
01706d6c-6aac-80bd-7ff5-f660c2dd58ef
|
||||
01706d6c-6aac-80bd-7ff5-f660c2dd58f0
|
||||
01706d6c-6aac-80bd-7ff5-f660c2dd58f1
|
||||
01706d6c-6aad-c795-370c-98d0be881bb8 < millisecond changed
|
||||
01706d6c-6aad-c795-370c-98d0be881bb9
|
||||
01706d6c-6aad-c795-370c-98d0be881bba
|
||||
01706d6c-6aad-c795-370c-98d0be881bbb
|
||||
01706d6c-6aad-c795-370c-98d0be881bbc
|
||||
01706d6c-6aad-c795-370c-98d0be881bbd
|
||||
01706d6c-6aad-c795-370c-98d0be881bbe
|
||||
01706d6c-6aad-c795-370c-98d0be881bbf
|
||||
^ look ^ look
|
||||
|
||||
|------------|---------------------|
|
||||
millisecs randomness
|
||||
```
|
||||
|
||||
#### How use the `UlidBasedGuidCreator` directly
|
||||
|
||||
These are some examples of using the `UlidBasedGuidCreator` to create ULIDs strings:
|
||||
These are some examples of using the `UlidSpecCreator` to create ULIDs strings:
|
||||
|
||||
```java
|
||||
|
||||
// with your custom timestamp strategy
|
||||
TimestampStrategy customStrategy = new CustomTimestampStrategy();
|
||||
String ulid = UlidCreator.getUlidBasedGuidCreator()
|
||||
String ulid = UlidCreator.getUlidSpecCreator()
|
||||
.withTimestampStrategy(customStrategy)
|
||||
.createString();
|
||||
|
||||
// with your custom random strategy that wraps any random generator
|
||||
RandomStrategy customStrategy = new CustomRandomStrategy();
|
||||
String ulid = UlidCreator.getUlidSpecCreator()
|
||||
.withRandomStrategy(customStrategy)
|
||||
.createString();
|
||||
|
||||
// with `java.util.Random` number generator
|
||||
Random random = new Random();
|
||||
String ulid = UlidCreator.getUlidBasedGuidCreator()
|
||||
String ulid = UlidCreator.getUlidSpecCreator()
|
||||
.withRandomGenerator(random)
|
||||
.createString();
|
||||
|
||||
// with fast random generator (the same as above)
|
||||
String ulid = UlidCreator.getUlidBasedGuidCreator()
|
||||
.withFastRandomGenerator()
|
||||
.createString();
|
||||
|
||||
```
|
||||
|
||||
|
||||
Benchmark
|
||||
------------------------------------------------------
|
||||
|
||||
This section shows benchmarks comparing `UlidCreator` to `java.util.UUID`.
|
||||
|
||||
```
|
||||
---------------------------------------------------------------------------
|
||||
THROUGHPUT Mode Cnt Score Error Units
|
||||
---------------------------------------------------------------------------
|
||||
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
|
||||
---------------------------------------------------------------------------
|
||||
```
|
||||
|
||||
```
|
||||
----------------------------------------------------------------------
|
||||
AVERAGE TIME Mode Cnt Score Error Units
|
||||
----------------------------------------------------------------------
|
||||
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
|
||||
----------------------------------------------------------------------
|
||||
```
|
||||
|
||||
System: CPU i5-3330, 8G RAM, Ubuntu 20.04.
|
||||
|
||||
See: [uuid-creator-benchmark](https://github.com/fabiolimace/uuid-creator-benchmark)
|
||||
|
||||
Links for generators
|
||||
-------------------------------------------
|
||||
* [UUID Creator](https://github.com/f4b6a3/uuid-creator)
|
||||
* [ULID Creator](https://github.com/f4b6a3/ulid-creator)
|
||||
* [TSID Creator](https://github.com/f4b6a3/tsid-creator)
|
||||
|
|
19
pom.xml
19
pom.xml
|
@ -3,11 +3,11 @@
|
|||
|
||||
<groupId>com.github.f4b6a3</groupId>
|
||||
<artifactId>ulid-creator</artifactId>
|
||||
<version>1.1.2-SNAPSHOT</version>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>ulid-creator</name>
|
||||
<url>http://github.com/f4b6a3</url>
|
||||
<url>http://github.com/f4b6a3/ulid-creator</url>
|
||||
<description>A Java library for generating and handling ULIDs.</description>
|
||||
|
||||
|
||||
|
@ -26,17 +26,12 @@
|
|||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<jdk.version>11</jdk.version>
|
||||
<jdk.version>8</jdk.version>
|
||||
<maven.compiler.source>${jdk.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${jdk.version}</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.f4b6a3</groupId>
|
||||
<artifactId>util</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
|
@ -46,10 +41,10 @@
|
|||
</dependencies>
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:git://github.com/dexecutor/dependent-tasks-executor.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:dexecutor/dexecutor.git</developerConnection>
|
||||
<url>https://github.com/dexecutor/dependent-tasks-executor</url>
|
||||
<tag>ulid-creator-1.0.0</tag>
|
||||
<url>https://github.com/f4b6a3/ulid-creator</url>
|
||||
<connection>scm:git:ssh://git@github.com/f4b6a3/ulid-creator.git</connection>
|
||||
<developerConnection>scm:git:ssh://git@github.com/f4b6a3/ulid-creator.git</developerConnection>
|
||||
<tag>HEAD</tag>
|
||||
</scm>
|
||||
|
||||
<distributionManagement>
|
||||
|
|
|
@ -26,19 +26,29 @@ package com.github.f4b6a3.ulid;
|
|||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.github.f4b6a3.util.random.Xorshift128PlusRandom;
|
||||
import com.github.f4b6a3.ulid.creator.UlidBasedGuidCreator;
|
||||
import com.github.f4b6a3.ulid.creator.UlidSpecCreator;
|
||||
import com.github.f4b6a3.ulid.util.UlidConverter;
|
||||
|
||||
/**
|
||||
* A factory for Universally Unique Lexicographically Sortable Identifiers.
|
||||
*
|
||||
* See the ULID spec: https://github.com/ulid/spec
|
||||
*/
|
||||
public class UlidCreator {
|
||||
public final class UlidCreator {
|
||||
|
||||
private UlidCreator() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ULID as GUID from a string.
|
||||
*
|
||||
* @param ulid a ULID string
|
||||
* @return a UUID
|
||||
*/
|
||||
public static UUID fromString(String ulid) {
|
||||
return UlidConverter.fromString(ulid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ULID as GUID.
|
||||
*
|
||||
|
@ -48,7 +58,7 @@ public class UlidCreator {
|
|||
* @return a UUID
|
||||
*/
|
||||
public static UUID getUlid() {
|
||||
return UlidBasedGuidCreatorHolder.INSTANCE.create();
|
||||
return UlidSpecCreatorHolder.INSTANCE.create();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,49 +72,19 @@ public class UlidCreator {
|
|||
* @return a ULID
|
||||
*/
|
||||
public static String getUlidString() {
|
||||
return UlidBasedGuidCreatorHolder.INSTANCE.createString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ULID as GUID.
|
||||
*
|
||||
* The random component is generated by a fast random number generator:
|
||||
* {@link Xorshift128PlusRandom}.
|
||||
*
|
||||
* @return a UUID
|
||||
*/
|
||||
public static UUID getFastUlid() {
|
||||
return FastUlidBasedGuidCreatorHolder.INSTANCE.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a fast ULID string.
|
||||
*
|
||||
* The returning string is encoded to Crockford's base32.
|
||||
*
|
||||
* The random component is generated by a fast random number generator:
|
||||
* {@link Xorshift128PlusRandom}.
|
||||
*
|
||||
* @return a ULID
|
||||
*/
|
||||
public static String getFastUlidString() {
|
||||
return FastUlidBasedGuidCreatorHolder.INSTANCE.createString();
|
||||
return UlidSpecCreatorHolder.INSTANCE.createString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a GUID creator for direct use.
|
||||
*
|
||||
* @return a {@link UlidBasedGuidCreator}
|
||||
* @return a {@link UlidSpecCreator}
|
||||
*/
|
||||
public static UlidBasedGuidCreator getUlidBasedCreator() {
|
||||
return new UlidBasedGuidCreator();
|
||||
public static UlidSpecCreator getUlidSpecCreator() {
|
||||
return new UlidSpecCreator();
|
||||
}
|
||||
|
||||
private static class UlidBasedGuidCreatorHolder {
|
||||
static final UlidBasedGuidCreator INSTANCE = getUlidBasedCreator();
|
||||
}
|
||||
|
||||
private static class FastUlidBasedGuidCreatorHolder {
|
||||
static final UlidBasedGuidCreator INSTANCE = getUlidBasedCreator().withFastRandomGenerator();
|
||||
private static class UlidSpecCreatorHolder {
|
||||
static final UlidSpecCreator INSTANCE = getUlidSpecCreator();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,13 +27,14 @@ package com.github.f4b6a3.ulid.creator;
|
|||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.github.f4b6a3.ulid.util.UlidConverter;
|
||||
import com.github.f4b6a3.util.random.Xorshift128PlusRandom;
|
||||
import com.github.f4b6a3.util.FingerprintUtil;
|
||||
import com.github.f4b6a3.util.RandomUtil;
|
||||
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;
|
||||
import com.github.f4b6a3.ulid.util.UlidUtil;
|
||||
|
||||
/**
|
||||
* Factory that creates lexicographically sortable GUIDs, based on the ULID
|
||||
|
@ -41,28 +42,28 @@ import com.github.f4b6a3.ulid.strategy.timestamp.DefaultTimestampStrategy;
|
|||
*
|
||||
* ULID specification: https://github.com/ulid/spec
|
||||
*/
|
||||
public class UlidBasedGuidCreator {
|
||||
public class UlidSpecCreator {
|
||||
|
||||
protected long randomMsb = 0;
|
||||
protected long randomLsb = 0;
|
||||
protected long random1 = 0;
|
||||
protected long random2 = 0;
|
||||
|
||||
protected long randomLsbMax;
|
||||
protected long randomMsbMax;
|
||||
protected long randomMax2;
|
||||
protected long randomMax1;
|
||||
|
||||
protected static final long HALF_RANDOM_COMPONENT = 0x000000ffffffffffL;
|
||||
protected static final long INCREMENT_MAX = 0x0000010000000000L;
|
||||
|
||||
protected long previousTimestamp;
|
||||
|
||||
protected Random random;
|
||||
|
||||
protected static final String OVERRUN_MESSAGE = "The system overran the generator by requesting too many GUIDs.";
|
||||
protected static final String OVERRUN_MESSAGE = "The system overran the generator by requesting too many ULIDs.";
|
||||
|
||||
protected TimestampStrategy timestampStrategy;
|
||||
protected RandomStrategy randomStrategy;
|
||||
|
||||
public UlidBasedGuidCreator() {
|
||||
this.reset();
|
||||
public UlidSpecCreator() {
|
||||
this.timestampStrategy = new DefaultTimestampStrategy();
|
||||
this.randomStrategy = new DefaultRandomStrategy();
|
||||
this.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -128,11 +129,11 @@ public class UlidBasedGuidCreator {
|
|||
|
||||
final long timestamp = this.getTimestamp();
|
||||
|
||||
final long randomHi = truncate(randomMsb);
|
||||
final long randomLo = truncate(randomLsb);
|
||||
final long rnd1 = random1 & HALF_RANDOM_COMPONENT;
|
||||
final long rnd2 = random2 & HALF_RANDOM_COMPONENT;
|
||||
|
||||
final long msb = (timestamp << 16) | (randomHi >>> 24);
|
||||
final long lsb = (randomHi << 40) | randomLo;
|
||||
final long msb = (timestamp << 16) | (rnd1 >>> 24);
|
||||
final long lsb = (rnd1 << 40) | rnd2;
|
||||
|
||||
return new UUID(msb, lsb);
|
||||
}
|
||||
|
@ -142,7 +143,10 @@ public class UlidBasedGuidCreator {
|
|||
*
|
||||
* The returning string is encoded to Crockford's base32.
|
||||
*
|
||||
* @return a ULID string
|
||||
* The random component is generated by a secure random number generator:
|
||||
* {@link java.security.SecureRandom}.
|
||||
*
|
||||
* @return a ULID
|
||||
*/
|
||||
public synchronized String createString() {
|
||||
return UlidConverter.toString(create());
|
||||
|
@ -173,17 +177,13 @@ public class UlidBasedGuidCreator {
|
|||
protected synchronized void reset() {
|
||||
|
||||
// Get random values
|
||||
if (random == null) {
|
||||
this.randomMsb = truncate(RandomUtil.get().nextLong());
|
||||
this.randomLsb = truncate(RandomUtil.get().nextLong());
|
||||
} else {
|
||||
this.randomMsb = truncate(random.nextLong());
|
||||
this.randomLsb = truncate(random.nextLong());
|
||||
}
|
||||
|
||||
final byte[] bytes = new byte[10];
|
||||
this.randomStrategy.nextBytes(bytes);
|
||||
this.random1 = UlidUtil.toNumber(bytes, 0, 5);
|
||||
this.random2 = UlidUtil.toNumber(bytes, 5, 10);
|
||||
// Save the random values
|
||||
this.randomMsbMax = this.randomMsb | INCREMENT_MAX;
|
||||
this.randomLsbMax = this.randomLsb | INCREMENT_MAX;
|
||||
this.randomMax1 = this.random1 | INCREMENT_MAX;
|
||||
this.randomMax2 = this.random2 | INCREMENT_MAX;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -196,7 +196,7 @@ public class UlidBasedGuidCreator {
|
|||
*/
|
||||
|
||||
protected synchronized void increment() {
|
||||
if ((++this.randomLsb == this.randomLsbMax) && (++this.randomMsb == this.randomMsbMax)) {
|
||||
if ((++this.random2 > this.randomMax2) && (++this.random1 > this.randomMax1)) {
|
||||
this.reset();
|
||||
throw new UlidCreatorException(OVERRUN_MESSAGE);
|
||||
}
|
||||
|
@ -206,73 +206,58 @@ public class UlidBasedGuidCreator {
|
|||
* Used for changing the timestamp strategy.
|
||||
*
|
||||
* @param timestampStrategy a timestamp strategy
|
||||
* @return {@link UlidBasedGuidCreator}
|
||||
* @return {@link UlidSpecCreator}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public synchronized <T extends UlidBasedGuidCreator> T withTimestampStrategy(TimestampStrategy timestampStrategy) {
|
||||
public synchronized <T extends UlidSpecCreator> T withTimestampStrategy(TimestampStrategy timestampStrategy) {
|
||||
this.timestampStrategy = timestampStrategy;
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the default random generator, in a fluent way, to another that
|
||||
* extends {@link Random}.
|
||||
* Replaces the default random strategy with another.
|
||||
*
|
||||
* The default random generator is {@link java.security.SecureRandom}.
|
||||
*
|
||||
* For other faster pseudo-random generators, see {@link XorshiftRandom} and its
|
||||
* variations.
|
||||
* The default random strategy uses {@link java.security.SecureRandom}.
|
||||
*
|
||||
* See {@link Random}.
|
||||
*
|
||||
* @param random a random generator
|
||||
* @return {@link UlidBasedGuidCreator}
|
||||
* @param <T> the type parameter
|
||||
* @return {@link AbstractRandomBasedUuidCreator}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public synchronized <T extends UlidBasedGuidCreator> T withRandomGenerator(Random random) {
|
||||
this.random = random;
|
||||
public synchronized <T extends UlidSpecCreator> T withRandomStrategy(RandomStrategy randomStrategy) {
|
||||
this.randomStrategy = randomStrategy;
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the default random generator with a faster one.
|
||||
* Replaces the default random strategy with another that uses the input
|
||||
* {@link Random} instance.
|
||||
*
|
||||
* The host fingerprint is used to generate a seed for the random number
|
||||
* generator.
|
||||
* It replaces the internal {@link DefaultRandomStrategy} with
|
||||
* {@link OtherRandomStrategy}.
|
||||
*
|
||||
* See {@link Xorshift128PlusRandom} and
|
||||
* {@link FingerprintUtil#getFingerprint()}
|
||||
*
|
||||
* @return {@link UlidBasedGuidCreator}
|
||||
* @param random a random generator
|
||||
* @return {@link UlidSpecCreator}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public synchronized <T extends UlidBasedGuidCreator> T withFastRandomGenerator() {
|
||||
final int salt = (int) FingerprintUtil.getFingerprint();
|
||||
this.random = new Xorshift128PlusRandom(salt);
|
||||
public synchronized <T extends UlidSpecCreator> T withRandomGenerator(Random random) {
|
||||
this.randomStrategy = new OtherRandomStrategy(random);
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate long to half random component.
|
||||
*
|
||||
* @param value a value to be truncated.
|
||||
* @return truncated value
|
||||
*/
|
||||
protected synchronized long truncate(final long value) {
|
||||
return (value & HALF_RANDOM_COMPONENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* For unit tests
|
||||
*/
|
||||
protected long extractRandomLsb(UUID uuid) {
|
||||
protected long extractRandom1(UUID uuid) {
|
||||
return ((uuid.getMostSignificantBits() & 0x000000000000ffff) << 24) | (uuid.getLeastSignificantBits() >>> 40);
|
||||
}
|
||||
|
||||
/**
|
||||
* For unit tests
|
||||
*/
|
||||
protected long extractRandom2(UUID uuid) {
|
||||
return uuid.getLeastSignificantBits() & HALF_RANDOM_COMPONENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* For unit tests
|
||||
*/
|
||||
protected long extractRandomMsb(UUID uuid) {
|
||||
return ((uuid.getMostSignificantBits() & 0xffff) << 24) | (uuid.getLeastSignificantBits() >>> 40);
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
package com.github.f4b6a3.ulid.exception;
|
||||
|
||||
public class InvalidUlidException extends RuntimeException {
|
||||
public final class InvalidUlidException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
package com.github.f4b6a3.ulid.exception;
|
||||
|
||||
public class UlidCreatorException extends RuntimeException {
|
||||
public final class UlidCreatorException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2018-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.strategy;
|
||||
|
||||
public interface RandomStrategy {
|
||||
void nextBytes(byte[] bytes);
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2018-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.strategy.random;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Random;
|
||||
|
||||
import com.github.f4b6a3.ulid.strategy.RandomStrategy;
|
||||
|
||||
/**
|
||||
* It uses a thread local instance of {@link java.security.SecureRandom}.
|
||||
*/
|
||||
public final class DefaultRandomStrategy implements RandomStrategy {
|
||||
|
||||
protected static final ThreadLocal<Random> THREAD_LOCAL_RANDOM = ThreadLocal.withInitial(SecureRandom::new);
|
||||
|
||||
@Override
|
||||
public void nextBytes(byte[] bytes) {
|
||||
THREAD_LOCAL_RANDOM.get().nextBytes(bytes);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2018-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.strategy.random;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import com.github.f4b6a3.ulid.strategy.RandomStrategy;
|
||||
|
||||
/**
|
||||
* It uses an instance of {@link java.util.Random} injected by constructor.
|
||||
*/
|
||||
public final class OtherRandomStrategy implements RandomStrategy {
|
||||
|
||||
private final Random random;
|
||||
|
||||
public OtherRandomStrategy(Random random) {
|
||||
this.random = random;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nextBytes(byte[] bytes) {
|
||||
this.random.nextBytes(bytes);
|
||||
}
|
||||
}
|
|
@ -26,10 +26,10 @@ package com.github.f4b6a3.ulid.strategy.timestamp;
|
|||
|
||||
import com.github.f4b6a3.ulid.strategy.TimestampStrategy;
|
||||
|
||||
public class DefaultTimestampStrategy implements TimestampStrategy {
|
||||
public final class DefaultTimestampStrategy implements TimestampStrategy {
|
||||
|
||||
/**
|
||||
* Returns the count of milliseconds since 01-01-1970.
|
||||
* Returns the count of milliseconds since 1970-01-01 (Unix epoch).
|
||||
*/
|
||||
@Override
|
||||
public long getTimestamp() {
|
||||
|
|
|
@ -26,9 +26,9 @@ package com.github.f4b6a3.ulid.strategy.timestamp;
|
|||
|
||||
import com.github.f4b6a3.ulid.strategy.TimestampStrategy;
|
||||
|
||||
public class FixedTimestampStretegy implements TimestampStrategy {
|
||||
public final class FixedTimestampStretegy implements TimestampStrategy {
|
||||
|
||||
protected long timestamp = 0;
|
||||
private long timestamp = 0;
|
||||
|
||||
public FixedTimestampStretegy(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
|
|
|
@ -26,10 +26,11 @@ package com.github.f4b6a3.ulid.util;
|
|||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.github.f4b6a3.util.Base32Util;
|
||||
import com.github.f4b6a3.util.ByteUtil;
|
||||
import com.github.f4b6a3.ulid.exception.InvalidUlidException;
|
||||
|
||||
public class UlidConverter {
|
||||
import static com.github.f4b6a3.ulid.util.UlidUtil.*;
|
||||
|
||||
public final class UlidConverter {
|
||||
|
||||
private UlidConverter() {
|
||||
}
|
||||
|
@ -39,8 +40,6 @@ public class UlidConverter {
|
|||
*
|
||||
* The returning string is encoded to Crockford's base32.
|
||||
*
|
||||
* The timestamp and random components are encoded separated.
|
||||
*
|
||||
* @param uuid a UUID
|
||||
* @return a ULID
|
||||
*/
|
||||
|
@ -49,19 +48,20 @@ public class UlidConverter {
|
|||
final long msb = uuid.getMostSignificantBits();
|
||||
final long lsb = uuid.getLeastSignificantBits();
|
||||
|
||||
// Extract timestamp component
|
||||
final long timeNumber = (msb >>> 16);
|
||||
String timestampComponent = leftPad(Base32Util.toBase32Crockford(timeNumber));
|
||||
final long time = ((msb & 0xffffffffffff0000L) >>> 16);
|
||||
final long random1 = ((msb & 0x000000000000ffffL) << 24) | ((lsb & 0xffffff0000000000L) >>> 40);
|
||||
final long random2 = (lsb & 0x000000ffffffffffL);
|
||||
|
||||
// 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);
|
||||
final char[] timeComponent = zerofill(toBase32Crockford(time), 10);
|
||||
final char[] randomComponent1 = zerofill(toBase32Crockford(random1), 8);
|
||||
final char[] randomComponent2 = zerofill(toBase32Crockford(random2), 8);
|
||||
|
||||
return timestampComponent + randomnessComponent;
|
||||
char[] output = new char[ULID_CHAR_LENGTH];
|
||||
System.arraycopy(timeComponent, 0, output, 0, 10);
|
||||
System.arraycopy(randomComponent1, 0, output, 10, 8);
|
||||
System.arraycopy(randomComponent2, 0, output, 18, 8);
|
||||
|
||||
return new String(output);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,34 +70,33 @@ public class UlidConverter {
|
|||
* The input string must be encoded to Crockford's base32, following the ULID
|
||||
* specification.
|
||||
*
|
||||
* The timestamp and random components are decoded separated.
|
||||
*
|
||||
* An exception is thrown if the ULID string is invalid.
|
||||
*
|
||||
* @param ulid a ULID
|
||||
* @return a UUID if valid
|
||||
* @throws InvalidUlidException if invalid
|
||||
*/
|
||||
public static UUID fromString(final String ulid) {
|
||||
|
||||
UlidValidator.validate(ulid);
|
||||
|
||||
// Extract timestamp component
|
||||
final String timestampComponent = ulid.substring(0, 10);
|
||||
final long timeNumber = Base32Util.fromBase32CrockfordAsLong(timestampComponent);
|
||||
final char[] input = ulid.toCharArray();
|
||||
final char[] timeComponent = new char[10];
|
||||
final char[] randomComponent1 = new char[8];
|
||||
final char[] randomComponent2 = new char[8];
|
||||
|
||||
// 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);
|
||||
System.arraycopy(input, 0, timeComponent, 0, 10);
|
||||
System.arraycopy(input, 10, randomComponent1, 0, 8);
|
||||
System.arraycopy(input, 18, randomComponent2, 0, 8);
|
||||
|
||||
final long msb = (timeNumber << 16) | ((randBytes[0] << 8) & 0x0000ff00L) | ((randBytes[1]) & 0x000000ffL);
|
||||
final long lsb = ByteUtil.toNumber(lsbBytes);
|
||||
final long time = fromBase32Crockford(timeComponent);
|
||||
final long random1 = fromBase32Crockford(randomComponent1);
|
||||
final long random2 = fromBase32Crockford(randomComponent2);
|
||||
|
||||
final long msb = ((time & 0x0000ffffffffffffL) << 16) | ((random1 & 0x000000ffff000000L) >>> 24);
|
||||
final long lsb = ((random1 & 0x0000000000ffffffL) << 40) | (random2 & 0x000000ffffffffffL);
|
||||
|
||||
return new UUID(msb, lsb);
|
||||
}
|
||||
|
||||
private static String leftPad(String unpadded) {
|
||||
return "0000000000".substring(unpadded.length()) + unpadded;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,9 +26,14 @@ package com.github.f4b6a3.ulid.util;
|
|||
|
||||
import java.time.Instant;
|
||||
|
||||
import com.github.f4b6a3.util.Base32Util;
|
||||
public final class UlidUtil {
|
||||
|
||||
public class UlidUtil {
|
||||
protected static final int BASE_32 = 32;
|
||||
|
||||
protected static final int ULID_CHAR_LENGTH = 26;
|
||||
|
||||
// Include 'O'->ZERO, 'I'->ONE and 'L'->ONE
|
||||
protected static final char[] ALPHABET_CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZOIL".toCharArray();
|
||||
|
||||
private UlidUtil() {
|
||||
}
|
||||
|
@ -50,11 +55,196 @@ public class UlidUtil {
|
|||
|
||||
public static String extractRandomnessComponent(String ulid) {
|
||||
UlidValidator.validate(ulid);
|
||||
return ulid.substring(10, 26);
|
||||
return ulid.substring(10, ULID_CHAR_LENGTH);
|
||||
}
|
||||
|
||||
protected static long extractUnixMilliseconds(String ulid) {
|
||||
String milliseconds = ulid.substring(0, 10);
|
||||
return Base32Util.fromBase32CrockfordAsLong(milliseconds);
|
||||
return fromBase32Crockford(extractTimestampComponent(ulid).toCharArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a number from a given array of bytes.
|
||||
*
|
||||
* @param bytes a byte array
|
||||
* @return a long
|
||||
*/
|
||||
public static long toNumber(final byte[] bytes) {
|
||||
return toNumber(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
public static long toNumber(final byte[] bytes, final int start, final int end) {
|
||||
long result = 0;
|
||||
for (int i = start; i < end; i++) {
|
||||
result = (result << 8) | (bytes[i] & 0xff);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of bytes from a given number.
|
||||
*
|
||||
* @param number a long value
|
||||
* @return a byte array
|
||||
*/
|
||||
protected static byte[] toBytes(final long number) {
|
||||
return new byte[] { (byte) (number >>> 56), (byte) (number >>> 48), (byte) (number >>> 40),
|
||||
(byte) (number >>> 32), (byte) (number >>> 24), (byte) (number >>> 16), (byte) (number >>> 8),
|
||||
(byte) (number) };
|
||||
}
|
||||
|
||||
protected static char[] removeHyphens(final char[] input) {
|
||||
|
||||
int count = 0;
|
||||
char[] buffer = new char[input.length];
|
||||
|
||||
for (int i = 0; i < input.length; i++) {
|
||||
if ((input[i] != '-')) {
|
||||
buffer[count++] = input[i];
|
||||
}
|
||||
}
|
||||
|
||||
char[] output = new char[count];
|
||||
System.arraycopy(buffer, 0, output, 0, count);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public static char[] toBase32Crockford(long number) {
|
||||
return encode(number, UlidUtil.ALPHABET_CROCKFORD);
|
||||
}
|
||||
|
||||
public static long fromBase32Crockford(char[] chars) {
|
||||
return decode(chars, UlidUtil.ALPHABET_CROCKFORD);
|
||||
}
|
||||
|
||||
protected static boolean isCrockfordBase32(final char[] chars) {
|
||||
char[] input = toUpperCase(chars);
|
||||
for (int i = 0; i < input.length; i++) {
|
||||
if (!isCrockfordBase32(input[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected static boolean isCrockfordBase32(char c) {
|
||||
for (int j = 0; j < ALPHABET_CROCKFORD.length; j++) {
|
||||
if (c == ALPHABET_CROCKFORD[j]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static char[] zerofill(char[] chars, int length) {
|
||||
return lpad(chars, length, '0');
|
||||
}
|
||||
|
||||
protected static char[] lpad(char[] chars, int length, char fill) {
|
||||
|
||||
int delta = 0;
|
||||
int limit = 0;
|
||||
|
||||
if (length > chars.length) {
|
||||
delta = length - chars.length;
|
||||
limit = length;
|
||||
} else {
|
||||
delta = 0;
|
||||
limit = chars.length;
|
||||
}
|
||||
|
||||
char[] output = new char[chars.length + delta];
|
||||
for (int i = 0; i < limit; i++) {
|
||||
if (i < delta) {
|
||||
output[i] = fill;
|
||||
} else {
|
||||
output[i] = chars[i - delta];
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
protected static char[] transliterate(char[] chars, char[] alphabet1, char[] alphabet2) {
|
||||
char[] output = chars.clone();
|
||||
for (int i = 0; i < output.length; i++) {
|
||||
for (int j = 0; j < alphabet1.length; j++) {
|
||||
if (output[i] == alphabet1[j]) {
|
||||
output[i] = alphabet2[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
protected static char[] toUpperCase(final char[] chars) {
|
||||
char[] output = new char[chars.length];
|
||||
for (int i = 0; i < output.length; i++) {
|
||||
if (chars[i] >= 0x61 && chars[i] <= 0x7a) {
|
||||
output[i] = (char) ((int) chars[i] & 0xffffffdf);
|
||||
} else {
|
||||
output[i] = chars[i];
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a long number to base 32 char array.
|
||||
*
|
||||
* @param number a long number
|
||||
* @param alphabet an alphabet
|
||||
* @return a base32 encoded char array
|
||||
*/
|
||||
protected static char[] encode(long number, char[] alphabet) {
|
||||
|
||||
final int CHARS_MAX = 13; // 13 * 5 = 65
|
||||
|
||||
if (number < 0) {
|
||||
throw new IllegalArgumentException(String.format("Number '%d' is not a positive integer.", number));
|
||||
}
|
||||
|
||||
long n = number;
|
||||
char[] buffer = new char[CHARS_MAX];
|
||||
char[] output;
|
||||
|
||||
int count = CHARS_MAX;
|
||||
while (n > 0) {
|
||||
buffer[--count] = alphabet[(int) (n % BASE_32)];
|
||||
n = n / BASE_32;
|
||||
}
|
||||
|
||||
output = new char[buffer.length - count];
|
||||
System.arraycopy(buffer, count, output, 0, output.length);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a base 32 char array to a long number.
|
||||
*
|
||||
* @param chars a base 32 encoded char array
|
||||
* @param alphabet an alphabet
|
||||
* @return a long number
|
||||
*/
|
||||
protected static long decode(char[] chars, char[] alphabet) {
|
||||
|
||||
long n = 0;
|
||||
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
int d = chr(chars[i], alphabet);
|
||||
n = BASE_32 * n + d;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
private static int chr(char c, char[] alphabet) {
|
||||
for (int i = 0; i < alphabet.length; i++) {
|
||||
if (alphabet[i] == c) {
|
||||
return (byte) i;
|
||||
}
|
||||
}
|
||||
return (byte) '0';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,11 +26,11 @@ package com.github.f4b6a3.ulid.util;
|
|||
|
||||
import com.github.f4b6a3.ulid.exception.InvalidUlidException;
|
||||
|
||||
public class UlidValidator {
|
||||
import static com.github.f4b6a3.ulid.util.UlidUtil.*;
|
||||
|
||||
protected static final String ULID_PATTERN = "^[0-9a-tv-zA-TV-Z]{26}$";
|
||||
public final class UlidValidator {
|
||||
|
||||
// Date: 10889-08-02T05:31:50.655Z
|
||||
// Date: 10889-08-02T05:31:50.655Z (epoch time: 281474976710655)
|
||||
protected static final long TIMESTAMP_MAX = (long) Math.pow(2, 48) - 1;
|
||||
|
||||
private UlidValidator() {
|
||||
|
@ -42,14 +42,12 @@ public class UlidValidator {
|
|||
* A valid ULID string is a sequence of 26 characters from Crockford's base 32
|
||||
* alphabet.
|
||||
*
|
||||
* Dashes are ignored by this validator.
|
||||
*
|
||||
* <pre>
|
||||
* Examples of valid ULID strings:
|
||||
* - 0123456789ABCDEFGHJKMNPKRS (26 alphanumeric, case insensitive, except iI, lL, oO and uU)
|
||||
* - 0123456789ABCDEFGHIJKLMNOP (26 alphanumeric, case insensitive, except uU)
|
||||
* - 0123456789-ABCDEFGHJK-MNPKRS (26 alphanumeric, case insensitive, except iI, lL, oO and uU)
|
||||
* - 0123456789-ABCDEFGHIJ-KLMNOP (26 alphanumeric, case insensitive, except uU, with dashes)
|
||||
* - 0123456789ABCDEFGHJKMNPKRS (26 alphanumeric, case insensitive, except U)
|
||||
* - 0123456789ABCDEFGHIJKLMNOP (26 alphanumeric, case insensitive, including OIL, except U)
|
||||
* - 0123456789-ABCDEFGHJK-MNPKRS (26 alphanumeric, case insensitive, except U, with hyphens)
|
||||
* - 0123456789-ABCDEFGHIJ-KLMNOP (26 alphanumeric, case insensitive, including OIL, except U, with hyphens)
|
||||
* </pre>
|
||||
*
|
||||
* @param ulid a ULID
|
||||
|
@ -57,16 +55,20 @@ public class UlidValidator {
|
|||
*/
|
||||
public static boolean isValid(String ulid) {
|
||||
|
||||
if (ulid == null || ulid.isEmpty()) {
|
||||
if (ulid == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String u = ulid.replaceAll("-", "");
|
||||
if (!u.matches(ULID_PATTERN)) {
|
||||
char[] chars = removeHyphens(ulid.toCharArray());
|
||||
if (chars.length != ULID_CHAR_LENGTH || !isCrockfordBase32(chars)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long timestamp = UlidUtil.extractUnixMilliseconds(ulid);
|
||||
// Extract time component
|
||||
final char[] timestampComponent = new char[10];
|
||||
System.arraycopy(chars, 0, timestampComponent, 0, 10);
|
||||
final long timestamp = fromBase32Crockford(timestampComponent);
|
||||
|
||||
return timestamp >= 0 && timestamp <= TIMESTAMP_MAX;
|
||||
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package com.github.f4b6a3.ulid;
|
|||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Suite;
|
||||
|
||||
import com.github.f4b6a3.ulid.creator.UlidBasedGuidCreatorTest;
|
||||
import com.github.f4b6a3.ulid.creator.UlidSpecCreatorTest;
|
||||
import com.github.f4b6a3.ulid.ulid.UlidCreatorTest;
|
||||
import com.github.f4b6a3.ulid.util.UlidConverterTest;
|
||||
import com.github.f4b6a3.ulid.util.UlidUtilTest;
|
||||
|
@ -12,7 +12,7 @@ import com.github.f4b6a3.ulid.util.UlidValidatorTest;
|
|||
@RunWith(Suite.class)
|
||||
@Suite.SuiteClasses({
|
||||
UlidCreatorTest.class,
|
||||
UlidBasedGuidCreatorTest.class,
|
||||
UlidSpecCreatorTest.class,
|
||||
UlidConverterTest.class,
|
||||
UlidUtilTest.class,
|
||||
UlidValidatorTest.class,
|
||||
|
|
|
@ -4,7 +4,7 @@ import java.util.HashSet;
|
|||
import java.util.UUID;
|
||||
|
||||
import com.github.f4b6a3.ulid.UlidCreator;
|
||||
import com.github.f4b6a3.ulid.creator.UlidBasedGuidCreator;
|
||||
import com.github.f4b6a3.ulid.creator.UlidSpecCreator;
|
||||
import com.github.f4b6a3.ulid.exception.UlidCreatorException;
|
||||
import com.github.f4b6a3.ulid.strategy.timestamp.FixedTimestampStretegy;
|
||||
|
||||
|
@ -27,7 +27,7 @@ public class UniquenessTest {
|
|||
private boolean verbose; // Show progress or not
|
||||
|
||||
// GUID creator based on ULID spec
|
||||
private UlidBasedGuidCreator creator;
|
||||
private UlidSpecCreator creator;
|
||||
|
||||
/**
|
||||
* Initialize the test.
|
||||
|
@ -36,7 +36,7 @@ public class UniquenessTest {
|
|||
* @param requestCount
|
||||
* @param creator
|
||||
*/
|
||||
public UniquenessTest(int threadCount, int requestCount, UlidBasedGuidCreator creator, boolean progress) {
|
||||
public UniquenessTest(int threadCount, int requestCount, UlidSpecCreator creator, boolean progress) {
|
||||
this.threadCount = threadCount;
|
||||
this.requestCount = requestCount;
|
||||
this.creator = creator;
|
||||
|
@ -125,7 +125,7 @@ public class UniquenessTest {
|
|||
}
|
||||
|
||||
public static void execute(boolean verbose, int threadCount, int requestCount) {
|
||||
UlidBasedGuidCreator creator = UlidCreator.getUlidBasedCreator()
|
||||
UlidSpecCreator creator = UlidCreator.getUlidSpecCreator()
|
||||
.withTimestampStrategy(new FixedTimestampStretegy(System.currentTimeMillis()));
|
||||
|
||||
UniquenessTest test = new UniquenessTest(threadCount, requestCount, creator, verbose);
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
package com.github.f4b6a3.ulid.creator;
|
||||
|
||||
import com.github.f4b6a3.ulid.creator.UlidBasedGuidCreator;
|
||||
|
||||
class UlidBasedGuidCreatorMock extends UlidBasedGuidCreator {
|
||||
|
||||
public UlidBasedGuidCreatorMock(long previousTimestamp) {
|
||||
super();
|
||||
this.previousTimestamp = previousTimestamp;
|
||||
}
|
||||
|
||||
public UlidBasedGuidCreatorMock(long randomMsb, long randomLsb, long randomMsbMax, long randomLsbMax, long previousTimestamp) {
|
||||
|
||||
this.randomMsb = randomMsb;
|
||||
this.randomLsb = randomLsb;
|
||||
|
||||
this.randomMsbMax = randomMsbMax;
|
||||
this.randomLsbMax = randomLsbMax;
|
||||
|
||||
this.previousTimestamp = previousTimestamp;
|
||||
}
|
||||
|
||||
public long getRandomMsb() {
|
||||
return this.randomMsb;
|
||||
}
|
||||
|
||||
public long getRandomLsb() {
|
||||
return this.randomLsb;
|
||||
}
|
||||
|
||||
public long getRandomHiMax() {
|
||||
return this.randomMsb;
|
||||
}
|
||||
|
||||
public long getRandomLoMax() {
|
||||
return this.randomLsb;
|
||||
}
|
||||
}
|
|
@ -1,168 +0,0 @@
|
|||
package com.github.f4b6a3.ulid.creator;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.github.f4b6a3.util.random.Xorshift128PlusRandom;
|
||||
import com.github.f4b6a3.ulid.exception.UlidCreatorException;
|
||||
import com.github.f4b6a3.ulid.strategy.timestamp.FixedTimestampStretegy;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class UlidBasedGuidCreatorTest {
|
||||
|
||||
private static final long DEFAULT_LOOP_MAX = 1_000_000;
|
||||
|
||||
private static final long TIMESTAMP = System.currentTimeMillis();
|
||||
|
||||
private static final Random RANDOM = new Xorshift128PlusRandom();
|
||||
|
||||
@Test
|
||||
public void testRandomMostSignificantBits() {
|
||||
|
||||
UlidBasedGuidCreatorMock creator = new UlidBasedGuidCreatorMock(TIMESTAMP);
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||
|
||||
UUID uuid = creator.create();
|
||||
long firstMsb = creator.extractRandomMsb(uuid);
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
uuid = creator.create();
|
||||
|
||||
}
|
||||
|
||||
long lastMsb = creator.extractRandomMsb(uuid);
|
||||
long expectedMsb = firstMsb;
|
||||
assertEquals(String.format("The last MSB should be iqual to the first %s.", expectedMsb), expectedMsb, lastMsb);
|
||||
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP + 1));
|
||||
uuid = creator.create();
|
||||
lastMsb = uuid.getMostSignificantBits();
|
||||
assertNotEquals("The last MSB should be random after timestamp changed.", firstMsb, lastMsb);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRandomLeastSignificantBits() {
|
||||
|
||||
UlidBasedGuidCreatorMock creator = new UlidBasedGuidCreatorMock(TIMESTAMP);
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||
|
||||
UUID uuid = creator.create();
|
||||
long firstLsb = creator.extractRandomLsb(uuid);
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
uuid = creator.create();
|
||||
}
|
||||
|
||||
long lastLsb = creator.extractRandomLsb(uuid);
|
||||
long expected = firstLsb + DEFAULT_LOOP_MAX;
|
||||
assertEquals(String.format("The last LSB should be iqual to %s.", expected), expected, lastLsb);
|
||||
|
||||
long notExpected = firstLsb + DEFAULT_LOOP_MAX + 1;
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP + 1));
|
||||
uuid = creator.create();
|
||||
lastLsb = uuid.getLeastSignificantBits();
|
||||
assertNotEquals("The last LSB should be random after timestamp changed.", notExpected, lastLsb);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncrementOfRandomLeastSignificantBits() {
|
||||
|
||||
UlidBasedGuidCreatorMock creator = new UlidBasedGuidCreatorMock(TIMESTAMP);
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||
|
||||
long lsb = creator.getRandomLsb();
|
||||
|
||||
UUID uuid = new UUID(0, 0);
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
uuid = creator.create();
|
||||
}
|
||||
|
||||
long expectedLsb = lsb + DEFAULT_LOOP_MAX;
|
||||
long randomLsb = creator.getRandomLsb();
|
||||
assertEquals("Wrong LSB after loop.", expectedLsb, randomLsb);
|
||||
|
||||
randomLsb = creator.extractRandomLsb(uuid);
|
||||
assertEquals("Wrong LSB after loop.", expectedLsb, randomLsb);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncrementOfRandomMostSignificantBits() {
|
||||
|
||||
UlidBasedGuidCreatorMock creator = new UlidBasedGuidCreatorMock(TIMESTAMP);
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||
|
||||
long msb = creator.getRandomMsb();
|
||||
|
||||
UUID uuid = new UUID(0, 0);
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
uuid = creator.create();
|
||||
}
|
||||
|
||||
long expectedMsb = msb;
|
||||
long randomMsb = creator.getRandomMsb();
|
||||
assertEquals("Wrong MSB after loop.", expectedMsb, randomMsb);
|
||||
|
||||
randomMsb = creator.extractRandomMsb(uuid);
|
||||
assertEquals("Wrong MSB after loop.", expectedMsb, randomMsb);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldThrowOverflowException1() {
|
||||
|
||||
long msbMax = 0x000001ffffffffffL;
|
||||
long lsbMax = 0x000001ffffffffffL;
|
||||
|
||||
long msb = msbMax - 1;
|
||||
long lsb = lsbMax - DEFAULT_LOOP_MAX;
|
||||
|
||||
UlidBasedGuidCreatorMock creator = new UlidBasedGuidCreatorMock(msb, lsb, msbMax, lsbMax, TIMESTAMP);
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX - 1; i++) {
|
||||
creator.create();
|
||||
}
|
||||
|
||||
try {
|
||||
creator.create();
|
||||
fail("It should throw an overflow exception.");
|
||||
} catch (UlidCreatorException e) {
|
||||
// success
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldThrowOverflowException2() {
|
||||
|
||||
long msbMax = (RANDOM.nextLong() & UlidBasedGuidCreatorMock.HALF_RANDOM_COMPONENT)
|
||||
| UlidBasedGuidCreatorMock.INCREMENT_MAX;
|
||||
long lsbMax = (RANDOM.nextLong() & UlidBasedGuidCreatorMock.HALF_RANDOM_COMPONENT)
|
||||
| UlidBasedGuidCreatorMock.INCREMENT_MAX;
|
||||
|
||||
long msb = msbMax - 1;
|
||||
long lsb = lsbMax - DEFAULT_LOOP_MAX;
|
||||
|
||||
UlidBasedGuidCreatorMock creator = new UlidBasedGuidCreatorMock(msb, lsb, msbMax, lsbMax, TIMESTAMP);
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||
|
||||
UUID uuid = new UUID(0, 0);
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX - 1; i++) {
|
||||
uuid = creator.create();
|
||||
}
|
||||
|
||||
long expectedLsb = (lsbMax - 1) & UlidBasedGuidCreatorMock.HALF_RANDOM_COMPONENT;
|
||||
long randomLsb = creator.extractRandomLsb(uuid);
|
||||
assertEquals("Incorrect LSB after loop.", expectedLsb, randomLsb);
|
||||
|
||||
long expectedMsb = (msbMax - 1) & UlidBasedGuidCreatorMock.HALF_RANDOM_COMPONENT;
|
||||
long randomMsb = creator.extractRandomMsb(uuid);
|
||||
assertEquals("Incorrect MSB after loop.", expectedMsb, randomMsb);
|
||||
|
||||
try {
|
||||
creator.create();
|
||||
fail("It should throw an overflow exception.");
|
||||
} catch (UlidCreatorException e) {
|
||||
// success
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package com.github.f4b6a3.ulid.creator;
|
||||
|
||||
import com.github.f4b6a3.ulid.creator.UlidSpecCreator;
|
||||
|
||||
class UlidSpecCreatorMock extends UlidSpecCreator {
|
||||
|
||||
public UlidSpecCreatorMock(long previousTimestamp) {
|
||||
super();
|
||||
this.previousTimestamp = previousTimestamp;
|
||||
}
|
||||
|
||||
public UlidSpecCreatorMock(long random1, long random2, long randomMax1, long randomMax2, long previousTimestamp) {
|
||||
|
||||
this.random1 = random1;
|
||||
this.random2 = random2;
|
||||
|
||||
this.randomMax1 = randomMax1;
|
||||
this.randomMax2 = randomMax2;
|
||||
|
||||
this.previousTimestamp = previousTimestamp;
|
||||
}
|
||||
|
||||
public long getRandom1() {
|
||||
return this.random1;
|
||||
}
|
||||
|
||||
public long getRandom2() {
|
||||
return this.random2;
|
||||
}
|
||||
|
||||
public long getRandomMax1() {
|
||||
return this.random1;
|
||||
}
|
||||
|
||||
public long getRandomMax2() {
|
||||
return this.random2;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,255 @@
|
|||
package com.github.f4b6a3.ulid.creator;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.HashSet;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
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.*;
|
||||
|
||||
public class UlidSpecCreatorTest {
|
||||
|
||||
private static final int DEFAULT_LOOP_MAX = 1_000_000;
|
||||
|
||||
private static final long TIMESTAMP = System.currentTimeMillis();
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
protected static final String DUPLICATE_UUID_MSG = "A duplicate ULID was created";
|
||||
|
||||
protected static final int THREAD_TOTAL = availableProcessors();
|
||||
|
||||
private static int availableProcessors() {
|
||||
int processors = Runtime.getRuntime().availableProcessors();
|
||||
if (processors < 4) {
|
||||
processors = 4;
|
||||
}
|
||||
return processors;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRandomMostSignificantBits() {
|
||||
|
||||
UlidSpecCreatorMock creator = new UlidSpecCreatorMock(TIMESTAMP);
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||
|
||||
UUID uuid = creator.create();
|
||||
long firstRand1 = creator.extractRandom1(uuid);
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
uuid = creator.create();
|
||||
|
||||
}
|
||||
|
||||
long lastRand1 = creator.extractRandom1(uuid);
|
||||
long expected1 = firstRand1;
|
||||
assertEquals(String.format("The last high random should be iqual to the first %s.", expected1), expected1,
|
||||
lastRand1);
|
||||
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP + 1));
|
||||
uuid = creator.create();
|
||||
lastRand1 = uuid.getMostSignificantBits();
|
||||
assertNotEquals("The last high random should be random after timestamp changed.", firstRand1, lastRand1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRandomLeastSignificantBits() {
|
||||
|
||||
UlidSpecCreatorMock creator = new UlidSpecCreatorMock(TIMESTAMP);
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||
|
||||
UUID uuid = creator.create();
|
||||
long firstRnd2 = creator.extractRandom2(uuid);
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
uuid = creator.create();
|
||||
}
|
||||
|
||||
long lastRand2 = creator.extractRandom2(uuid);
|
||||
long expected = firstRnd2 + DEFAULT_LOOP_MAX;
|
||||
assertEquals(String.format("The last low random should be iqual to %s.", expected), expected, lastRand2);
|
||||
|
||||
long notExpected = firstRnd2 + DEFAULT_LOOP_MAX + 1;
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP + 1));
|
||||
uuid = creator.create();
|
||||
lastRand2 = uuid.getLeastSignificantBits();
|
||||
assertNotEquals("The last low random should be random after timestamp changed.", notExpected, lastRand2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncrementOfRandomLeastSignificantBits() {
|
||||
|
||||
UlidSpecCreatorMock creator = new UlidSpecCreatorMock(TIMESTAMP);
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||
|
||||
long random2 = creator.getRandom2();
|
||||
|
||||
UUID uuid = new UUID(0, 0);
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
uuid = creator.create();
|
||||
}
|
||||
|
||||
long expected2 = random2 + DEFAULT_LOOP_MAX;
|
||||
long rand2 = creator.getRandom2();
|
||||
assertEquals("Wrong low random after loop.", expected2, rand2);
|
||||
|
||||
rand2 = creator.extractRandom2(uuid);
|
||||
assertEquals("Wrong low random after loop.", expected2, rand2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncrementOfRandomMostSignificantBits() {
|
||||
|
||||
UlidSpecCreatorMock creator = new UlidSpecCreatorMock(TIMESTAMP);
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||
|
||||
long random1 = creator.getRandom1();
|
||||
|
||||
UUID uuid = new UUID(0, 0);
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
uuid = creator.create();
|
||||
}
|
||||
|
||||
long expected1 = random1;
|
||||
long rand1 = creator.getRandom1();
|
||||
assertEquals("Wrong high random after loop.", expected1, rand1);
|
||||
|
||||
rand1 = creator.extractRandom1(uuid);
|
||||
assertEquals("Wrong high random after loop.", expected1, rand1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldThrowOverflowException1() {
|
||||
|
||||
long random1 = 0x000000ffffffffffL;
|
||||
long random2 = 0x000000ffffffffffL;
|
||||
|
||||
long max1 = random1 | UlidSpecCreatorMock.INCREMENT_MAX;
|
||||
long max2 = random2 | UlidSpecCreatorMock.INCREMENT_MAX;
|
||||
|
||||
random1 = max1;
|
||||
random2 = max2 - DEFAULT_LOOP_MAX;
|
||||
|
||||
UlidSpecCreatorMock creator = new UlidSpecCreatorMock(random1, random2, max1, max2, TIMESTAMP);
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||
|
||||
UUID uuid = new UUID(0, 0);
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
uuid = creator.create();
|
||||
}
|
||||
|
||||
long hi1 = random1 & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT;
|
||||
long lo1 = random2 & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT;
|
||||
String concat1 = (Long.toHexString(hi1) + Long.toHexString(lo1));
|
||||
BigInteger bigint1 = new BigInteger(concat1, 16);
|
||||
long hi2 = creator.extractRandom1(uuid);
|
||||
long lo2 = creator.extractRandom2(uuid);
|
||||
String concat2 = (Long.toHexString(hi2) + Long.toHexString(lo2));
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldThrowOverflowException2() {
|
||||
|
||||
long random1 = (RANDOM.nextLong() & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT);
|
||||
long random2 = (RANDOM.nextLong() & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT);
|
||||
|
||||
long max1 = random1 | UlidSpecCreatorMock.INCREMENT_MAX;
|
||||
long max2 = random2 | UlidSpecCreatorMock.INCREMENT_MAX;
|
||||
|
||||
random1 = max1;
|
||||
random2 = max2 - DEFAULT_LOOP_MAX;
|
||||
|
||||
UlidSpecCreatorMock creator = new UlidSpecCreatorMock(random1, random2, max1, max2, TIMESTAMP);
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||
|
||||
UUID uuid = new UUID(0, 0);
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
uuid = creator.create();
|
||||
}
|
||||
|
||||
long rand1 = creator.extractRandom1(uuid);
|
||||
long expected1 = (max1 & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT);
|
||||
assertEquals("Incorrect high random after loop.", expected1, rand1);
|
||||
|
||||
long rand2 = creator.extractRandom2(uuid);
|
||||
long expected2 = (max2 & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT);
|
||||
assertEquals("Incorrect low random after loop.", expected2, rand2);
|
||||
|
||||
long hi1 = random1 & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT;
|
||||
long lo1 = random2 & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT;
|
||||
String concat1 = (Long.toHexString(hi1) + Long.toHexString(lo1));
|
||||
BigInteger bigint1 = new BigInteger(concat1, 16);
|
||||
long hi2 = creator.extractRandom1(uuid);
|
||||
long lo2 = creator.extractRandom2(uuid);
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUlidParallelGeneratorsShouldCreateUniqueUlids() throws InterruptedException {
|
||||
|
||||
Thread[] threads = new Thread[THREAD_TOTAL];
|
||||
TestThread.clearHashSet();
|
||||
|
||||
// Instantiate and start many threads
|
||||
for (int i = 0; i < THREAD_TOTAL; i++) {
|
||||
threads[i] = new TestThread(UlidCreator.getUlidSpecCreator(), DEFAULT_LOOP_MAX);
|
||||
threads[i].start();
|
||||
}
|
||||
|
||||
// Wait all the threads to finish
|
||||
for (Thread thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
// Check if the quantity of unique UUIDs is correct
|
||||
assertEquals(DUPLICATE_UUID_MSG, TestThread.hashSet.size(), (DEFAULT_LOOP_MAX * THREAD_TOTAL));
|
||||
}
|
||||
|
||||
public static class TestThread extends Thread {
|
||||
|
||||
public static Set<UUID> hashSet = new HashSet<>();
|
||||
private UlidSpecCreator creator;
|
||||
private int loopLimit;
|
||||
|
||||
public TestThread(UlidSpecCreator creator, int loopLimit) {
|
||||
this.creator = creator;
|
||||
this.loopLimit = loopLimit;
|
||||
}
|
||||
|
||||
public static void clearHashSet() {
|
||||
hashSet = new HashSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for (int i = 0; i < loopLimit; i++) {
|
||||
synchronized (hashSet) {
|
||||
hashSet.add(creator.create());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,37 +1,20 @@
|
|||
package com.github.f4b6a3.ulid.ulid;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.github.f4b6a3.ulid.UlidCreator;
|
||||
import com.github.f4b6a3.ulid.creator.UlidBasedGuidCreator;
|
||||
import com.github.f4b6a3.ulid.util.UlidUtil;
|
||||
import com.github.f4b6a3.ulid.util.UlidValidator;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public class UlidCreatorTest {
|
||||
|
||||
private static int processors;
|
||||
|
||||
private static final int ULID_LENGTH = 26;
|
||||
private static final int DEFAULT_LOOP_MAX = 100_000;
|
||||
|
||||
private static final String DUPLICATE_UUID_MSG = "A duplicate ULID was created";
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
|
||||
processors = Runtime.getRuntime().availableProcessors();
|
||||
if (processors < 4) {
|
||||
processors = 4;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUlid() {
|
||||
String[] list = new String[DEFAULT_LOOP_MAX];
|
||||
|
@ -52,9 +35,9 @@ public class UlidCreatorTest {
|
|||
|
||||
private void checkNullOrInvalid(String[] list) {
|
||||
for (String ulid : list) {
|
||||
assertTrue("ULID is null", ulid != null);
|
||||
assertNotNull("ULID is null", ulid);
|
||||
assertTrue("ULID is empty", !ulid.isEmpty());
|
||||
assertTrue("ULID length is wrong ", ulid.length() == ULID_LENGTH);
|
||||
assertEquals("ULID length is wrong", ULID_LENGTH, ulid.length());
|
||||
assertTrue("ULID is not valid", UlidValidator.isValid(ulid));
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +50,7 @@ public class UlidCreatorTest {
|
|||
assertTrue(String.format("ULID is duplicated %s", ulid), set.add(ulid));
|
||||
}
|
||||
|
||||
assertTrue("There are duplicated ULIDs", set.size() == list.length);
|
||||
assertEquals("There are duplicated ULIDs", set.size(), list.length);
|
||||
}
|
||||
|
||||
private void checkCreationTime(String[] list, long startTime, long endTime) {
|
||||
|
@ -87,53 +70,7 @@ public class UlidCreatorTest {
|
|||
Arrays.sort(other);
|
||||
|
||||
for (int i = 0; i < list.length; i++) {
|
||||
assertTrue("The ULID list is not ordered", list[i].equals(other[i]));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUlidBasedGuidParallelGeneratorsShouldCreateUniqueUuids() throws InterruptedException {
|
||||
|
||||
Thread[] threads = new Thread[processors];
|
||||
TestThread.clearHashSet();
|
||||
|
||||
// Instantiate and start many threads
|
||||
for (int i = 0; i < processors; i++) {
|
||||
threads[i] = new TestThread(UlidCreator.getUlidBasedCreator(), DEFAULT_LOOP_MAX);
|
||||
threads[i].start();
|
||||
}
|
||||
|
||||
// Wait all the threads to finish
|
||||
for (Thread thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
// Check if the quantity of unique UUIDs is correct
|
||||
assertTrue(DUPLICATE_UUID_MSG, TestThread.hashSet.size() == (DEFAULT_LOOP_MAX * processors));
|
||||
}
|
||||
|
||||
private static class TestThread extends Thread {
|
||||
|
||||
private static Set<UUID> hashSet = new HashSet<>();
|
||||
private UlidBasedGuidCreator creator;
|
||||
private int loopLimit;
|
||||
|
||||
public TestThread(UlidBasedGuidCreator creator, int loopLimit) {
|
||||
this.creator = creator;
|
||||
this.loopLimit = loopLimit;
|
||||
}
|
||||
|
||||
public static void clearHashSet() {
|
||||
hashSet = new HashSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for (int i = 0; i < loopLimit; i++) {
|
||||
synchronized (hashSet) {
|
||||
hashSet.add(creator.create());
|
||||
}
|
||||
}
|
||||
assertEquals("The ULID list is not ordered", list[i], other[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,9 +22,9 @@ public class UlidConverterTest {
|
|||
UUID uuid1 = UlidCreator.getUlid();
|
||||
String ulid = UlidConverter.toString(uuid1);
|
||||
|
||||
assertTrue("ULID is null", ulid != null);
|
||||
assertNotNull("ULID is null", ulid);
|
||||
assertTrue("ULID is empty", !ulid.isEmpty());
|
||||
assertTrue("ULID length is wrong ", ulid.length() == ULID_LENGTH);
|
||||
assertEquals("ULID length is wrong ", ULID_LENGTH, ulid.length());
|
||||
assertTrue("ULID is not valid", UlidValidator.isValid(ulid));
|
||||
|
||||
UUID uuid2 = UlidConverter.fromString(ulid);
|
||||
|
|
|
@ -5,10 +5,8 @@ import java.time.Instant;
|
|||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.github.f4b6a3.util.Base32Util;
|
||||
import com.github.f4b6a3.util.ByteUtil;
|
||||
import com.github.f4b6a3.ulid.exception.InvalidUlidException;
|
||||
import com.github.f4b6a3.ulid.util.UlidUtil;
|
||||
import static com.github.f4b6a3.ulid.util.UlidUtil.*;
|
||||
|
||||
public class UlidUtilTest {
|
||||
|
||||
|
@ -21,20 +19,43 @@ public class UlidUtilTest {
|
|||
private static final String[] EXAMPLE_DATES = { "1970-01-01T00:00:00.000Z", "1985-10-26T01:16:00.123Z",
|
||||
"2001-09-09T01:46:40.456Z", "2020-01-15T14:30:33.789Z", "2038-01-19T03:14:07.321Z" };
|
||||
|
||||
private static final int[] NUMBERS = { 102685630, 725393777, 573697669, 614668535, 790665079, 728958755, 966150230,
|
||||
410015018, 605266173, 946077566, 214051168, 775737014, 723003700, 391609366, 147844737, 514081413,
|
||||
488279622, 550860813, 611087782, 223492126, 706308515, 158990768, 549042286, 26926303, 775714134, 602886016,
|
||||
27282100, 675097356, 641101167, 515280699, 454184468, 371424784, 633917378, 887459583, 792903202, 168552040,
|
||||
824806922, 696445335, 653338746, 357696553, 353677217, 972662902, 400738139, 537701151, 202077579,
|
||||
110209145, 356152341, 168702810, 684185451, 419840003, 480132486, 308833881, 997154252, 918202260,
|
||||
103304091, 328467776, 648729690, 733655121, 645189051, 342500864, 560919543, 509761384, 626871960,
|
||||
429248550, 319025067, 507317265, 348303729, 256009160, 660250872, 85224414, 414490625, 355994979, 318005886,
|
||||
326093128, 492813589, 569014099, 503350412, 168303553, 801566586, 800368918, 742601973, 395588591,
|
||||
257341245, 722366808, 501878988, 200718306, 184948029, 149469829, 992401543, 240364551, 976817281,
|
||||
161998068, 515579566, 275182272, 376045488, 899163436, 941443452, 974372015, 934795357, 958806784 };
|
||||
|
||||
private static final String[] NUMBERS_BASE_32_CROCKFORD = { "31XPXY", "NKS8BH", "H33VM5", "JA667Q", "QJ15VQ",
|
||||
"NQ61S3", "WSCJ2P", "C70N9A", "J1787X", "W67ZVY", "6C4AB0", "Q3SKNP", "NHGA9M", "BNEZ0P", "4CZVM1",
|
||||
"FA8GM5", "EHN3J6", "GDAY0D", "J6RXD6", "6N4E0Y", "N1JTD3", "4QM0DG", "GBKE3E", "SNQ6Z", "Q3RXAP", "HYYKW0",
|
||||
"T0JNM", "M3TARC", "K3CVBF", "FBD3SV", "DH4KGM", "B26ZGG", "JWHKY2", "TEB3QZ", "QM5FH2", "50QSK8", "RJK3GA",
|
||||
"MR5TCQ", "KF2A3T", "AN4119", "AH9BX1", "WZKA3P", "BY5HTV", "G0SARZ", "60PXCB", "393A3S", "AKMX0N",
|
||||
"50WCTT", "MCFNVB", "CGCG03", "E9WFC6", "96GVJS", "XPYQEC", "VBN9WM", "32GJWV", "9S81A0", "KANN2T",
|
||||
"NVNC2H", "K79KDV", "A6M9G0", "GPXWZQ", "F64NV8", "JNTKMR", "CSBM16", "9G7VXB", "F3T30H", "AC5CBH",
|
||||
"7M4RY8", "KNN87R", "2H8TYY", "CB9801", "AKG3B3", "9F8RKY", "9PZJA8", "ENZF8N", "GYMXTK", "F0114C",
|
||||
"50G6Y1", "QWDVVT", "QV9A8P", "P46D7N", "BS8CZF", "7NDDSX", "NGWWAR", "EYM46C", "5ZDDZ2", "5GC59X",
|
||||
"4EHEM5", "XJDP47", "757B07", "X3J341", "4TFS7M", "FBP7NE", "86DWP0", "B6KZXG", "TSG99C", "W1TJBW",
|
||||
"X17F5F", "VVFP2X", "WJCER0" };
|
||||
|
||||
@Test(expected = InvalidUlidException.class)
|
||||
public void testExtractTimestamp() {
|
||||
|
||||
String ulid = "0000000000" + EXAMPLE_RANDOMNESS;
|
||||
long milliseconds = UlidUtil.extractTimestamp(ulid);
|
||||
long milliseconds = extractTimestamp(ulid);
|
||||
assertEquals(0, milliseconds);
|
||||
|
||||
ulid = "7ZZZZZZZZZ" + EXAMPLE_RANDOMNESS;
|
||||
milliseconds = UlidUtil.extractTimestamp(ulid);
|
||||
milliseconds = extractTimestamp(ulid);
|
||||
assertEquals(TIMESTAMP_MAX, milliseconds);
|
||||
|
||||
ulid = "8ZZZZZZZZZ" + EXAMPLE_RANDOMNESS;
|
||||
UlidUtil.extractTimestamp(ulid);
|
||||
fail("Should throw exception: invalid ULID");
|
||||
extractTimestamp(ulid);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -43,12 +64,11 @@ public class UlidUtilTest {
|
|||
String randomnessComponent = EXAMPLE_RANDOMNESS;
|
||||
|
||||
for (String i : EXAMPLE_DATES) {
|
||||
|
||||
long milliseconds = Instant.parse(i).toEpochMilli();
|
||||
|
||||
String timestampComponent = leftPad(Base32Util.toBase32Crockford(milliseconds));
|
||||
String timestampComponent = new String(UlidUtil.zerofill(toBase32Crockford(milliseconds), 10));
|
||||
String ulid = timestampComponent + randomnessComponent;
|
||||
long result = UlidUtil.extractTimestamp(ulid);
|
||||
long result = extractTimestamp(ulid);
|
||||
|
||||
assertEquals(milliseconds, result);
|
||||
}
|
||||
|
@ -65,11 +85,11 @@ public class UlidUtilTest {
|
|||
long milliseconds = Instant.parse(i).toEpochMilli();
|
||||
|
||||
byte[] bytes = new byte[6];
|
||||
System.arraycopy(ByteUtil.toBytes(milliseconds), 2, bytes, 0, 6);
|
||||
System.arraycopy(toBytes(milliseconds), 2, bytes, 0, 6);
|
||||
|
||||
String timestampComponent = leftPad(Base32Util.toBase32Crockford(milliseconds));
|
||||
String timestampComponent = new String(UlidUtil.zerofill(toBase32Crockford(milliseconds), 10));
|
||||
String ulid = timestampComponent + randomnessComponent;
|
||||
Instant result = UlidUtil.extractInstant(ulid);
|
||||
Instant result = extractInstant(ulid);
|
||||
|
||||
assertEquals(instant, result);
|
||||
}
|
||||
|
@ -79,7 +99,7 @@ public class UlidUtilTest {
|
|||
public void testExtractTimestampComponent() {
|
||||
String ulid = EXAMPLE_ULID;
|
||||
String expected = EXAMPLE_TIMESTAMP;
|
||||
String result = UlidUtil.extractTimestampComponent(ulid);
|
||||
String result = extractTimestampComponent(ulid);
|
||||
assertEquals(expected, result);
|
||||
}
|
||||
|
||||
|
@ -87,11 +107,157 @@ public class UlidUtilTest {
|
|||
public void testExtractRandomnessComponent() {
|
||||
String ulid = EXAMPLE_ULID;
|
||||
String expected = EXAMPLE_RANDOMNESS;
|
||||
String result = UlidUtil.extractRandomnessComponent(ulid);
|
||||
String result = extractRandomnessComponent(ulid);
|
||||
assertEquals(expected, result);
|
||||
}
|
||||
|
||||
private String leftPad(String unpadded) {
|
||||
return "0000000000".substring(unpadded.length()) + unpadded;
|
||||
@Test
|
||||
public void testToUpperCase() {
|
||||
String string = "Aq7zmxKxPc61QKiGRu8Y3PdYMer64lrRxfb9A5JAJuDeEhXSrbsxsaUoHrFzmEJUYBKJPgV+1rAd";
|
||||
char[] chars1 = string.toCharArray();
|
||||
char[] chars2 = UlidUtil.toUpperCase(chars1);
|
||||
assertEquals(new String(string).toUpperCase(), new String(chars2));
|
||||
|
||||
string = "kL9zTzZfzlwKYCEmWKPxFYxINf6JZCSmSqykyG5ONWZcFkJG2WGc7gq71YCEzt2hYcsTvfQqEmn0";
|
||||
chars1 = string.toCharArray();
|
||||
chars2 = UlidUtil.toUpperCase(chars1);
|
||||
assertEquals(new String(string).toUpperCase(), new String(chars2));
|
||||
|
||||
string = "XXEOUV3jJb3f+wpRPDVke9NgWwEgdkzChnKnpZZWS/mCSqTi757GmmqYdzuDGOa5ftqHI3/zqKrS";
|
||||
chars1 = string.toCharArray();
|
||||
chars2 = UlidUtil.toUpperCase(chars1);
|
||||
assertEquals(new String(string).toUpperCase(), new String(chars2));
|
||||
|
||||
string = "t9LVRQZbCxTQgaxlajNE/VYpLpKiHtKt7jHrtxSDIJ2hrHaJI2UPF1zA7I35m9cKz01lHYD1IXlM";
|
||||
chars1 = string.toCharArray();
|
||||
chars2 = UlidUtil.toUpperCase(chars1);
|
||||
assertEquals(new String(string).toUpperCase(), new String(chars2));
|
||||
|
||||
string = "jyS52J42LLT6GY+Zywo1R4tQv4bTfAqpFB6aiKEuA3yDxFkuXzuKe8PaGlUTaXD5WgRFMnO9nRLU";
|
||||
chars1 = string.toCharArray();
|
||||
chars2 = UlidUtil.toUpperCase(chars1);
|
||||
assertEquals(new String(string).toUpperCase(), new String(chars2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testZerofill() {
|
||||
assertEquals("001", new String(UlidUtil.zerofill("1".toCharArray(), 3)));
|
||||
assertEquals("000123", new String(UlidUtil.zerofill("123".toCharArray(), 6)));
|
||||
assertEquals("0000000000", new String(UlidUtil.zerofill("".toCharArray(), 10)));
|
||||
assertEquals("9876543210", new String(UlidUtil.zerofill("9876543210".toCharArray(), 10)));
|
||||
assertEquals("0000000000123456", new String(UlidUtil.zerofill("123456".toCharArray(), 16)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLpad() {
|
||||
|
||||
String string = "";
|
||||
char[] chars1 = string.toCharArray();
|
||||
char[] chars2 = UlidUtil.lpad(chars1, 8, 'x');
|
||||
assertEquals("xxxxxxxx", new String(chars2));
|
||||
|
||||
string = "";
|
||||
chars1 = string.toCharArray();
|
||||
chars2 = UlidUtil.lpad(chars1, 12, 'W');
|
||||
assertEquals("WWWWWWWWWWWW", new String(chars2));
|
||||
|
||||
string = "TCgpYATMlK9BmSzX";
|
||||
chars1 = string.toCharArray();
|
||||
chars2 = UlidUtil.lpad(chars1, 13, '0');
|
||||
assertEquals(string, new String(chars2));
|
||||
|
||||
string = "2kgy3m9U646L6TJ5";
|
||||
chars1 = string.toCharArray();
|
||||
chars2 = UlidUtil.lpad(chars1, 16, '0');
|
||||
assertEquals(string, new String(chars2));
|
||||
|
||||
string = "2kgy3m9U646L6TJ5";
|
||||
chars1 = string.toCharArray();
|
||||
chars2 = UlidUtil.lpad(chars1, 17, '0');
|
||||
assertEquals("0" + string, new String(chars2));
|
||||
|
||||
string = "LH6hfYcGJu06xSNF";
|
||||
chars1 = string.toCharArray();
|
||||
chars2 = UlidUtil.lpad(chars1, 25, '0');
|
||||
assertEquals("000000000" + string, new String(chars2));
|
||||
|
||||
string = "t9LVRQZbCxTQgaxlajNE/VYpLpKiHtKt7jHrtxSDIJ2hrHaJI2UPF1zA7I35m9cKz01lHYD1IXlM";
|
||||
chars1 = string.toCharArray();
|
||||
chars2 = UlidUtil.lpad(chars1, 80, '0');
|
||||
assertEquals("0000" + string, new String(chars2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveHyphens() {
|
||||
String string = "-ZGQ8yCsza-RFxlYyA-FaXa4wd-k4Owa-/ITDvqWOl4-Do3/NwW--Lawx6GcO-LmSRDsd3af3Zt-VMNnvLgIw9-";
|
||||
char[] chars1 = string.toCharArray();
|
||||
char[] chars2 = UlidUtil.removeHyphens(chars1);
|
||||
assertEquals(string.replace("-", ""), new String(chars2));
|
||||
|
||||
string = "qi3q-EMvc1-Kk7XzYMj----SUnwf-lp0K7-Ucj-W-cDplP-2dG-3x+5y-r9JBc-ZT-0e--cRHoMbU/lBzZsJ6rcJ5zT/J";
|
||||
chars1 = string.toCharArray();
|
||||
chars2 = UlidUtil.removeHyphens(chars1);
|
||||
assertEquals(string.replace("-", ""), new String(chars2));
|
||||
|
||||
string = "RXMD0DJV---Zf3Jqcv39uGjzBuiLkLNL-IvPnTyfMteEet-I7u-Z8oyE+BIUBf/OPi30iICP1TnQpMve4j";
|
||||
chars1 = string.toCharArray();
|
||||
chars2 = UlidUtil.removeHyphens(chars1);
|
||||
assertEquals(string.replace("-", ""), new String(chars2));
|
||||
|
||||
string = "---dFH-b-ylQPA60-kuRxZ9-6q5MLd1-qLTKdma-rF2yEABt-t6mJg0U-ibIYcVnt-Guqdn-z-G-43Ob-W/-Gxah1+53a-";
|
||||
chars1 = string.toCharArray();
|
||||
chars2 = UlidUtil.removeHyphens(chars1);
|
||||
assertEquals(string.replace("-", ""), new String(chars2));
|
||||
|
||||
string = "-------------------------------";
|
||||
chars1 = string.toCharArray();
|
||||
chars2 = UlidUtil.removeHyphens(chars1);
|
||||
assertEquals(string.replace("-", ""), new String(chars2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransliterate() {
|
||||
|
||||
char[] alphabetCrockford = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".toCharArray();
|
||||
char[] alphabetDefault = "0123456789abcdefghijklmnopqrstuv".toCharArray();
|
||||
|
||||
char[] chars2 = UlidUtil.transliterate(alphabetCrockford, alphabetCrockford, alphabetDefault);
|
||||
assertEquals(new String(alphabetDefault), new String(chars2));
|
||||
|
||||
chars2 = UlidUtil.transliterate(alphabetDefault, alphabetDefault, alphabetCrockford);
|
||||
assertEquals(new String(alphabetCrockford), new String(chars2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsBase32Crockford() {
|
||||
assertTrue(UlidUtil.isCrockfordBase32("16JD".toCharArray()));
|
||||
assertTrue(UlidUtil.isCrockfordBase32("01BX5ZZKBKACTAV9WEVGEMMVRY".toCharArray()));
|
||||
assertTrue(UlidUtil.isCrockfordBase32(UlidUtil.ALPHABET_CROCKFORD));
|
||||
assertFalse(UlidUtil.isCrockfordBase32("U6JD".toCharArray()));
|
||||
assertFalse(UlidUtil.isCrockfordBase32("*1BX5ZZKBKACTAV9WEVGEMMVRY".toCharArray()));
|
||||
assertFalse(UlidUtil.isCrockfordBase32("u".toCharArray()));
|
||||
assertFalse(UlidUtil.isCrockfordBase32("U".toCharArray()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToBase32Crockford() {
|
||||
assertEquals("7ZZZZZZZZZ", new String(UlidUtil.toBase32Crockford(281474976710655L)));
|
||||
// Encode from long to base 32
|
||||
for (int i = 0; i < NUMBERS.length; i++) {
|
||||
String result = new String(UlidUtil.toBase32Crockford(NUMBERS[i]));
|
||||
assertEquals(NUMBERS_BASE_32_CROCKFORD[i].length(), result.length());
|
||||
assertEquals(NUMBERS_BASE_32_CROCKFORD[i], result);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromBase32Crockford() {
|
||||
assertEquals(281474976710655L, UlidUtil.fromBase32Crockford("7ZZZZZZZZZ".toCharArray()));
|
||||
// Decode from base 32 to long
|
||||
long number = 0;
|
||||
for (int i = 0; i < NUMBERS.length; i++) {
|
||||
number = UlidUtil.fromBase32Crockford((NUMBERS_BASE_32_CROCKFORD[i]).toCharArray());
|
||||
assertEquals(NUMBERS[i], number);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import com.github.f4b6a3.ulid.util.UlidValidator;
|
|||
public class UlidValidatorTest {
|
||||
|
||||
@Test
|
||||
public void testIsValidStrict() {
|
||||
public void testIsValid() {
|
||||
|
||||
String ulid = null; // Null
|
||||
assertFalse("Null ULID should be invalid.", UlidValidator.isValid(ulid));
|
||||
|
@ -38,7 +38,7 @@ public class UlidValidatorTest {
|
|||
ulid = "#123456789ABCDEFGHJKMNPQRS"; // Special char
|
||||
assertFalse("ULID with special chars should be invalid.", UlidValidator.isValid(ulid));
|
||||
|
||||
ulid = "01234-56789-ABCDEFGHJKMNPQRS"; // Hyphens
|
||||
ulid = "01234-56789-ABCDEFGHJKM---NPQRS"; // Hyphens
|
||||
assertTrue("ULID with hiphens should be valid.", UlidValidator.isValid(ulid));
|
||||
|
||||
ulid = "8ZZZZZZZZZABCDEFGHJKMNPQRS"; // timestamp > (2^48)-1
|
||||
|
|
Loading…
Reference in New Issue