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
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2020 f4b6a3
|
Copyright (c) 2020 Fabio Lima
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
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();
|
UUID ulid = UlidCreator.getUlid();
|
||||||
```
|
```
|
||||||
|
|
||||||
Create a ULID string:
|
Create a ULID as string:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
String ulid = UlidCreator.getUlidString();
|
String ulid = UlidCreator.getUlidString();
|
||||||
|
@ -28,7 +28,7 @@ Add these lines to your `pom.xml`.
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.f4b6a3</groupId>
|
<groupId>com.github.f4b6a3</groupId>
|
||||||
<artifactId>ulid-creator</artifactId>
|
<artifactId>ulid-creator</artifactId>
|
||||||
<version>1.1.1</version>
|
<version>2.0.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator).
|
See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator).
|
||||||
|
@ -36,7 +36,45 @@ See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b
|
||||||
Implementation
|
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.
|
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
|
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.
|
These are some examples of using the `UlidSpecCreator` to create ULIDs strings:
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
|
||||||
// with your custom timestamp strategy
|
// with your custom timestamp strategy
|
||||||
TimestampStrategy customStrategy = new CustomTimestampStrategy();
|
TimestampStrategy customStrategy = new CustomTimestampStrategy();
|
||||||
String ulid = UlidCreator.getUlidBasedGuidCreator()
|
String ulid = UlidCreator.getUlidSpecCreator()
|
||||||
.withTimestampStrategy(customStrategy)
|
.withTimestampStrategy(customStrategy)
|
||||||
.createString();
|
.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
|
// with `java.util.Random` number generator
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
String ulid = UlidCreator.getUlidBasedGuidCreator()
|
String ulid = UlidCreator.getUlidSpecCreator()
|
||||||
.withRandomGenerator(random)
|
.withRandomGenerator(random)
|
||||||
.createString();
|
.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>
|
<groupId>com.github.f4b6a3</groupId>
|
||||||
<artifactId>ulid-creator</artifactId>
|
<artifactId>ulid-creator</artifactId>
|
||||||
<version>1.1.2-SNAPSHOT</version>
|
<version>2.0.0-SNAPSHOT</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>ulid-creator</name>
|
<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>
|
<description>A Java library for generating and handling ULIDs.</description>
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,17 +26,12 @@
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<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.source>${jdk.version}</maven.compiler.source>
|
||||||
<maven.compiler.target>${jdk.version}</maven.compiler.target>
|
<maven.compiler.target>${jdk.version}</maven.compiler.target>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
|
||||||
<groupId>com.github.f4b6a3</groupId>
|
|
||||||
<artifactId>util</artifactId>
|
|
||||||
<version>1.0.0</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
|
@ -46,10 +41,10 @@
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<scm>
|
<scm>
|
||||||
<connection>scm:git:git://github.com/dexecutor/dependent-tasks-executor.git</connection>
|
<url>https://github.com/f4b6a3/ulid-creator</url>
|
||||||
<developerConnection>scm:git:git@github.com:dexecutor/dexecutor.git</developerConnection>
|
<connection>scm:git:ssh://git@github.com/f4b6a3/ulid-creator.git</connection>
|
||||||
<url>https://github.com/dexecutor/dependent-tasks-executor</url>
|
<developerConnection>scm:git:ssh://git@github.com/f4b6a3/ulid-creator.git</developerConnection>
|
||||||
<tag>ulid-creator-1.0.0</tag>
|
<tag>HEAD</tag>
|
||||||
</scm>
|
</scm>
|
||||||
|
|
||||||
<distributionManagement>
|
<distributionManagement>
|
||||||
|
|
|
@ -26,19 +26,29 @@ package com.github.f4b6a3.ulid;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import com.github.f4b6a3.util.random.Xorshift128PlusRandom;
|
import com.github.f4b6a3.ulid.creator.UlidSpecCreator;
|
||||||
import com.github.f4b6a3.ulid.creator.UlidBasedGuidCreator;
|
import com.github.f4b6a3.ulid.util.UlidConverter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A factory for Universally Unique Lexicographically Sortable Identifiers.
|
* A factory for Universally Unique Lexicographically Sortable Identifiers.
|
||||||
*
|
*
|
||||||
* See the ULID spec: https://github.com/ulid/spec
|
* See the ULID spec: https://github.com/ulid/spec
|
||||||
*/
|
*/
|
||||||
public class UlidCreator {
|
public final class UlidCreator {
|
||||||
|
|
||||||
private 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.
|
* Returns a ULID as GUID.
|
||||||
*
|
*
|
||||||
|
@ -48,7 +58,7 @@ public class UlidCreator {
|
||||||
* @return a UUID
|
* @return a UUID
|
||||||
*/
|
*/
|
||||||
public static UUID getUlid() {
|
public static UUID getUlid() {
|
||||||
return UlidBasedGuidCreatorHolder.INSTANCE.create();
|
return UlidSpecCreatorHolder.INSTANCE.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,49 +72,19 @@ public class UlidCreator {
|
||||||
* @return a ULID
|
* @return a ULID
|
||||||
*/
|
*/
|
||||||
public static String getUlidString() {
|
public static String getUlidString() {
|
||||||
return UlidBasedGuidCreatorHolder.INSTANCE.createString();
|
return UlidSpecCreatorHolder.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 a GUID creator for direct use.
|
* Return a GUID creator for direct use.
|
||||||
*
|
*
|
||||||
* @return a {@link UlidBasedGuidCreator}
|
* @return a {@link UlidSpecCreator}
|
||||||
*/
|
*/
|
||||||
public static UlidBasedGuidCreator getUlidBasedCreator() {
|
public static UlidSpecCreator getUlidSpecCreator() {
|
||||||
return new UlidBasedGuidCreator();
|
return new UlidSpecCreator();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class UlidBasedGuidCreatorHolder {
|
private static class UlidSpecCreatorHolder {
|
||||||
static final UlidBasedGuidCreator INSTANCE = getUlidBasedCreator();
|
static final UlidSpecCreator INSTANCE = getUlidSpecCreator();
|
||||||
}
|
|
||||||
|
|
||||||
private static class FastUlidBasedGuidCreatorHolder {
|
|
||||||
static final UlidBasedGuidCreator INSTANCE = getUlidBasedCreator().withFastRandomGenerator();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,13 +27,14 @@ package com.github.f4b6a3.ulid.creator;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.util.UlidConverter;
|
import com.github.f4b6a3.ulid.strategy.RandomStrategy;
|
||||||
import com.github.f4b6a3.util.random.Xorshift128PlusRandom;
|
import com.github.f4b6a3.ulid.strategy.random.DefaultRandomStrategy;
|
||||||
import com.github.f4b6a3.util.FingerprintUtil;
|
import com.github.f4b6a3.ulid.strategy.random.OtherRandomStrategy;
|
||||||
import com.github.f4b6a3.util.RandomUtil;
|
|
||||||
import com.github.f4b6a3.ulid.exception.UlidCreatorException;
|
import com.github.f4b6a3.ulid.exception.UlidCreatorException;
|
||||||
import com.github.f4b6a3.ulid.strategy.TimestampStrategy;
|
import com.github.f4b6a3.ulid.strategy.TimestampStrategy;
|
||||||
import com.github.f4b6a3.ulid.strategy.timestamp.DefaultTimestampStrategy;
|
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
|
* 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
|
* ULID specification: https://github.com/ulid/spec
|
||||||
*/
|
*/
|
||||||
public class UlidBasedGuidCreator {
|
public class UlidSpecCreator {
|
||||||
|
|
||||||
protected long randomMsb = 0;
|
protected long random1 = 0;
|
||||||
protected long randomLsb = 0;
|
protected long random2 = 0;
|
||||||
|
|
||||||
protected long randomLsbMax;
|
protected long randomMax2;
|
||||||
protected long randomMsbMax;
|
protected long randomMax1;
|
||||||
|
|
||||||
protected static final long HALF_RANDOM_COMPONENT = 0x000000ffffffffffL;
|
protected static final long HALF_RANDOM_COMPONENT = 0x000000ffffffffffL;
|
||||||
protected static final long INCREMENT_MAX = 0x0000010000000000L;
|
protected static final long INCREMENT_MAX = 0x0000010000000000L;
|
||||||
|
|
||||||
protected long previousTimestamp;
|
protected long previousTimestamp;
|
||||||
|
|
||||||
protected Random random;
|
protected static final String OVERRUN_MESSAGE = "The system overran the generator by requesting too many ULIDs.";
|
||||||
|
|
||||||
protected static final String OVERRUN_MESSAGE = "The system overran the generator by requesting too many GUIDs.";
|
|
||||||
|
|
||||||
protected TimestampStrategy timestampStrategy;
|
protected TimestampStrategy timestampStrategy;
|
||||||
|
protected RandomStrategy randomStrategy;
|
||||||
|
|
||||||
public UlidBasedGuidCreator() {
|
public UlidSpecCreator() {
|
||||||
this.reset();
|
|
||||||
this.timestampStrategy = new DefaultTimestampStrategy();
|
this.timestampStrategy = new DefaultTimestampStrategy();
|
||||||
|
this.randomStrategy = new DefaultRandomStrategy();
|
||||||
|
this.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -128,11 +129,11 @@ public class UlidBasedGuidCreator {
|
||||||
|
|
||||||
final long timestamp = this.getTimestamp();
|
final long timestamp = this.getTimestamp();
|
||||||
|
|
||||||
final long randomHi = truncate(randomMsb);
|
final long rnd1 = random1 & HALF_RANDOM_COMPONENT;
|
||||||
final long randomLo = truncate(randomLsb);
|
final long rnd2 = random2 & HALF_RANDOM_COMPONENT;
|
||||||
|
|
||||||
final long msb = (timestamp << 16) | (randomHi >>> 24);
|
final long msb = (timestamp << 16) | (rnd1 >>> 24);
|
||||||
final long lsb = (randomHi << 40) | randomLo;
|
final long lsb = (rnd1 << 40) | rnd2;
|
||||||
|
|
||||||
return new UUID(msb, lsb);
|
return new UUID(msb, lsb);
|
||||||
}
|
}
|
||||||
|
@ -142,7 +143,10 @@ public class UlidBasedGuidCreator {
|
||||||
*
|
*
|
||||||
* The returning string is encoded to Crockford's base32.
|
* 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() {
|
public synchronized String createString() {
|
||||||
return UlidConverter.toString(create());
|
return UlidConverter.toString(create());
|
||||||
|
@ -173,17 +177,13 @@ public class UlidBasedGuidCreator {
|
||||||
protected synchronized void reset() {
|
protected synchronized void reset() {
|
||||||
|
|
||||||
// Get random values
|
// Get random values
|
||||||
if (random == null) {
|
final byte[] bytes = new byte[10];
|
||||||
this.randomMsb = truncate(RandomUtil.get().nextLong());
|
this.randomStrategy.nextBytes(bytes);
|
||||||
this.randomLsb = truncate(RandomUtil.get().nextLong());
|
this.random1 = UlidUtil.toNumber(bytes, 0, 5);
|
||||||
} else {
|
this.random2 = UlidUtil.toNumber(bytes, 5, 10);
|
||||||
this.randomMsb = truncate(random.nextLong());
|
|
||||||
this.randomLsb = truncate(random.nextLong());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the random values
|
// Save the random values
|
||||||
this.randomMsbMax = this.randomMsb | INCREMENT_MAX;
|
this.randomMax1 = this.random1 | INCREMENT_MAX;
|
||||||
this.randomLsbMax = this.randomLsb | INCREMENT_MAX;
|
this.randomMax2 = this.random2 | INCREMENT_MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -196,7 +196,7 @@ public class UlidBasedGuidCreator {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
protected synchronized void increment() {
|
protected synchronized void increment() {
|
||||||
if ((++this.randomLsb == this.randomLsbMax) && (++this.randomMsb == this.randomMsbMax)) {
|
if ((++this.random2 > this.randomMax2) && (++this.random1 > this.randomMax1)) {
|
||||||
this.reset();
|
this.reset();
|
||||||
throw new UlidCreatorException(OVERRUN_MESSAGE);
|
throw new UlidCreatorException(OVERRUN_MESSAGE);
|
||||||
}
|
}
|
||||||
|
@ -206,73 +206,58 @@ public class UlidBasedGuidCreator {
|
||||||
* Used for changing the timestamp strategy.
|
* Used for changing the timestamp strategy.
|
||||||
*
|
*
|
||||||
* @param timestampStrategy a timestamp strategy
|
* @param timestampStrategy a timestamp strategy
|
||||||
* @return {@link UlidBasedGuidCreator}
|
* @return {@link UlidSpecCreator}
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public synchronized <T extends UlidBasedGuidCreator> T withTimestampStrategy(TimestampStrategy timestampStrategy) {
|
public synchronized <T extends UlidSpecCreator> T withTimestampStrategy(TimestampStrategy timestampStrategy) {
|
||||||
this.timestampStrategy = timestampStrategy;
|
this.timestampStrategy = timestampStrategy;
|
||||||
return (T) this;
|
return (T) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace the default random generator, in a fluent way, to another that
|
* Replaces the default random strategy with another.
|
||||||
* extends {@link Random}.
|
|
||||||
*
|
*
|
||||||
* The default random generator is {@link java.security.SecureRandom}.
|
* The default random strategy uses {@link java.security.SecureRandom}.
|
||||||
*
|
|
||||||
* For other faster pseudo-random generators, see {@link XorshiftRandom} and its
|
|
||||||
* variations.
|
|
||||||
*
|
*
|
||||||
* See {@link Random}.
|
* See {@link Random}.
|
||||||
*
|
*
|
||||||
* @param random a random generator
|
* @param random a random generator
|
||||||
* @return {@link UlidBasedGuidCreator}
|
* @param <T> the type parameter
|
||||||
|
* @return {@link AbstractRandomBasedUuidCreator}
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public synchronized <T extends UlidBasedGuidCreator> T withRandomGenerator(Random random) {
|
public synchronized <T extends UlidSpecCreator> T withRandomStrategy(RandomStrategy randomStrategy) {
|
||||||
this.random = random;
|
this.randomStrategy = randomStrategy;
|
||||||
return (T) this;
|
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
|
* It replaces the internal {@link DefaultRandomStrategy} with
|
||||||
* generator.
|
* {@link OtherRandomStrategy}.
|
||||||
*
|
*
|
||||||
* See {@link Xorshift128PlusRandom} and
|
* @param random a random generator
|
||||||
* {@link FingerprintUtil#getFingerprint()}
|
* @return {@link UlidSpecCreator}
|
||||||
*
|
|
||||||
* @return {@link UlidBasedGuidCreator}
|
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public synchronized <T extends UlidBasedGuidCreator> T withFastRandomGenerator() {
|
public synchronized <T extends UlidSpecCreator> T withRandomGenerator(Random random) {
|
||||||
final int salt = (int) FingerprintUtil.getFingerprint();
|
this.randomStrategy = new OtherRandomStrategy(random);
|
||||||
this.random = new Xorshift128PlusRandom(salt);
|
|
||||||
return (T) this;
|
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
|
* 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;
|
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;
|
package com.github.f4b6a3.ulid.exception;
|
||||||
|
|
||||||
public class InvalidUlidException extends RuntimeException {
|
public final class InvalidUlidException extends RuntimeException {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
|
|
||||||
package com.github.f4b6a3.ulid.exception;
|
package com.github.f4b6a3.ulid.exception;
|
||||||
|
|
||||||
public class UlidCreatorException extends RuntimeException {
|
public final class UlidCreatorException extends RuntimeException {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
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;
|
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
|
@Override
|
||||||
public long getTimestamp() {
|
public long getTimestamp() {
|
||||||
|
|
|
@ -26,9 +26,9 @@ package com.github.f4b6a3.ulid.strategy.timestamp;
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.strategy.TimestampStrategy;
|
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) {
|
public FixedTimestampStretegy(long timestamp) {
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
|
|
|
@ -26,10 +26,11 @@ package com.github.f4b6a3.ulid.util;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import com.github.f4b6a3.util.Base32Util;
|
import com.github.f4b6a3.ulid.exception.InvalidUlidException;
|
||||||
import com.github.f4b6a3.util.ByteUtil;
|
|
||||||
|
|
||||||
public class UlidConverter {
|
import static com.github.f4b6a3.ulid.util.UlidUtil.*;
|
||||||
|
|
||||||
|
public final class UlidConverter {
|
||||||
|
|
||||||
private UlidConverter() {
|
private UlidConverter() {
|
||||||
}
|
}
|
||||||
|
@ -39,8 +40,6 @@ public class UlidConverter {
|
||||||
*
|
*
|
||||||
* The returning string is encoded to Crockford's base32.
|
* The returning string is encoded to Crockford's base32.
|
||||||
*
|
*
|
||||||
* The timestamp and random components are encoded separated.
|
|
||||||
*
|
|
||||||
* @param uuid a UUID
|
* @param uuid a UUID
|
||||||
* @return a ULID
|
* @return a ULID
|
||||||
*/
|
*/
|
||||||
|
@ -49,19 +48,20 @@ public class UlidConverter {
|
||||||
final long msb = uuid.getMostSignificantBits();
|
final long msb = uuid.getMostSignificantBits();
|
||||||
final long lsb = uuid.getLeastSignificantBits();
|
final long lsb = uuid.getLeastSignificantBits();
|
||||||
|
|
||||||
// Extract timestamp component
|
final long time = ((msb & 0xffffffffffff0000L) >>> 16);
|
||||||
final long timeNumber = (msb >>> 16);
|
final long random1 = ((msb & 0x000000000000ffffL) << 24) | ((lsb & 0xffffff0000000000L) >>> 40);
|
||||||
String timestampComponent = leftPad(Base32Util.toBase32Crockford(timeNumber));
|
final long random2 = (lsb & 0x000000ffffffffffL);
|
||||||
|
|
||||||
// Extract randomness component
|
final char[] timeComponent = zerofill(toBase32Crockford(time), 10);
|
||||||
byte[] randBytes = new byte[10];
|
final char[] randomComponent1 = zerofill(toBase32Crockford(random1), 8);
|
||||||
randBytes[0] = (byte) (msb >>> 8);
|
final char[] randomComponent2 = zerofill(toBase32Crockford(random2), 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;
|
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
|
* The input string must be encoded to Crockford's base32, following the ULID
|
||||||
* specification.
|
* specification.
|
||||||
*
|
*
|
||||||
* The timestamp and random components are decoded separated.
|
|
||||||
*
|
|
||||||
* An exception is thrown if the ULID string is invalid.
|
* An exception is thrown if the ULID string is invalid.
|
||||||
*
|
*
|
||||||
* @param ulid a ULID
|
* @param ulid a ULID
|
||||||
* @return a UUID if valid
|
* @return a UUID if valid
|
||||||
|
* @throws InvalidUlidException if invalid
|
||||||
*/
|
*/
|
||||||
public static UUID fromString(final String ulid) {
|
public static UUID fromString(final String ulid) {
|
||||||
|
|
||||||
UlidValidator.validate(ulid);
|
UlidValidator.validate(ulid);
|
||||||
|
|
||||||
// Extract timestamp component
|
final char[] input = ulid.toCharArray();
|
||||||
final String timestampComponent = ulid.substring(0, 10);
|
final char[] timeComponent = new char[10];
|
||||||
final long timeNumber = Base32Util.fromBase32CrockfordAsLong(timestampComponent);
|
final char[] randomComponent1 = new char[8];
|
||||||
|
final char[] randomComponent2 = new char[8];
|
||||||
|
|
||||||
// Extract randomness component
|
System.arraycopy(input, 0, timeComponent, 0, 10);
|
||||||
final String randomnessComponent = ulid.substring(10, 26);
|
System.arraycopy(input, 10, randomComponent1, 0, 8);
|
||||||
byte[] randBytes = Base32Util.fromBase32Crockford(randomnessComponent);
|
System.arraycopy(input, 18, randomComponent2, 0, 8);
|
||||||
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 time = fromBase32Crockford(timeComponent);
|
||||||
final long lsb = ByteUtil.toNumber(lsbBytes);
|
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);
|
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 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() {
|
private UlidUtil() {
|
||||||
}
|
}
|
||||||
|
@ -50,11 +55,196 @@ public class UlidUtil {
|
||||||
|
|
||||||
public static String extractRandomnessComponent(String ulid) {
|
public static String extractRandomnessComponent(String ulid) {
|
||||||
UlidValidator.validate(ulid);
|
UlidValidator.validate(ulid);
|
||||||
return ulid.substring(10, 26);
|
return ulid.substring(10, ULID_CHAR_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static long extractUnixMilliseconds(String ulid) {
|
protected static long extractUnixMilliseconds(String ulid) {
|
||||||
String milliseconds = ulid.substring(0, 10);
|
return fromBase32Crockford(extractTimestampComponent(ulid).toCharArray());
|
||||||
return Base32Util.fromBase32CrockfordAsLong(milliseconds);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
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;
|
protected static final long TIMESTAMP_MAX = (long) Math.pow(2, 48) - 1;
|
||||||
|
|
||||||
private UlidValidator() {
|
private UlidValidator() {
|
||||||
|
@ -42,14 +42,12 @@ public class UlidValidator {
|
||||||
* A valid ULID string is a sequence of 26 characters from Crockford's base 32
|
* A valid ULID string is a sequence of 26 characters from Crockford's base 32
|
||||||
* alphabet.
|
* alphabet.
|
||||||
*
|
*
|
||||||
* Dashes are ignored by this validator.
|
|
||||||
*
|
|
||||||
* <pre>
|
* <pre>
|
||||||
* Examples of valid ULID strings:
|
* Examples of valid ULID strings:
|
||||||
* - 0123456789ABCDEFGHJKMNPKRS (26 alphanumeric, case insensitive, except iI, lL, oO and uU)
|
* - 0123456789ABCDEFGHJKMNPKRS (26 alphanumeric, case insensitive, except U)
|
||||||
* - 0123456789ABCDEFGHIJKLMNOP (26 alphanumeric, case insensitive, except uU)
|
* - 0123456789ABCDEFGHIJKLMNOP (26 alphanumeric, case insensitive, including OIL, except U)
|
||||||
* - 0123456789-ABCDEFGHJK-MNPKRS (26 alphanumeric, case insensitive, except iI, lL, oO and uU)
|
* - 0123456789-ABCDEFGHJK-MNPKRS (26 alphanumeric, case insensitive, except U, with hyphens)
|
||||||
* - 0123456789-ABCDEFGHIJ-KLMNOP (26 alphanumeric, case insensitive, except uU, with dashes)
|
* - 0123456789-ABCDEFGHIJ-KLMNOP (26 alphanumeric, case insensitive, including OIL, except U, with hyphens)
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param ulid a ULID
|
* @param ulid a ULID
|
||||||
|
@ -57,16 +55,20 @@ public class UlidValidator {
|
||||||
*/
|
*/
|
||||||
public static boolean isValid(String ulid) {
|
public static boolean isValid(String ulid) {
|
||||||
|
|
||||||
if (ulid == null || ulid.isEmpty()) {
|
if (ulid == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String u = ulid.replaceAll("-", "");
|
char[] chars = removeHyphens(ulid.toCharArray());
|
||||||
if (!u.matches(ULID_PATTERN)) {
|
if (chars.length != ULID_CHAR_LENGTH || !isCrockfordBase32(chars)) {
|
||||||
return false;
|
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;
|
return timestamp >= 0 && timestamp <= TIMESTAMP_MAX;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package com.github.f4b6a3.ulid;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.Suite;
|
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.ulid.UlidCreatorTest;
|
||||||
import com.github.f4b6a3.ulid.util.UlidConverterTest;
|
import com.github.f4b6a3.ulid.util.UlidConverterTest;
|
||||||
import com.github.f4b6a3.ulid.util.UlidUtilTest;
|
import com.github.f4b6a3.ulid.util.UlidUtilTest;
|
||||||
|
@ -12,7 +12,7 @@ import com.github.f4b6a3.ulid.util.UlidValidatorTest;
|
||||||
@RunWith(Suite.class)
|
@RunWith(Suite.class)
|
||||||
@Suite.SuiteClasses({
|
@Suite.SuiteClasses({
|
||||||
UlidCreatorTest.class,
|
UlidCreatorTest.class,
|
||||||
UlidBasedGuidCreatorTest.class,
|
UlidSpecCreatorTest.class,
|
||||||
UlidConverterTest.class,
|
UlidConverterTest.class,
|
||||||
UlidUtilTest.class,
|
UlidUtilTest.class,
|
||||||
UlidValidatorTest.class,
|
UlidValidatorTest.class,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import java.util.HashSet;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.UlidCreator;
|
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.exception.UlidCreatorException;
|
||||||
import com.github.f4b6a3.ulid.strategy.timestamp.FixedTimestampStretegy;
|
import com.github.f4b6a3.ulid.strategy.timestamp.FixedTimestampStretegy;
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ public class UniquenessTest {
|
||||||
private boolean verbose; // Show progress or not
|
private boolean verbose; // Show progress or not
|
||||||
|
|
||||||
// GUID creator based on ULID spec
|
// GUID creator based on ULID spec
|
||||||
private UlidBasedGuidCreator creator;
|
private UlidSpecCreator creator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the test.
|
* Initialize the test.
|
||||||
|
@ -36,7 +36,7 @@ public class UniquenessTest {
|
||||||
* @param requestCount
|
* @param requestCount
|
||||||
* @param creator
|
* @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.threadCount = threadCount;
|
||||||
this.requestCount = requestCount;
|
this.requestCount = requestCount;
|
||||||
this.creator = creator;
|
this.creator = creator;
|
||||||
|
@ -125,7 +125,7 @@ public class UniquenessTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void execute(boolean verbose, int threadCount, int requestCount) {
|
public static void execute(boolean verbose, int threadCount, int requestCount) {
|
||||||
UlidBasedGuidCreator creator = UlidCreator.getUlidBasedCreator()
|
UlidSpecCreator creator = UlidCreator.getUlidSpecCreator()
|
||||||
.withTimestampStrategy(new FixedTimestampStretegy(System.currentTimeMillis()));
|
.withTimestampStrategy(new FixedTimestampStretegy(System.currentTimeMillis()));
|
||||||
|
|
||||||
UniquenessTest test = new UniquenessTest(threadCount, requestCount, creator, verbose);
|
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;
|
package com.github.f4b6a3.ulid.ulid;
|
||||||
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.UlidCreator;
|
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.UlidUtil;
|
||||||
import com.github.f4b6a3.ulid.util.UlidValidator;
|
import com.github.f4b6a3.ulid.util.UlidValidator;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class UlidCreatorTest {
|
public class UlidCreatorTest {
|
||||||
|
|
||||||
private static int processors;
|
|
||||||
|
|
||||||
private static final int ULID_LENGTH = 26;
|
private static final int ULID_LENGTH = 26;
|
||||||
private static final int DEFAULT_LOOP_MAX = 100_000;
|
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
|
@Test
|
||||||
public void testGetUlid() {
|
public void testGetUlid() {
|
||||||
String[] list = new String[DEFAULT_LOOP_MAX];
|
String[] list = new String[DEFAULT_LOOP_MAX];
|
||||||
|
@ -52,9 +35,9 @@ public class UlidCreatorTest {
|
||||||
|
|
||||||
private void checkNullOrInvalid(String[] list) {
|
private void checkNullOrInvalid(String[] list) {
|
||||||
for (String ulid : list) {
|
for (String ulid : list) {
|
||||||
assertTrue("ULID is null", ulid != null);
|
assertNotNull("ULID is null", ulid);
|
||||||
assertTrue("ULID is empty", !ulid.isEmpty());
|
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));
|
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(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) {
|
private void checkCreationTime(String[] list, long startTime, long endTime) {
|
||||||
|
@ -87,53 +70,7 @@ public class UlidCreatorTest {
|
||||||
Arrays.sort(other);
|
Arrays.sort(other);
|
||||||
|
|
||||||
for (int i = 0; i < list.length; i++) {
|
for (int i = 0; i < list.length; i++) {
|
||||||
assertTrue("The ULID list is not ordered", list[i].equals(other[i]));
|
assertEquals("The ULID list is not ordered", list[i], 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,9 +22,9 @@ public class UlidConverterTest {
|
||||||
UUID uuid1 = UlidCreator.getUlid();
|
UUID uuid1 = UlidCreator.getUlid();
|
||||||
String ulid = UlidConverter.toString(uuid1);
|
String ulid = UlidConverter.toString(uuid1);
|
||||||
|
|
||||||
assertTrue("ULID is null", ulid != null);
|
assertNotNull("ULID is null", ulid);
|
||||||
assertTrue("ULID is empty", !ulid.isEmpty());
|
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));
|
assertTrue("ULID is not valid", UlidValidator.isValid(ulid));
|
||||||
|
|
||||||
UUID uuid2 = UlidConverter.fromString(ulid);
|
UUID uuid2 = UlidConverter.fromString(ulid);
|
||||||
|
|
|
@ -5,10 +5,8 @@ import java.time.Instant;
|
||||||
|
|
||||||
import org.junit.Test;
|
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.exception.InvalidUlidException;
|
||||||
import com.github.f4b6a3.ulid.util.UlidUtil;
|
import static com.github.f4b6a3.ulid.util.UlidUtil.*;
|
||||||
|
|
||||||
public class UlidUtilTest {
|
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",
|
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" };
|
"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)
|
@Test(expected = InvalidUlidException.class)
|
||||||
public void testExtractTimestamp() {
|
public void testExtractTimestamp() {
|
||||||
|
|
||||||
String ulid = "0000000000" + EXAMPLE_RANDOMNESS;
|
String ulid = "0000000000" + EXAMPLE_RANDOMNESS;
|
||||||
long milliseconds = UlidUtil.extractTimestamp(ulid);
|
long milliseconds = extractTimestamp(ulid);
|
||||||
assertEquals(0, milliseconds);
|
assertEquals(0, milliseconds);
|
||||||
|
|
||||||
ulid = "7ZZZZZZZZZ" + EXAMPLE_RANDOMNESS;
|
ulid = "7ZZZZZZZZZ" + EXAMPLE_RANDOMNESS;
|
||||||
milliseconds = UlidUtil.extractTimestamp(ulid);
|
milliseconds = extractTimestamp(ulid);
|
||||||
assertEquals(TIMESTAMP_MAX, milliseconds);
|
assertEquals(TIMESTAMP_MAX, milliseconds);
|
||||||
|
|
||||||
ulid = "8ZZZZZZZZZ" + EXAMPLE_RANDOMNESS;
|
ulid = "8ZZZZZZZZZ" + EXAMPLE_RANDOMNESS;
|
||||||
UlidUtil.extractTimestamp(ulid);
|
extractTimestamp(ulid);
|
||||||
fail("Should throw exception: invalid ULID");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -43,12 +64,11 @@ public class UlidUtilTest {
|
||||||
String randomnessComponent = EXAMPLE_RANDOMNESS;
|
String randomnessComponent = EXAMPLE_RANDOMNESS;
|
||||||
|
|
||||||
for (String i : EXAMPLE_DATES) {
|
for (String i : EXAMPLE_DATES) {
|
||||||
|
|
||||||
long milliseconds = Instant.parse(i).toEpochMilli();
|
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;
|
String ulid = timestampComponent + randomnessComponent;
|
||||||
long result = UlidUtil.extractTimestamp(ulid);
|
long result = extractTimestamp(ulid);
|
||||||
|
|
||||||
assertEquals(milliseconds, result);
|
assertEquals(milliseconds, result);
|
||||||
}
|
}
|
||||||
|
@ -65,11 +85,11 @@ public class UlidUtilTest {
|
||||||
long milliseconds = Instant.parse(i).toEpochMilli();
|
long milliseconds = Instant.parse(i).toEpochMilli();
|
||||||
|
|
||||||
byte[] bytes = new byte[6];
|
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;
|
String ulid = timestampComponent + randomnessComponent;
|
||||||
Instant result = UlidUtil.extractInstant(ulid);
|
Instant result = extractInstant(ulid);
|
||||||
|
|
||||||
assertEquals(instant, result);
|
assertEquals(instant, result);
|
||||||
}
|
}
|
||||||
|
@ -79,7 +99,7 @@ public class UlidUtilTest {
|
||||||
public void testExtractTimestampComponent() {
|
public void testExtractTimestampComponent() {
|
||||||
String ulid = EXAMPLE_ULID;
|
String ulid = EXAMPLE_ULID;
|
||||||
String expected = EXAMPLE_TIMESTAMP;
|
String expected = EXAMPLE_TIMESTAMP;
|
||||||
String result = UlidUtil.extractTimestampComponent(ulid);
|
String result = extractTimestampComponent(ulid);
|
||||||
assertEquals(expected, result);
|
assertEquals(expected, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,11 +107,157 @@ public class UlidUtilTest {
|
||||||
public void testExtractRandomnessComponent() {
|
public void testExtractRandomnessComponent() {
|
||||||
String ulid = EXAMPLE_ULID;
|
String ulid = EXAMPLE_ULID;
|
||||||
String expected = EXAMPLE_RANDOMNESS;
|
String expected = EXAMPLE_RANDOMNESS;
|
||||||
String result = UlidUtil.extractRandomnessComponent(ulid);
|
String result = extractRandomnessComponent(ulid);
|
||||||
assertEquals(expected, result);
|
assertEquals(expected, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String leftPad(String unpadded) {
|
@Test
|
||||||
return "0000000000".substring(unpadded.length()) + unpadded;
|
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 {
|
public class UlidValidatorTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIsValidStrict() {
|
public void testIsValid() {
|
||||||
|
|
||||||
String ulid = null; // Null
|
String ulid = null; // Null
|
||||||
assertFalse("Null ULID should be invalid.", UlidValidator.isValid(ulid));
|
assertFalse("Null ULID should be invalid.", UlidValidator.isValid(ulid));
|
||||||
|
@ -38,7 +38,7 @@ public class UlidValidatorTest {
|
||||||
ulid = "#123456789ABCDEFGHJKMNPQRS"; // Special char
|
ulid = "#123456789ABCDEFGHJKMNPQRS"; // Special char
|
||||||
assertFalse("ULID with special chars should be invalid.", UlidValidator.isValid(ulid));
|
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));
|
assertTrue("ULID with hiphens should be valid.", UlidValidator.isValid(ulid));
|
||||||
|
|
||||||
ulid = "8ZZZZZZZZZABCDEFGHJKMNPQRS"; // timestamp > (2^48)-1
|
ulid = "8ZZZZZZZZZABCDEFGHJKMNPQRS"; // timestamp > (2^48)-1
|
||||||
|
|
Loading…
Reference in New Issue