Development of version 3.0.0 #7
Continuing... Improve Ulid Simplify UlidSpecCreator Simplify MonotonicUlidSpecCreator Remove OtherRandomStrategy Remove UlidValidator Update test cases
This commit is contained in:
parent
ef2842bfe8
commit
3aa29be465
|
@ -28,8 +28,6 @@ import java.io.Serializable;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.util.UlidValidator;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a ULID.
|
* This class represents a ULID.
|
||||||
*/
|
*/
|
||||||
|
@ -48,81 +46,89 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
||||||
public static final int TIME_BYTES_LENGTH = 6;
|
public static final int TIME_BYTES_LENGTH = 6;
|
||||||
public static final int RANDOM_BYTES_LENGTH = 10;
|
public static final int RANDOM_BYTES_LENGTH = 10;
|
||||||
|
|
||||||
protected static final char[] ENCODING_CHARS = //
|
// 0xffffffffffffffffL + 1 = 0x0000000000000000L
|
||||||
|
private static final long INCREMENT_OVERFLOW = 0x0000000000000000L;
|
||||||
|
|
||||||
|
protected static final char[] ALPHABET_UPPERCASE = //
|
||||||
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', //
|
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', //
|
||||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', //
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', //
|
||||||
'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z' };
|
'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z' };
|
||||||
|
|
||||||
protected static final long[] ENCODING_VALUES = new long[128];
|
protected static final char[] ALPHABET_LOWERCASE = //
|
||||||
|
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', //
|
||||||
|
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', //
|
||||||
|
'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z' };
|
||||||
|
|
||||||
|
protected static final long[] ALPHABET_VALUES = new long[128];
|
||||||
static {
|
static {
|
||||||
for (int i = 0; i < ENCODING_VALUES.length; i++) {
|
for (int i = 0; i < ALPHABET_VALUES.length; i++) {
|
||||||
ENCODING_VALUES[i] = -1;
|
ALPHABET_VALUES[i] = -1;
|
||||||
}
|
}
|
||||||
// Numbers
|
// Numbers
|
||||||
ENCODING_VALUES['0'] = 0x00;
|
ALPHABET_VALUES['0'] = 0x00;
|
||||||
ENCODING_VALUES['1'] = 0x01;
|
ALPHABET_VALUES['1'] = 0x01;
|
||||||
ENCODING_VALUES['2'] = 0x02;
|
ALPHABET_VALUES['2'] = 0x02;
|
||||||
ENCODING_VALUES['3'] = 0x03;
|
ALPHABET_VALUES['3'] = 0x03;
|
||||||
ENCODING_VALUES['4'] = 0x04;
|
ALPHABET_VALUES['4'] = 0x04;
|
||||||
ENCODING_VALUES['5'] = 0x05;
|
ALPHABET_VALUES['5'] = 0x05;
|
||||||
ENCODING_VALUES['6'] = 0x06;
|
ALPHABET_VALUES['6'] = 0x06;
|
||||||
ENCODING_VALUES['7'] = 0x07;
|
ALPHABET_VALUES['7'] = 0x07;
|
||||||
ENCODING_VALUES['8'] = 0x08;
|
ALPHABET_VALUES['8'] = 0x08;
|
||||||
ENCODING_VALUES['9'] = 0x09;
|
ALPHABET_VALUES['9'] = 0x09;
|
||||||
// Lower case
|
// Lower case
|
||||||
ENCODING_VALUES['a'] = 0x0a;
|
ALPHABET_VALUES['a'] = 0x0a;
|
||||||
ENCODING_VALUES['b'] = 0x0b;
|
ALPHABET_VALUES['b'] = 0x0b;
|
||||||
ENCODING_VALUES['c'] = 0x0c;
|
ALPHABET_VALUES['c'] = 0x0c;
|
||||||
ENCODING_VALUES['d'] = 0x0d;
|
ALPHABET_VALUES['d'] = 0x0d;
|
||||||
ENCODING_VALUES['e'] = 0x0e;
|
ALPHABET_VALUES['e'] = 0x0e;
|
||||||
ENCODING_VALUES['f'] = 0x0f;
|
ALPHABET_VALUES['f'] = 0x0f;
|
||||||
ENCODING_VALUES['g'] = 0x10;
|
ALPHABET_VALUES['g'] = 0x10;
|
||||||
ENCODING_VALUES['h'] = 0x11;
|
ALPHABET_VALUES['h'] = 0x11;
|
||||||
ENCODING_VALUES['j'] = 0x12;
|
ALPHABET_VALUES['j'] = 0x12;
|
||||||
ENCODING_VALUES['k'] = 0x13;
|
ALPHABET_VALUES['k'] = 0x13;
|
||||||
ENCODING_VALUES['m'] = 0x14;
|
ALPHABET_VALUES['m'] = 0x14;
|
||||||
ENCODING_VALUES['n'] = 0x15;
|
ALPHABET_VALUES['n'] = 0x15;
|
||||||
ENCODING_VALUES['p'] = 0x16;
|
ALPHABET_VALUES['p'] = 0x16;
|
||||||
ENCODING_VALUES['q'] = 0x17;
|
ALPHABET_VALUES['q'] = 0x17;
|
||||||
ENCODING_VALUES['r'] = 0x18;
|
ALPHABET_VALUES['r'] = 0x18;
|
||||||
ENCODING_VALUES['s'] = 0x19;
|
ALPHABET_VALUES['s'] = 0x19;
|
||||||
ENCODING_VALUES['t'] = 0x1a;
|
ALPHABET_VALUES['t'] = 0x1a;
|
||||||
ENCODING_VALUES['v'] = 0x1b;
|
ALPHABET_VALUES['v'] = 0x1b;
|
||||||
ENCODING_VALUES['w'] = 0x1c;
|
ALPHABET_VALUES['w'] = 0x1c;
|
||||||
ENCODING_VALUES['x'] = 0x1d;
|
ALPHABET_VALUES['x'] = 0x1d;
|
||||||
ENCODING_VALUES['y'] = 0x1e;
|
ALPHABET_VALUES['y'] = 0x1e;
|
||||||
ENCODING_VALUES['z'] = 0x1f;
|
ALPHABET_VALUES['z'] = 0x1f;
|
||||||
// Lower case OIL
|
// Lower case OIL
|
||||||
ENCODING_VALUES['o'] = 0x00;
|
ALPHABET_VALUES['o'] = 0x00;
|
||||||
ENCODING_VALUES['i'] = 0x01;
|
ALPHABET_VALUES['i'] = 0x01;
|
||||||
ENCODING_VALUES['l'] = 0x01;
|
ALPHABET_VALUES['l'] = 0x01;
|
||||||
// Upper case
|
// Upper case
|
||||||
ENCODING_VALUES['A'] = 0x0a;
|
ALPHABET_VALUES['A'] = 0x0a;
|
||||||
ENCODING_VALUES['B'] = 0x0b;
|
ALPHABET_VALUES['B'] = 0x0b;
|
||||||
ENCODING_VALUES['C'] = 0x0c;
|
ALPHABET_VALUES['C'] = 0x0c;
|
||||||
ENCODING_VALUES['D'] = 0x0d;
|
ALPHABET_VALUES['D'] = 0x0d;
|
||||||
ENCODING_VALUES['E'] = 0x0e;
|
ALPHABET_VALUES['E'] = 0x0e;
|
||||||
ENCODING_VALUES['F'] = 0x0f;
|
ALPHABET_VALUES['F'] = 0x0f;
|
||||||
ENCODING_VALUES['G'] = 0x10;
|
ALPHABET_VALUES['G'] = 0x10;
|
||||||
ENCODING_VALUES['H'] = 0x11;
|
ALPHABET_VALUES['H'] = 0x11;
|
||||||
ENCODING_VALUES['J'] = 0x12;
|
ALPHABET_VALUES['J'] = 0x12;
|
||||||
ENCODING_VALUES['K'] = 0x13;
|
ALPHABET_VALUES['K'] = 0x13;
|
||||||
ENCODING_VALUES['M'] = 0x14;
|
ALPHABET_VALUES['M'] = 0x14;
|
||||||
ENCODING_VALUES['N'] = 0x15;
|
ALPHABET_VALUES['N'] = 0x15;
|
||||||
ENCODING_VALUES['P'] = 0x16;
|
ALPHABET_VALUES['P'] = 0x16;
|
||||||
ENCODING_VALUES['Q'] = 0x17;
|
ALPHABET_VALUES['Q'] = 0x17;
|
||||||
ENCODING_VALUES['R'] = 0x18;
|
ALPHABET_VALUES['R'] = 0x18;
|
||||||
ENCODING_VALUES['S'] = 0x19;
|
ALPHABET_VALUES['S'] = 0x19;
|
||||||
ENCODING_VALUES['T'] = 0x1a;
|
ALPHABET_VALUES['T'] = 0x1a;
|
||||||
ENCODING_VALUES['V'] = 0x1b;
|
ALPHABET_VALUES['V'] = 0x1b;
|
||||||
ENCODING_VALUES['W'] = 0x1c;
|
ALPHABET_VALUES['W'] = 0x1c;
|
||||||
ENCODING_VALUES['X'] = 0x1d;
|
ALPHABET_VALUES['X'] = 0x1d;
|
||||||
ENCODING_VALUES['Y'] = 0x1e;
|
ALPHABET_VALUES['Y'] = 0x1e;
|
||||||
ENCODING_VALUES['Z'] = 0x1f;
|
ALPHABET_VALUES['Z'] = 0x1f;
|
||||||
// Upper case OIL
|
// Upper case OIL
|
||||||
ENCODING_VALUES['O'] = 0x00;
|
ALPHABET_VALUES['O'] = 0x00;
|
||||||
ENCODING_VALUES['I'] = 0x01;
|
ALPHABET_VALUES['I'] = 0x01;
|
||||||
ENCODING_VALUES['L'] = 0x01;
|
ALPHABET_VALUES['L'] = 0x01;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,6 +139,14 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
||||||
this.lsb = leastSignificantBits;
|
this.lsb = leastSignificantBits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Ulid of(Ulid ulid) {
|
||||||
|
return new Ulid(ulid.getMostSignificantBits(), ulid.getLeastSignificantBits());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Ulid of(UUID uuid) {
|
||||||
|
return new Ulid(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits());
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: test
|
// TODO: test
|
||||||
public static Ulid of(byte[] bytes) {
|
public static Ulid of(byte[] bytes) {
|
||||||
|
|
||||||
|
@ -165,43 +179,42 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: optimize
|
// TODO: optimize
|
||||||
public static Ulid of(String ulid) {
|
public static Ulid of(String string) {
|
||||||
|
|
||||||
final char[] chars = ulid == null ? new char[0] : ulid.toCharArray();
|
final char[] chars = toCharArray(string);
|
||||||
UlidValidator.validate(chars);
|
|
||||||
|
|
||||||
long tm = 0;
|
long tm = 0;
|
||||||
long r1 = 0;
|
long r1 = 0;
|
||||||
long r2 = 0;
|
long r2 = 0;
|
||||||
|
|
||||||
tm |= ENCODING_VALUES[chars[0x00]] << 45;
|
tm |= ALPHABET_VALUES[chars[0x00]] << 45;
|
||||||
tm |= ENCODING_VALUES[chars[0x01]] << 40;
|
tm |= ALPHABET_VALUES[chars[0x01]] << 40;
|
||||||
tm |= ENCODING_VALUES[chars[0x02]] << 35;
|
tm |= ALPHABET_VALUES[chars[0x02]] << 35;
|
||||||
tm |= ENCODING_VALUES[chars[0x03]] << 30;
|
tm |= ALPHABET_VALUES[chars[0x03]] << 30;
|
||||||
tm |= ENCODING_VALUES[chars[0x04]] << 25;
|
tm |= ALPHABET_VALUES[chars[0x04]] << 25;
|
||||||
tm |= ENCODING_VALUES[chars[0x05]] << 20;
|
tm |= ALPHABET_VALUES[chars[0x05]] << 20;
|
||||||
tm |= ENCODING_VALUES[chars[0x06]] << 15;
|
tm |= ALPHABET_VALUES[chars[0x06]] << 15;
|
||||||
tm |= ENCODING_VALUES[chars[0x07]] << 10;
|
tm |= ALPHABET_VALUES[chars[0x07]] << 10;
|
||||||
tm |= ENCODING_VALUES[chars[0x08]] << 5;
|
tm |= ALPHABET_VALUES[chars[0x08]] << 5;
|
||||||
tm |= ENCODING_VALUES[chars[0x09]];
|
tm |= ALPHABET_VALUES[chars[0x09]];
|
||||||
|
|
||||||
r1 |= ENCODING_VALUES[chars[0x0a]] << 35;
|
r1 |= ALPHABET_VALUES[chars[0x0a]] << 35;
|
||||||
r1 |= ENCODING_VALUES[chars[0x0b]] << 30;
|
r1 |= ALPHABET_VALUES[chars[0x0b]] << 30;
|
||||||
r1 |= ENCODING_VALUES[chars[0x0c]] << 25;
|
r1 |= ALPHABET_VALUES[chars[0x0c]] << 25;
|
||||||
r1 |= ENCODING_VALUES[chars[0x0d]] << 20;
|
r1 |= ALPHABET_VALUES[chars[0x0d]] << 20;
|
||||||
r1 |= ENCODING_VALUES[chars[0x0e]] << 15;
|
r1 |= ALPHABET_VALUES[chars[0x0e]] << 15;
|
||||||
r1 |= ENCODING_VALUES[chars[0x0f]] << 10;
|
r1 |= ALPHABET_VALUES[chars[0x0f]] << 10;
|
||||||
r1 |= ENCODING_VALUES[chars[0x10]] << 5;
|
r1 |= ALPHABET_VALUES[chars[0x10]] << 5;
|
||||||
r1 |= ENCODING_VALUES[chars[0x11]];
|
r1 |= ALPHABET_VALUES[chars[0x11]];
|
||||||
|
|
||||||
r2 |= ENCODING_VALUES[chars[0x12]] << 35;
|
r2 |= ALPHABET_VALUES[chars[0x12]] << 35;
|
||||||
r2 |= ENCODING_VALUES[chars[0x13]] << 30;
|
r2 |= ALPHABET_VALUES[chars[0x13]] << 30;
|
||||||
r2 |= ENCODING_VALUES[chars[0x14]] << 25;
|
r2 |= ALPHABET_VALUES[chars[0x14]] << 25;
|
||||||
r2 |= ENCODING_VALUES[chars[0x15]] << 20;
|
r2 |= ALPHABET_VALUES[chars[0x15]] << 20;
|
||||||
r2 |= ENCODING_VALUES[chars[0x16]] << 15;
|
r2 |= ALPHABET_VALUES[chars[0x16]] << 15;
|
||||||
r2 |= ENCODING_VALUES[chars[0x17]] << 10;
|
r2 |= ALPHABET_VALUES[chars[0x17]] << 10;
|
||||||
r2 |= ENCODING_VALUES[chars[0x18]] << 5;
|
r2 |= ALPHABET_VALUES[chars[0x18]] << 5;
|
||||||
r2 |= ENCODING_VALUES[chars[0x19]];
|
r2 |= ALPHABET_VALUES[chars[0x19]];
|
||||||
|
|
||||||
final long msb = (tm << 16) | (r1 >>> 24);
|
final long msb = (tm << 16) | (r1 >>> 24);
|
||||||
final long lsb = (r1 << 40) | (r2 & 0xffffffffffL);
|
final long lsb = (r1 << 40) | (r2 & 0xffffffffffL);
|
||||||
|
@ -209,10 +222,6 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
||||||
return new Ulid(msb, lsb);
|
return new Ulid(msb, lsb);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Ulid of(UUID uuid) {
|
|
||||||
return new Ulid(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Ulid of(long time, byte[] random) {
|
public static Ulid of(long time, byte[] random) {
|
||||||
|
|
||||||
if ((time & 0xffff000000000000L) != 0) {
|
if ((time & 0xffff000000000000L) != 0) {
|
||||||
|
@ -241,6 +250,17 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
||||||
return new Ulid(msb, lsb);
|
return new Ulid(msb, lsb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UUID toUuid() {
|
||||||
|
return new UUID(this.msb, this.lsb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: test
|
||||||
|
public UUID toUuid4() {
|
||||||
|
final long msb4 = (this.msb & 0xffffffffffff0fffL) | 0x0000000000004000L; // apply version 4
|
||||||
|
final long lsb4 = (this.lsb & 0x3fffffffffffffffL) | 0x8000000000000000L; // apply variant RFC-4122
|
||||||
|
return new UUID(msb4, lsb4);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: test
|
// TODO: test
|
||||||
public byte[] toBytes() {
|
public byte[] toBytes() {
|
||||||
|
|
||||||
|
@ -272,64 +292,29 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
||||||
return Ulid.of(this.toUuid4()).toBytes();
|
return Ulid.of(this.toUuid4()).toBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: optimize
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
return this.toUpperCase();
|
||||||
final char[] chars = new char[ULID_LENGTH];
|
|
||||||
long long0 = this.msb;
|
|
||||||
long long1 = this.lsb;
|
|
||||||
|
|
||||||
long time = long0 >>> 16;
|
|
||||||
long random1 = ((long0 & 0xffffL) << 24) | (long1 >>> 40);
|
|
||||||
long random2 = (long1 & 0xffffffffffL);
|
|
||||||
|
|
||||||
chars[0x00] = ENCODING_CHARS[(int) (time >>> 45 & 0b11111)];
|
|
||||||
chars[0x01] = ENCODING_CHARS[(int) (time >>> 40 & 0b11111)];
|
|
||||||
chars[0x02] = ENCODING_CHARS[(int) (time >>> 35 & 0b11111)];
|
|
||||||
chars[0x03] = ENCODING_CHARS[(int) (time >>> 30 & 0b11111)];
|
|
||||||
chars[0x04] = ENCODING_CHARS[(int) (time >>> 25 & 0b11111)];
|
|
||||||
chars[0x05] = ENCODING_CHARS[(int) (time >>> 20 & 0b11111)];
|
|
||||||
chars[0x06] = ENCODING_CHARS[(int) (time >>> 15 & 0b11111)];
|
|
||||||
chars[0x07] = ENCODING_CHARS[(int) (time >>> 10 & 0b11111)];
|
|
||||||
chars[0x08] = ENCODING_CHARS[(int) (time >>> 5 & 0b11111)];
|
|
||||||
chars[0x09] = ENCODING_CHARS[(int) (time & 0b11111)];
|
|
||||||
|
|
||||||
chars[0x0a] = ENCODING_CHARS[(int) (random1 >>> 35 & 0b11111)];
|
|
||||||
chars[0x0b] = ENCODING_CHARS[(int) (random1 >>> 30 & 0b11111)];
|
|
||||||
chars[0x0c] = ENCODING_CHARS[(int) (random1 >>> 25 & 0b11111)];
|
|
||||||
chars[0x0d] = ENCODING_CHARS[(int) (random1 >>> 20 & 0b11111)];
|
|
||||||
chars[0x0e] = ENCODING_CHARS[(int) (random1 >>> 15 & 0b11111)];
|
|
||||||
chars[0x0f] = ENCODING_CHARS[(int) (random1 >>> 10 & 0b11111)];
|
|
||||||
chars[0x10] = ENCODING_CHARS[(int) (random1 >>> 5 & 0b11111)];
|
|
||||||
chars[0x11] = ENCODING_CHARS[(int) (random1 & 0b11111)];
|
|
||||||
|
|
||||||
chars[0x12] = ENCODING_CHARS[(int) (random2 >>> 35 & 0b11111)];
|
|
||||||
chars[0x13] = ENCODING_CHARS[(int) (random2 >>> 30 & 0b11111)];
|
|
||||||
chars[0x14] = ENCODING_CHARS[(int) (random2 >>> 25 & 0b11111)];
|
|
||||||
chars[0x15] = ENCODING_CHARS[(int) (random2 >>> 20 & 0b11111)];
|
|
||||||
chars[0x16] = ENCODING_CHARS[(int) (random2 >>> 15 & 0b11111)];
|
|
||||||
chars[0x17] = ENCODING_CHARS[(int) (random2 >>> 10 & 0b11111)];
|
|
||||||
chars[0x18] = ENCODING_CHARS[(int) (random2 >>> 5 & 0b11111)];
|
|
||||||
chars[0x19] = ENCODING_CHARS[(int) (random2 & 0b11111)];
|
|
||||||
|
|
||||||
return new String(chars);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test
|
// TODO: test
|
||||||
public String toString4() {
|
public String toUpperCase() {
|
||||||
return Ulid.of(this.toUuid4()).toString();
|
return toString(ALPHABET_UPPERCASE);
|
||||||
}
|
|
||||||
|
|
||||||
public UUID toUuid() {
|
|
||||||
return new UUID(this.msb, this.lsb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test
|
// TODO: test
|
||||||
public UUID toUuid4() {
|
public String toUpperCase4() {
|
||||||
final long msb4 = (this.msb & 0xffffffffffff0fffL) | 0x0000000000004000L; // apply version 4
|
return Ulid.of(this.toUuid4()).toUpperCase();
|
||||||
final long lsb4 = (this.lsb & 0x3fffffffffffffffL) | 0x8000000000000000L; // apply variant RFC-4122
|
}
|
||||||
return new UUID(msb4, lsb4);
|
|
||||||
|
// TODO: test
|
||||||
|
public String toLowerCase() {
|
||||||
|
return toString(ALPHABET_LOWERCASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: test
|
||||||
|
public String toLowerCase4() {
|
||||||
|
return Ulid.of(this.toUuid4()).toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTime() {
|
public long getTime() {
|
||||||
|
@ -348,6 +333,20 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
||||||
return this.lsb;
|
return this.lsb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: test
|
||||||
|
public Ulid increment() {
|
||||||
|
|
||||||
|
long msb1 = this.msb;
|
||||||
|
long lsb1 = this.lsb + 1; // Increment the LSB
|
||||||
|
|
||||||
|
if (lsb1 == INCREMENT_OVERFLOW) {
|
||||||
|
// Increment the random bits of the MSB
|
||||||
|
msb1 = (msb1 & 0xffffffffffff0000L) | ((msb1 + 1) & 0x000000000000ffffL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Ulid(msb1, lsb1);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
final int prime = 31;
|
final int prime = 31;
|
||||||
|
@ -385,4 +384,86 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
|
||||||
return 1;
|
return 1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: optimize
|
||||||
|
protected String toString(char[] alphabet) {
|
||||||
|
|
||||||
|
final char[] chars = new char[ULID_LENGTH];
|
||||||
|
|
||||||
|
long time = this.msb >>> 16;
|
||||||
|
long random1 = ((this.msb & 0xffffL) << 24) | (this.lsb >>> 40);
|
||||||
|
long random2 = (this.lsb & 0xffffffffffL);
|
||||||
|
|
||||||
|
chars[0x00] = alphabet[(int) (time >>> 45 & 0b11111)];
|
||||||
|
chars[0x01] = alphabet[(int) (time >>> 40 & 0b11111)];
|
||||||
|
chars[0x02] = alphabet[(int) (time >>> 35 & 0b11111)];
|
||||||
|
chars[0x03] = alphabet[(int) (time >>> 30 & 0b11111)];
|
||||||
|
chars[0x04] = alphabet[(int) (time >>> 25 & 0b11111)];
|
||||||
|
chars[0x05] = alphabet[(int) (time >>> 20 & 0b11111)];
|
||||||
|
chars[0x06] = alphabet[(int) (time >>> 15 & 0b11111)];
|
||||||
|
chars[0x07] = alphabet[(int) (time >>> 10 & 0b11111)];
|
||||||
|
chars[0x08] = alphabet[(int) (time >>> 5 & 0b11111)];
|
||||||
|
chars[0x09] = alphabet[(int) (time & 0b11111)];
|
||||||
|
|
||||||
|
chars[0x0a] = alphabet[(int) (random1 >>> 35 & 0b11111)];
|
||||||
|
chars[0x0b] = alphabet[(int) (random1 >>> 30 & 0b11111)];
|
||||||
|
chars[0x0c] = alphabet[(int) (random1 >>> 25 & 0b11111)];
|
||||||
|
chars[0x0d] = alphabet[(int) (random1 >>> 20 & 0b11111)];
|
||||||
|
chars[0x0e] = alphabet[(int) (random1 >>> 15 & 0b11111)];
|
||||||
|
chars[0x0f] = alphabet[(int) (random1 >>> 10 & 0b11111)];
|
||||||
|
chars[0x10] = alphabet[(int) (random1 >>> 5 & 0b11111)];
|
||||||
|
chars[0x11] = alphabet[(int) (random1 & 0b11111)];
|
||||||
|
|
||||||
|
chars[0x12] = alphabet[(int) (random2 >>> 35 & 0b11111)];
|
||||||
|
chars[0x13] = alphabet[(int) (random2 >>> 30 & 0b11111)];
|
||||||
|
chars[0x14] = alphabet[(int) (random2 >>> 25 & 0b11111)];
|
||||||
|
chars[0x15] = alphabet[(int) (random2 >>> 20 & 0b11111)];
|
||||||
|
chars[0x16] = alphabet[(int) (random2 >>> 15 & 0b11111)];
|
||||||
|
chars[0x17] = alphabet[(int) (random2 >>> 10 & 0b11111)];
|
||||||
|
chars[0x18] = alphabet[(int) (random2 >>> 5 & 0b11111)];
|
||||||
|
chars[0x19] = alphabet[(int) (random2 & 0b11111)];
|
||||||
|
|
||||||
|
return new String(chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isValidString(String string) {
|
||||||
|
return isValidArray(string == null ? null : string.toCharArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the string is a valid ULID.
|
||||||
|
*
|
||||||
|
* A valid ULID string is a sequence of 26 characters from Crockford's base 32
|
||||||
|
* alphabet.
|
||||||
|
*
|
||||||
|
* @param chars a char array
|
||||||
|
* @return boolean true if valid
|
||||||
|
*/
|
||||||
|
protected static boolean isValidArray(final char[] chars) {
|
||||||
|
|
||||||
|
if (chars == null || chars.length != ULID_LENGTH) {
|
||||||
|
return false; // null or wrong size!
|
||||||
|
}
|
||||||
|
|
||||||
|
// the two extra bits added by base-32 encoding must be zero
|
||||||
|
if ((ALPHABET_VALUES[chars[0]] & 0b11000) != 0) {
|
||||||
|
return false; // overflow!
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < chars.length; i++) {
|
||||||
|
if (ALPHABET_VALUES[chars[i]] == -1) {
|
||||||
|
return false; // invalid character!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // It seems to be OK.
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static char[] toCharArray(String string) {
|
||||||
|
char[] chars = string == null ? new char[0] : string.toCharArray();
|
||||||
|
if (!isValidArray(chars)) {
|
||||||
|
throw new IllegalArgumentException(String.format("Invalid ULID: \"%s\"", string));
|
||||||
|
}
|
||||||
|
return chars;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,8 @@
|
||||||
|
|
||||||
package com.github.f4b6a3.ulid;
|
package com.github.f4b6a3.ulid;
|
||||||
|
|
||||||
|
import com.github.f4b6a3.ulid.creator.MonotonicUlidSpecCreator;
|
||||||
import com.github.f4b6a3.ulid.creator.UlidSpecCreator;
|
import com.github.f4b6a3.ulid.creator.UlidSpecCreator;
|
||||||
import com.github.f4b6a3.ulid.creator.impl.DefaultUlidSpecCreator;
|
|
||||||
import com.github.f4b6a3.ulid.creator.impl.MonotonicUlidSpecCreator;
|
|
||||||
|
|
||||||
public final class UlidCreator {
|
public final class UlidCreator {
|
||||||
|
|
||||||
|
@ -49,19 +48,19 @@ public final class UlidCreator {
|
||||||
return MonotonicCreatorHolder.INSTANCE.create(time);
|
return MonotonicCreatorHolder.INSTANCE.create(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DefaultUlidSpecCreator getDefaultCreator() {
|
public static UlidSpecCreator getUlidSpecCreator() {
|
||||||
return new DefaultUlidSpecCreator();
|
return new UlidSpecCreator();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MonotonicUlidSpecCreator getMonotonicCreator() {
|
public static UlidSpecCreator getMonotonicUlidSpecCreator() {
|
||||||
return new MonotonicUlidSpecCreator();
|
return new MonotonicUlidSpecCreator();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DefaultCreatorHolder {
|
private static class DefaultCreatorHolder {
|
||||||
static final UlidSpecCreator INSTANCE = getDefaultCreator();
|
static final UlidSpecCreator INSTANCE = getUlidSpecCreator();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MonotonicCreatorHolder {
|
private static class MonotonicCreatorHolder {
|
||||||
static final UlidSpecCreator INSTANCE = getMonotonicCreator();
|
static final UlidSpecCreator INSTANCE = getMonotonicUlidSpecCreator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,13 +22,27 @@
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.github.f4b6a3.ulid.exception;
|
package com.github.f4b6a3.ulid.creator;
|
||||||
|
|
||||||
public final class InvalidUlidException extends RuntimeException {
|
import com.github.f4b6a3.ulid.Ulid;
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
public final class MonotonicUlidSpecCreator extends UlidSpecCreator {
|
||||||
|
|
||||||
public InvalidUlidException(String message) {
|
private long lastTime;
|
||||||
super(message);
|
private Ulid lastUlid;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized Ulid create(final long time) {
|
||||||
|
|
||||||
|
if (time == this.lastTime) {
|
||||||
|
this.lastUlid = lastUlid.increment();
|
||||||
|
} else {
|
||||||
|
final byte[] random = new byte[10];
|
||||||
|
this.randomStrategy.nextBytes(random);
|
||||||
|
this.lastUlid = Ulid.of(time, random);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastTime = time;
|
||||||
|
return this.lastUlid;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -27,11 +27,10 @@ package com.github.f4b6a3.ulid.creator;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.Ulid;
|
import com.github.f4b6a3.ulid.Ulid;
|
||||||
|
import com.github.f4b6a3.ulid.strategy.DefaultRandomStrategy;
|
||||||
import com.github.f4b6a3.ulid.strategy.RandomStrategy;
|
import com.github.f4b6a3.ulid.strategy.RandomStrategy;
|
||||||
import com.github.f4b6a3.ulid.strategy.random.DefaultRandomStrategy;
|
|
||||||
import com.github.f4b6a3.ulid.strategy.random.OtherRandomStrategy;
|
|
||||||
|
|
||||||
public abstract class UlidSpecCreator {
|
public class UlidSpecCreator {
|
||||||
|
|
||||||
protected RandomStrategy randomStrategy;
|
protected RandomStrategy randomStrategy;
|
||||||
|
|
||||||
|
@ -39,11 +38,15 @@ public abstract class UlidSpecCreator {
|
||||||
this.randomStrategy = new DefaultRandomStrategy();
|
this.randomStrategy = new DefaultRandomStrategy();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Ulid create() {
|
public Ulid create() {
|
||||||
return create(System.currentTimeMillis());
|
return create(System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Ulid create(final long time);
|
public Ulid create(final long time) {
|
||||||
|
final byte[] random = new byte[10];
|
||||||
|
this.randomStrategy.nextBytes(random);
|
||||||
|
return Ulid.of(time, random);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces the default random strategy with another.
|
* Replaces the default random strategy with another.
|
||||||
|
@ -61,20 +64,4 @@ public abstract class UlidSpecCreator {
|
||||||
this.randomStrategy = randomStrategy;
|
this.randomStrategy = randomStrategy;
|
||||||
return (T) this;
|
return (T) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces the default random strategy with another that uses the input
|
|
||||||
* {@link Random} instance.
|
|
||||||
*
|
|
||||||
* It replaces the internal {@link DefaultRandomStrategy} with
|
|
||||||
* {@link OtherRandomStrategy}.
|
|
||||||
*
|
|
||||||
* @param random a random generator
|
|
||||||
* @return {@link UlidSpecCreator}
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public synchronized <T extends UlidSpecCreator> T withRandomGenerator(Random random) {
|
|
||||||
this.randomStrategy = new OtherRandomStrategy(random);
|
|
||||||
return (T) this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
/*
|
|
||||||
* MIT License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2020 Fabio Lima
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.github.f4b6a3.ulid.creator.impl;
|
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.Ulid;
|
|
||||||
import com.github.f4b6a3.ulid.creator.UlidSpecCreator;
|
|
||||||
|
|
||||||
public final class DefaultUlidSpecCreator extends UlidSpecCreator {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized Ulid create(final long time) {
|
|
||||||
|
|
||||||
long msb = 0;
|
|
||||||
long lsb = 0;
|
|
||||||
|
|
||||||
final byte[] bytes = new byte[10];
|
|
||||||
this.randomStrategy.nextBytes(bytes);
|
|
||||||
|
|
||||||
msb |= time << 16;
|
|
||||||
msb |= (long) (bytes[0x0] & 0xff) << 8;
|
|
||||||
msb |= (long) (bytes[0x1] & 0xff);
|
|
||||||
|
|
||||||
lsb |= (long) (bytes[0x2] & 0xff) << 56;
|
|
||||||
lsb |= (long) (bytes[0x3] & 0xff) << 48;
|
|
||||||
lsb |= (long) (bytes[0x4] & 0xff) << 40;
|
|
||||||
lsb |= (long) (bytes[0x5] & 0xff) << 32;
|
|
||||||
lsb |= (long) (bytes[0x6] & 0xff) << 24;
|
|
||||||
lsb |= (long) (bytes[0x7] & 0xff) << 16;
|
|
||||||
lsb |= (long) (bytes[0x8] & 0xff) << 8;
|
|
||||||
lsb |= (long) (bytes[0x9] & 0xff);
|
|
||||||
|
|
||||||
return new Ulid(msb, lsb);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
/*
|
|
||||||
* MIT License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2020 Fabio Lima
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.github.f4b6a3.ulid.creator.impl;
|
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.Ulid;
|
|
||||||
import com.github.f4b6a3.ulid.creator.UlidSpecCreator;
|
|
||||||
|
|
||||||
public final class MonotonicUlidSpecCreator extends UlidSpecCreator {
|
|
||||||
|
|
||||||
private long msb = 0;
|
|
||||||
private long lsb = 0;
|
|
||||||
|
|
||||||
private long lastTime;
|
|
||||||
|
|
||||||
// 0xffffffffffffffffL + 1 = 0x0000000000000000L
|
|
||||||
private static final long UNSIGNED_OVERFLOW = 0x0000000000000000L;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized Ulid create(final long time) {
|
|
||||||
|
|
||||||
// TODO: test
|
|
||||||
if (time == this.lastTime) {
|
|
||||||
if (++this.lsb == UNSIGNED_OVERFLOW) {
|
|
||||||
// Increment the random bits of the MSB
|
|
||||||
this.msb = (this.msb & 0xffffffffffff0000L) | ((this.msb + 1) & 0x000000000000ffffL);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
|
|
||||||
this.msb = 0;
|
|
||||||
this.lsb = 0;
|
|
||||||
|
|
||||||
final byte[] bytes = new byte[10];
|
|
||||||
this.randomStrategy.nextBytes(bytes);
|
|
||||||
|
|
||||||
this.msb |= time << 16;
|
|
||||||
this.msb |= (long) (bytes[0x0] & 0xff) << 8;
|
|
||||||
this.msb |= (long) (bytes[0x1] & 0xff);
|
|
||||||
|
|
||||||
this.lsb |= (long) (bytes[0x2] & 0xff) << 56;
|
|
||||||
this.lsb |= (long) (bytes[0x3] & 0xff) << 48;
|
|
||||||
this.lsb |= (long) (bytes[0x4] & 0xff) << 40;
|
|
||||||
this.lsb |= (long) (bytes[0x5] & 0xff) << 32;
|
|
||||||
this.lsb |= (long) (bytes[0x6] & 0xff) << 24;
|
|
||||||
this.lsb |= (long) (bytes[0x7] & 0xff) << 16;
|
|
||||||
this.lsb |= (long) (bytes[0x8] & 0xff) << 8;
|
|
||||||
this.lsb |= (long) (bytes[0x9] & 0xff);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lastTime = time;
|
|
||||||
return new Ulid(msb, lsb);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -22,13 +22,11 @@
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.github.f4b6a3.ulid.strategy.random;
|
package com.github.f4b6a3.ulid.strategy;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.strategy.RandomStrategy;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* It uses an instance of {@link java.security.SecureRandom}.
|
* It uses an instance of {@link java.security.SecureRandom}.
|
||||||
*/
|
*/
|
|
@ -1,46 +0,0 @@
|
||||||
/*
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,223 +0,0 @@
|
||||||
/*
|
|
||||||
* MIT License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2020 Fabio Lima
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.github.f4b6a3.ulid.util;
|
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.exception.InvalidUlidException;
|
|
||||||
|
|
||||||
public final class UlidValidator {
|
|
||||||
|
|
||||||
protected static final int STRING_LENGTH = 26;
|
|
||||||
|
|
||||||
protected static final long[] BASE32_VALUES = new long[128];
|
|
||||||
static {
|
|
||||||
for (int i = 0; i < BASE32_VALUES.length; i++) {
|
|
||||||
BASE32_VALUES[i] = -1;
|
|
||||||
}
|
|
||||||
// Numbers
|
|
||||||
BASE32_VALUES['0'] = 0x00;
|
|
||||||
BASE32_VALUES['1'] = 0x01;
|
|
||||||
BASE32_VALUES['2'] = 0x02;
|
|
||||||
BASE32_VALUES['3'] = 0x03;
|
|
||||||
BASE32_VALUES['4'] = 0x04;
|
|
||||||
BASE32_VALUES['5'] = 0x05;
|
|
||||||
BASE32_VALUES['6'] = 0x06;
|
|
||||||
BASE32_VALUES['7'] = 0x07;
|
|
||||||
BASE32_VALUES['8'] = 0x08;
|
|
||||||
BASE32_VALUES['9'] = 0x09;
|
|
||||||
// Lower case
|
|
||||||
BASE32_VALUES['a'] = 0x0a;
|
|
||||||
BASE32_VALUES['b'] = 0x0b;
|
|
||||||
BASE32_VALUES['c'] = 0x0c;
|
|
||||||
BASE32_VALUES['d'] = 0x0d;
|
|
||||||
BASE32_VALUES['e'] = 0x0e;
|
|
||||||
BASE32_VALUES['f'] = 0x0f;
|
|
||||||
BASE32_VALUES['g'] = 0x10;
|
|
||||||
BASE32_VALUES['h'] = 0x11;
|
|
||||||
BASE32_VALUES['j'] = 0x12;
|
|
||||||
BASE32_VALUES['k'] = 0x13;
|
|
||||||
BASE32_VALUES['m'] = 0x14;
|
|
||||||
BASE32_VALUES['n'] = 0x15;
|
|
||||||
BASE32_VALUES['p'] = 0x16;
|
|
||||||
BASE32_VALUES['q'] = 0x17;
|
|
||||||
BASE32_VALUES['r'] = 0x18;
|
|
||||||
BASE32_VALUES['s'] = 0x19;
|
|
||||||
BASE32_VALUES['t'] = 0x1a;
|
|
||||||
BASE32_VALUES['v'] = 0x1b;
|
|
||||||
BASE32_VALUES['w'] = 0x1c;
|
|
||||||
BASE32_VALUES['x'] = 0x1d;
|
|
||||||
BASE32_VALUES['y'] = 0x1e;
|
|
||||||
BASE32_VALUES['z'] = 0x1f;
|
|
||||||
// Lower case OIL
|
|
||||||
BASE32_VALUES['o'] = 0x00;
|
|
||||||
BASE32_VALUES['i'] = 0x01;
|
|
||||||
BASE32_VALUES['l'] = 0x01;
|
|
||||||
// Upper case
|
|
||||||
BASE32_VALUES['A'] = 0x0a;
|
|
||||||
BASE32_VALUES['B'] = 0x0b;
|
|
||||||
BASE32_VALUES['C'] = 0x0c;
|
|
||||||
BASE32_VALUES['D'] = 0x0d;
|
|
||||||
BASE32_VALUES['E'] = 0x0e;
|
|
||||||
BASE32_VALUES['F'] = 0x0f;
|
|
||||||
BASE32_VALUES['G'] = 0x10;
|
|
||||||
BASE32_VALUES['H'] = 0x11;
|
|
||||||
BASE32_VALUES['J'] = 0x12;
|
|
||||||
BASE32_VALUES['K'] = 0x13;
|
|
||||||
BASE32_VALUES['M'] = 0x14;
|
|
||||||
BASE32_VALUES['N'] = 0x15;
|
|
||||||
BASE32_VALUES['P'] = 0x16;
|
|
||||||
BASE32_VALUES['Q'] = 0x17;
|
|
||||||
BASE32_VALUES['R'] = 0x18;
|
|
||||||
BASE32_VALUES['S'] = 0x19;
|
|
||||||
BASE32_VALUES['T'] = 0x1a;
|
|
||||||
BASE32_VALUES['V'] = 0x1b;
|
|
||||||
BASE32_VALUES['W'] = 0x1c;
|
|
||||||
BASE32_VALUES['X'] = 0x1d;
|
|
||||||
BASE32_VALUES['Y'] = 0x1e;
|
|
||||||
BASE32_VALUES['Z'] = 0x1f;
|
|
||||||
// Upper case OIL
|
|
||||||
BASE32_VALUES['O'] = 0x00;
|
|
||||||
BASE32_VALUES['I'] = 0x01;
|
|
||||||
BASE32_VALUES['L'] = 0x01;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private UlidValidator() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the string is a valid ULID.
|
|
||||||
*
|
|
||||||
* A valid ULID string is a sequence of 26 characters from Crockford's base 32
|
|
||||||
* alphabet.
|
|
||||||
*
|
|
||||||
* It also checks if the timestamp is between 0 and 2^48-1.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* Examples of valid ULID strings:
|
|
||||||
* - 0123456789ABCDEFGHJKMNPKRS (26 alphanumeric, case insensitive, except U)
|
|
||||||
* - 0123456789ABCDEFGHIJKLMNOP (26 alphanumeric, case insensitive, including OIL, except U)
|
|
||||||
* - 0123456789-ABCDEFGHJK-MNPKRS (26 alphanumeric, case insensitive, except U, with hyphens)
|
|
||||||
* - 0123456789-ABCDEFGHIJ-KLMNOP (26 alphanumeric, case insensitive, including OIL, except U, with hyphens)
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param ulid a ULID
|
|
||||||
* @return boolean true if valid
|
|
||||||
*/
|
|
||||||
public static boolean isValid(String ulid) {
|
|
||||||
return (ulid != null && ulid.length() != 0 && isValidString(ulid.toCharArray()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the char array is a valid ULID.
|
|
||||||
*
|
|
||||||
* A valid ULID string is a sequence of 26 characters from Crockford's base 32
|
|
||||||
* alphabet.
|
|
||||||
*
|
|
||||||
* It also checks if the timestamp is between 0 and 2^48-1.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* Examples of valid ULID strings:
|
|
||||||
* - 0123456789ABCDEFGHJKMNPKRS (26 alphanumeric, case insensitive, except U)
|
|
||||||
* - 0123456789ABCDEFGHIJKLMNOP (26 alphanumeric, case insensitive, including OIL, except U)
|
|
||||||
* - 0123456789-ABCDEFGHJK-MNPKRS (26 alphanumeric, case insensitive, except U, with hyphens)
|
|
||||||
* - 0123456789-ABCDEFGHIJ-KLMNOP (26 alphanumeric, case insensitive, including OIL, except U, with hyphens)
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param ulid a ULID char array
|
|
||||||
* @return boolean true if valid
|
|
||||||
*/
|
|
||||||
public static boolean isValid(char[] ulid) {
|
|
||||||
return (ulid != null && ulid.length != 0 && isValidString(ulid));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the ULID string is valid.
|
|
||||||
*
|
|
||||||
* See {@link UlidValidator#isValid(String)}.
|
|
||||||
*
|
|
||||||
* @param ulid a ULID string
|
|
||||||
* @throws InvalidUlidException if invalid
|
|
||||||
*/
|
|
||||||
public static void validate(String ulid) {
|
|
||||||
if (ulid == null || ulid.length() == 0 || !isValidString(ulid.toCharArray())) {
|
|
||||||
throw new InvalidUlidException("Invalid ULID: \"" + ulid + "\"");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the ULID char array is valid.
|
|
||||||
*
|
|
||||||
* See {@link UlidValidator#isValid(String)}.
|
|
||||||
*
|
|
||||||
* @param ulid a ULID char array
|
|
||||||
* @throws InvalidUlidException if invalid
|
|
||||||
*/
|
|
||||||
public static void validate(char[] ulid) {
|
|
||||||
if (ulid == null || ulid.length == 0 || !isValidString(ulid)) {
|
|
||||||
throw new InvalidUlidException("Invalid ULID: \"" + (ulid == null ? null : new String(ulid)) + "\"");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the string is a valid ULID.
|
|
||||||
*
|
|
||||||
* A valid ULID string is a sequence of 26 characters from Crockford's base 32
|
|
||||||
* alphabet.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* Examples of valid ULID strings:
|
|
||||||
* - 0123456789ABCDEFGHJKMNPKRS (26 alphanumeric, case insensitive, except U)
|
|
||||||
* - 0123456789ABCDEFGHIJKLMNOP (26 alphanumeric, case insensitive, including OIL, except U)
|
|
||||||
* - 0123456789-ABCDEFGHJK-MNPKRS (26 alphanumeric, case insensitive, except U, with hyphens)
|
|
||||||
* - 0123456789-ABCDEFGHIJ-KLMNOP (26 alphanumeric, case insensitive, including OIL, except U, with hyphens)
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param c a char array
|
|
||||||
* @return boolean true if valid
|
|
||||||
*/
|
|
||||||
protected static boolean isValidString(final char[] c) {
|
|
||||||
|
|
||||||
// the two extra bits added by base-32 encoding must be zero
|
|
||||||
if ((BASE32_VALUES[c[0]] & 0b11000) != 0) {
|
|
||||||
return false; // overflow
|
|
||||||
}
|
|
||||||
|
|
||||||
int hyphen = 0;
|
|
||||||
for (int i = 0; i < c.length; i++) {
|
|
||||||
if (c[i] == '-') {
|
|
||||||
hyphen++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (c[i] == 'U' || c[i] == 'u') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// ASCII codes: A-Z, 0-9, a-z
|
|
||||||
if (!((c[i] >= 0x41 && c[i] <= 0x5a) || (c[i] >= 0x30 && c[i] <= 0x39) || (c[i] >= 0x61 && c[i] <= 0x7a))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (c.length - hyphen) == STRING_LENGTH;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,22 +3,13 @@ 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.MonotonicUlidSpecCreatorTest;
|
||||||
import com.github.f4b6a3.ulid.creator.UlidSpecCreatorTest;
|
import com.github.f4b6a3.ulid.creator.UlidSpecCreatorTest;
|
||||||
import com.github.f4b6a3.ulid.ulid.UlidCreatorDefaultTest;
|
|
||||||
import com.github.f4b6a3.ulid.ulid.UlidCreatorDefaultStringTest;
|
|
||||||
import com.github.f4b6a3.ulid.ulid.UlidCreatorMonotonicTest;
|
|
||||||
import com.github.f4b6a3.ulid.ulid.UlidCreatorMonotonicStringTest;
|
|
||||||
import com.github.f4b6a3.ulid.util.UlidValidatorTest;
|
|
||||||
import com.github.f4b6a3.ulid.util.internal.UlidTest;
|
|
||||||
|
|
||||||
@RunWith(Suite.class)
|
@RunWith(Suite.class)
|
||||||
@Suite.SuiteClasses({
|
@Suite.SuiteClasses({
|
||||||
UlidCreatorDefaultTest.class,
|
MonotonicUlidSpecCreatorTest.class,
|
||||||
UlidCreatorDefaultStringTest.class,
|
|
||||||
UlidCreatorMonotonicTest.class,
|
|
||||||
UlidCreatorMonotonicStringTest.class,
|
|
||||||
UlidSpecCreatorTest.class,
|
UlidSpecCreatorTest.class,
|
||||||
UlidValidatorTest.class,
|
|
||||||
UlidTest.class,
|
UlidTest.class,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package com.github.f4b6a3.ulid.util.internal;
|
package com.github.f4b6a3.ulid;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
@ -11,7 +13,7 @@ import com.github.f4b6a3.ulid.Ulid;
|
||||||
|
|
||||||
public class UlidTest {
|
public class UlidTest {
|
||||||
|
|
||||||
private static final int DEFAULT_LOOP_MAX = 100_000;
|
private static final int DEFAULT_LOOP_MAX = 10_000;
|
||||||
|
|
||||||
protected static final char[] ALPHABET_CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".toCharArray();
|
protected static final char[] ALPHABET_CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".toCharArray();
|
||||||
protected static final char[] ALPHABET_JAVA = "0123456789abcdefghijklmnopqrstuv".toCharArray(); // Long.parseUnsignedLong()
|
protected static final char[] ALPHABET_JAVA = "0123456789abcdefghijklmnopqrstuv".toCharArray(); // Long.parseUnsignedLong()
|
||||||
|
@ -192,6 +194,40 @@ public class UlidTest {
|
||||||
return r1 + r2;
|
return r1 + r2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isValidString() {
|
||||||
|
|
||||||
|
String ulid = null; // Null
|
||||||
|
assertFalse("Null ULID should be invalid.", Ulid.isValidString(ulid));
|
||||||
|
|
||||||
|
ulid = ""; // length: 0
|
||||||
|
assertFalse("ULID with empty string should be invalid .", Ulid.isValidString(ulid));
|
||||||
|
|
||||||
|
ulid = "0123456789ABCDEFGHJKMNPQRS"; // All upper case
|
||||||
|
assertTrue("ULID in upper case should valid.", Ulid.isValidString(ulid));
|
||||||
|
|
||||||
|
ulid = "0123456789abcdefghjklmnpqr"; // All lower case
|
||||||
|
assertTrue("ULID in lower case should be valid.", Ulid.isValidString(ulid));
|
||||||
|
|
||||||
|
ulid = "0123456789AbCdEfGhJkMnPqRs"; // Mixed case
|
||||||
|
assertTrue("Ulid in upper and lower case should valid.", Ulid.isValidString(ulid));
|
||||||
|
|
||||||
|
ulid = "0123456789ABCDEFGHJKLMNPQ"; // length: 25
|
||||||
|
assertFalse("ULID length lower than 26 should be invalid.", Ulid.isValidString(ulid));
|
||||||
|
|
||||||
|
ulid = "0123456789ABCDEFGHJKMNPQZZZ"; // length: 27
|
||||||
|
assertFalse("ULID length greater than 26 should be invalid.", Ulid.isValidString(ulid));
|
||||||
|
|
||||||
|
ulid = "u123456789ABCDEFGHJKMNPQRS"; // Letter u
|
||||||
|
assertFalse("ULID with 'u' or 'U' should be invalid.", Ulid.isValidString(ulid));
|
||||||
|
|
||||||
|
ulid = "#123456789ABCDEFGHJKMNPQRS"; // Special char
|
||||||
|
assertFalse("ULID with special chars should be invalid.", Ulid.isValidString(ulid));
|
||||||
|
|
||||||
|
ulid = "8ZZZZZZZZZABCDEFGHJKMNPQRS"; // timestamp > (2^48)-1
|
||||||
|
assertFalse("ULID with timestamp greater than (2^48)-1 should be invalid.", Ulid.isValidString(ulid));
|
||||||
|
}
|
||||||
|
|
||||||
private static String transliterate(String string, char[] alphabet1, char[] alphabet2) {
|
private static String transliterate(String string, char[] alphabet1, char[] alphabet2) {
|
||||||
char[] output = string.toCharArray();
|
char[] output = string.toCharArray();
|
||||||
for (int i = 0; i < output.length; i++) {
|
for (int i = 0; i < output.length; i++) {
|
|
@ -117,7 +117,7 @@ public class UniquenessTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void execute(boolean verbose, int threadCount, int requestCount) {
|
public static void execute(boolean verbose, int threadCount, int requestCount) {
|
||||||
UlidSpecCreator creator = UlidCreator.getMonotonicCreator();
|
UlidSpecCreator creator = UlidCreator.getMonotonicUlidSpecCreator();
|
||||||
|
|
||||||
UniquenessTest test = new UniquenessTest(threadCount, requestCount, creator, verbose);
|
UniquenessTest test = new UniquenessTest(threadCount, requestCount, creator, verbose);
|
||||||
test.start();
|
test.start();
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.github.f4b6a3.ulid.creator;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public abstract class AbstractUlidSpecCreatorTest {
|
||||||
|
|
||||||
|
protected static final int DEFAULT_LOOP_MAX = 10_000;
|
||||||
|
|
||||||
|
protected static final String DUPLICATE_UUID_MSG = "A duplicate ULID was created.";
|
||||||
|
|
||||||
|
protected static final int THREAD_TOTAL = availableProcessors();
|
||||||
|
|
||||||
|
protected static final Random RANDOM = new Random();
|
||||||
|
|
||||||
|
protected static final long TIME_MASK = 0x0000ffffffffffffL;
|
||||||
|
|
||||||
|
private static int availableProcessors() {
|
||||||
|
int processors = Runtime.getRuntime().availableProcessors();
|
||||||
|
if (processors < 4) {
|
||||||
|
processors = 4;
|
||||||
|
}
|
||||||
|
return processors;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected 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() {
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
|
for (int i = 0; i < loopLimit; i++) {
|
||||||
|
synchronized (hashSet) {
|
||||||
|
hashSet.add(creator.create(timestamp).toUuid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package com.github.f4b6a3.ulid.ulid;
|
package com.github.f4b6a3.ulid.creator;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@ -6,12 +6,11 @@ import com.github.f4b6a3.ulid.Ulid;
|
||||||
import com.github.f4b6a3.ulid.UlidCreator;
|
import com.github.f4b6a3.ulid.UlidCreator;
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
public class UlidCreatorMonotonicTest {
|
public class MonotonicUlidSpecCreatorTest extends AbstractUlidSpecCreatorTest {
|
||||||
|
|
||||||
private static final int DEFAULT_LOOP_MAX = 100_000;
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetUlid() {
|
public void testGetUlid() {
|
||||||
|
@ -68,4 +67,34 @@ public class UlidCreatorMonotonicTest {
|
||||||
assertEquals("The ULID list is not ordered", list[i], other[i]);
|
assertEquals("The ULID list is not ordered", list[i], other[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetMonotonicUlidInParallel() 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.getMonotonicUlidSpecCreator(), 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetMonotonicUlidTime() {
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
long time = RANDOM.nextLong() & TIME_MASK;
|
||||||
|
Ulid ulid = UlidCreator.getMonotonicUlid(time);
|
||||||
|
assertEquals(time, ulid.getTime());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,10 +1,5 @@
|
||||||
package com.github.f4b6a3.ulid.creator;
|
package com.github.f4b6a3.ulid.creator;
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.Ulid;
|
import com.github.f4b6a3.ulid.Ulid;
|
||||||
|
@ -12,24 +7,75 @@ import com.github.f4b6a3.ulid.UlidCreator;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class UlidSpecCreatorTest {
|
import java.util.HashSet;
|
||||||
|
|
||||||
private static final int DEFAULT_LOOP_MAX = 100_000;
|
public class UlidSpecCreatorTest extends AbstractUlidSpecCreatorTest {
|
||||||
|
|
||||||
protected static final String DUPLICATE_UUID_MSG = "A duplicate ULID was created.";
|
@Test
|
||||||
|
public void testGetUlid() {
|
||||||
|
Ulid[] list = new Ulid[DEFAULT_LOOP_MAX];
|
||||||
|
|
||||||
protected static final int THREAD_TOTAL = availableProcessors();
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
private static final Random RANDOM = new Random();
|
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||||
|
list[i] = UlidCreator.getUlid();
|
||||||
private static final long TIME_MASK = 0x0000ffffffffffffL;
|
|
||||||
|
|
||||||
private static int availableProcessors() {
|
|
||||||
int processors = Runtime.getRuntime().availableProcessors();
|
|
||||||
if (processors < 4) {
|
|
||||||
processors = 4;
|
|
||||||
}
|
}
|
||||||
return processors;
|
|
||||||
|
long endTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
checkNullOrInvalid(list);
|
||||||
|
checkUniqueness(list);
|
||||||
|
checkCreationTime(list, startTime, endTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkNullOrInvalid(Ulid[] list) {
|
||||||
|
for (Ulid ulid : list) {
|
||||||
|
assertNotNull("ULID is null", ulid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkUniqueness(Ulid[] list) {
|
||||||
|
|
||||||
|
HashSet<Ulid> set = new HashSet<>();
|
||||||
|
|
||||||
|
for (Ulid ulid : list) {
|
||||||
|
assertTrue(String.format("ULID is duplicated %s", ulid), set.add(ulid));
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("There are duplicated ULIDs", set.size(), list.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkCreationTime(Ulid[] list, long startTime, long endTime) {
|
||||||
|
|
||||||
|
assertTrue("Start time was after end time", startTime <= endTime);
|
||||||
|
|
||||||
|
for (Ulid ulid : list) {
|
||||||
|
long creationTime = ulid.getTime();
|
||||||
|
assertTrue("Creation time was before start time " + creationTime + " " + startTime,
|
||||||
|
creationTime >= startTime);
|
||||||
|
assertTrue("Creation time was after end time", creationTime <= endTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetUlidInParallel() 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -40,81 +86,4 @@ public class UlidSpecCreatorTest {
|
||||||
assertEquals(time, ulid.getTime());
|
assertEquals(time, ulid.getTime());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetMonotonicUlidTime() {
|
|
||||||
for (int i = 0; i < 100; i++) {
|
|
||||||
long time = RANDOM.nextLong() & TIME_MASK;
|
|
||||||
Ulid ulid = UlidCreator.getMonotonicUlid(time);
|
|
||||||
assertEquals(time, ulid.getTime());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetDefaultUlidInParallel() 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.getDefaultCreator(), 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetMonotonicUlidInParallel() 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.getMonotonicCreator(), 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() {
|
|
||||||
long timestamp = System.currentTimeMillis();
|
|
||||||
for (int i = 0; i < loopLimit; i++) {
|
|
||||||
synchronized (hashSet) {
|
|
||||||
hashSet.add(creator.create(timestamp).toUuid());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
package com.github.f4b6a3.ulid.ulid;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.Ulid;
|
|
||||||
import com.github.f4b6a3.ulid.UlidCreator;
|
|
||||||
import com.github.f4b6a3.ulid.util.UlidValidator;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
public class UlidCreatorDefaultStringTest {
|
|
||||||
|
|
||||||
private static final int ULID_LENGTH = 26;
|
|
||||||
private static final int DEFAULT_LOOP_MAX = 100_000;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetUlid() {
|
|
||||||
String[] list = new String[DEFAULT_LOOP_MAX];
|
|
||||||
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
|
||||||
list[i] = UlidCreator.getUlid().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
long endTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
checkNullOrInvalid(list);
|
|
||||||
checkUniqueness(list);
|
|
||||||
checkCreationTime(list, startTime, endTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkNullOrInvalid(String[] list) {
|
|
||||||
for (String ulid : list) {
|
|
||||||
assertNotNull("ULID is null", ulid);
|
|
||||||
assertTrue("ULID is empty", !ulid.isEmpty());
|
|
||||||
assertEquals("ULID length is wrong", ULID_LENGTH, ulid.length());
|
|
||||||
assertTrue("ULID is not valid", UlidValidator.isValid(ulid));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkUniqueness(String[] list) {
|
|
||||||
|
|
||||||
HashSet<String> set = new HashSet<>();
|
|
||||||
|
|
||||||
for (String ulid : list) {
|
|
||||||
assertTrue(String.format("ULID is duplicated %s", ulid), set.add(ulid));
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals("There are duplicated ULIDs", set.size(), list.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkCreationTime(String[] list, long startTime, long endTime) {
|
|
||||||
|
|
||||||
assertTrue("Start time was after end time", startTime <= endTime);
|
|
||||||
|
|
||||||
for (String ulid : list) {
|
|
||||||
long creationTime = Ulid.of(ulid).getTime();
|
|
||||||
assertTrue("Creation time was before start time " + creationTime + " " + startTime,
|
|
||||||
creationTime >= startTime);
|
|
||||||
assertTrue("Creation time was after end time", creationTime <= endTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
package com.github.f4b6a3.ulid.ulid;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.Ulid;
|
|
||||||
import com.github.f4b6a3.ulid.UlidCreator;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
public class UlidCreatorDefaultTest {
|
|
||||||
|
|
||||||
private static final int DEFAULT_LOOP_MAX = 100_000;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetUlid() {
|
|
||||||
Ulid[] list = new Ulid[DEFAULT_LOOP_MAX];
|
|
||||||
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
|
||||||
list[i] = UlidCreator.getUlid();
|
|
||||||
}
|
|
||||||
|
|
||||||
long endTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
checkNullOrInvalid(list);
|
|
||||||
checkUniqueness(list);
|
|
||||||
checkCreationTime(list, startTime, endTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkNullOrInvalid(Ulid[] list) {
|
|
||||||
for (Ulid ulid : list) {
|
|
||||||
assertNotNull("ULID is null", ulid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkUniqueness(Ulid[] list) {
|
|
||||||
|
|
||||||
HashSet<Ulid> set = new HashSet<>();
|
|
||||||
|
|
||||||
for (Ulid ulid : list) {
|
|
||||||
assertTrue(String.format("ULID is duplicated %s", ulid), set.add(ulid));
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals("There are duplicated ULIDs", set.size(), list.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkCreationTime(Ulid[] list, long startTime, long endTime) {
|
|
||||||
|
|
||||||
assertTrue("Start time was after end time", startTime <= endTime);
|
|
||||||
|
|
||||||
for (Ulid ulid : list) {
|
|
||||||
long creationTime = ulid.getTime();
|
|
||||||
assertTrue("Creation time was before start time " + creationTime + " " + startTime,
|
|
||||||
creationTime >= startTime);
|
|
||||||
assertTrue("Creation time was after end time", creationTime <= endTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
package com.github.f4b6a3.ulid.ulid;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.Ulid;
|
|
||||||
import com.github.f4b6a3.ulid.UlidCreator;
|
|
||||||
import com.github.f4b6a3.ulid.util.UlidValidator;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
public class UlidCreatorMonotonicStringTest {
|
|
||||||
|
|
||||||
private static final int ULID_LENGTH = 26;
|
|
||||||
private static final int DEFAULT_LOOP_MAX = 100_000;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetUlid() {
|
|
||||||
String[] list = new String[DEFAULT_LOOP_MAX];
|
|
||||||
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
|
||||||
list[i] = UlidCreator.getMonotonicUlid().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
long endTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
checkNullOrInvalid(list);
|
|
||||||
checkUniqueness(list);
|
|
||||||
checkOrdering(list);
|
|
||||||
checkCreationTime(list, startTime, endTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkNullOrInvalid(String[] list) {
|
|
||||||
for (String ulid : list) {
|
|
||||||
assertNotNull("ULID is null", ulid);
|
|
||||||
assertTrue("ULID is empty", !ulid.isEmpty());
|
|
||||||
assertEquals("ULID length is wrong", ULID_LENGTH, ulid.length());
|
|
||||||
assertTrue("ULID is not valid", UlidValidator.isValid(ulid));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkUniqueness(String[] list) {
|
|
||||||
|
|
||||||
HashSet<String> set = new HashSet<>();
|
|
||||||
|
|
||||||
for (String ulid : list) {
|
|
||||||
assertTrue(String.format("ULID is duplicated %s", ulid), set.add(ulid));
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals("There are duplicated ULIDs", set.size(), list.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkCreationTime(String[] list, long startTime, long endTime) {
|
|
||||||
|
|
||||||
assertTrue("Start time was after end time", startTime <= endTime);
|
|
||||||
|
|
||||||
for (String ulid : list) {
|
|
||||||
long creationTime = Ulid.of(ulid).getTime();
|
|
||||||
assertTrue("Creation time was before start time " + creationTime + " " + startTime,
|
|
||||||
creationTime >= startTime);
|
|
||||||
assertTrue("Creation time was after end time", creationTime <= endTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkOrdering(String[] list) {
|
|
||||||
String[] other = Arrays.copyOf(list, list.length);
|
|
||||||
Arrays.sort(other);
|
|
||||||
|
|
||||||
for (int i = 0; i < list.length; i++) {
|
|
||||||
assertEquals("The ULID list is not ordered", list[i], other[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
package com.github.f4b6a3.ulid.util;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.util.UlidValidator;
|
|
||||||
|
|
||||||
public class UlidValidatorTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testIsValid() {
|
|
||||||
|
|
||||||
String ulid = null; // Null
|
|
||||||
assertFalse("Null ULID should be invalid.", UlidValidator.isValid(ulid));
|
|
||||||
|
|
||||||
ulid = ""; // length: 0
|
|
||||||
assertFalse("ULID with empty string should be invalid .", UlidValidator.isValid(ulid));
|
|
||||||
|
|
||||||
ulid = "0123456789ABCDEFGHJKMNPQRS"; // All upper case
|
|
||||||
assertTrue("ULID in upper case should valid.", UlidValidator.isValid(ulid));
|
|
||||||
|
|
||||||
ulid = "0123456789abcdefghjklmnpqr"; // All lower case
|
|
||||||
assertTrue("ULID in lower case should be valid.", UlidValidator.isValid(ulid));
|
|
||||||
|
|
||||||
ulid = "0123456789AbCdEfGhJkMnPqRs"; // Mixed case
|
|
||||||
assertTrue("Ulid in upper and lower case should valid.", UlidValidator.isValid(ulid));
|
|
||||||
|
|
||||||
ulid = "0123456789ABCDEFGHJKLMNPQ"; // length: 25
|
|
||||||
assertFalse("ULID length lower than 26 should be invalid.", UlidValidator.isValid(ulid));
|
|
||||||
|
|
||||||
ulid = "0123456789ABCDEFGHJKMNPQZZZ"; // length: 27
|
|
||||||
assertFalse("ULID length greater than 26 should be invalid.", UlidValidator.isValid(ulid));
|
|
||||||
|
|
||||||
ulid = "u123456789ABCDEFGHJKMNPQRS"; // Letter u
|
|
||||||
assertFalse("ULID with 'u' or 'U' should be invalid.", UlidValidator.isValid(ulid));
|
|
||||||
|
|
||||||
ulid = "#123456789ABCDEFGHJKMNPQRS"; // Special char
|
|
||||||
assertFalse("ULID with special chars should be invalid.", UlidValidator.isValid(ulid));
|
|
||||||
|
|
||||||
ulid = "01234-56789-ABCDEFGHJKM---NPQRS"; // Hyphens
|
|
||||||
assertTrue("ULID with hiphens should be valid.", UlidValidator.isValid(ulid));
|
|
||||||
|
|
||||||
ulid = "8ZZZZZZZZZABCDEFGHJKMNPQRS"; // timestamp > (2^48)-1
|
|
||||||
assertFalse("ULID with timestamp greater than (2^48)-1 should be invalid.", UlidValidator.isValid(ulid));
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue