[#4] Optimize the generation of ULID in string format
Now the generation of ULID in string format is 2.5x faster than before. List of changes: Optimize UlidSpecCreator Optimize UlidConverter Optimize UlidValidator Create UlidStruct // class for internal use and test cases Add test cases Update README.md Update javadoc Coverage 94.3%
This commit is contained in:
parent
b8741f56df
commit
54cd5c0595
30
README.md
30
README.md
|
@ -33,7 +33,7 @@ Add these lines to your `pom.xml`.
|
|||
<dependency>
|
||||
<groupId>com.github.f4b6a3</groupId>
|
||||
<artifactId>ulid-creator</artifactId>
|
||||
<version>2.1.0</version>
|
||||
<version>2.2.0</version>
|
||||
</dependency>
|
||||
```
|
||||
See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator).
|
||||
|
@ -146,30 +146,36 @@ Benchmark
|
|||
|
||||
This section shows benchmarks comparing `UlidCreator` to `java.util.UUID`.
|
||||
|
||||
* **ulid-creator v2.1.0:**
|
||||
|
||||
```
|
||||
---------------------------------------------------------------------------
|
||||
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
|
||||
Throughput.JDK_RandomBased thrpt 5 2196,215 ± 13,668 ops/ms
|
||||
Throughput.UlidCreator_Ulid thrpt 5 19224,340 ± 106,231 ops/ms
|
||||
Throughput.UlidCreator_UlidString thrpt 5 5006,424 ± 26,946 ops/ms
|
||||
---------------------------------------------------------------------------
|
||||
Total time: 00:04:01
|
||||
---------------------------------------------------------------------------
|
||||
```
|
||||
|
||||
* **ulid-creator v2.2.0:**
|
||||
|
||||
```
|
||||
----------------------------------------------------------------------
|
||||
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
|
||||
----------------------------------------------------------------------
|
||||
---------------------------------------------------------------------------
|
||||
THROUGHPUT Mode Cnt Score Error Units
|
||||
---------------------------------------------------------------------------
|
||||
Throughput.JDK_RandomBased thrpt 5 2191,690 ± 8,947 ops/ms
|
||||
Throughput.UlidCreator_Ulid thrpt 5 19236,123 ± 156,123 ops/ms
|
||||
Throughput.UlidCreator_UlidString thrpt 5 12893,016 ± 179,618 ops/ms <- 2.5x faster
|
||||
---------------------------------------------------------------------------
|
||||
Total time: 00:04:01
|
||||
----------------------------------------------------------------------
|
||||
---------------------------------------------------------------------------
|
||||
```
|
||||
|
||||
The ULID string generation is 2.5x faster in version 2.2.0 than before.
|
||||
|
||||
System: CPU i5-3330, 8G RAM, Ubuntu 20.04.
|
||||
|
||||
See: [uuid-creator-benchmark](https://github.com/fabiolimace/uuid-creator-benchmark)
|
||||
|
|
|
@ -32,8 +32,8 @@ import com.github.f4b6a3.ulid.strategy.random.DefaultRandomStrategy;
|
|||
import com.github.f4b6a3.ulid.strategy.random.OtherRandomStrategy;
|
||||
import com.github.f4b6a3.ulid.strategy.TimestampStrategy;
|
||||
import com.github.f4b6a3.ulid.strategy.timestamp.DefaultTimestampStrategy;
|
||||
import com.github.f4b6a3.ulid.util.UlidConverter;
|
||||
import com.github.f4b6a3.ulid.util.UlidUtil;
|
||||
import com.github.f4b6a3.ulid.util.internal.UlidStruct;
|
||||
|
||||
/**
|
||||
* Factory that creates lexicographically sortable GUIDs, based on the ULID
|
||||
|
@ -121,16 +121,8 @@ public class UlidSpecCreator {
|
|||
* @return {@link UUID} a GUID value
|
||||
*/
|
||||
public synchronized UUID create() {
|
||||
|
||||
final long timestamp = this.getTimestamp();
|
||||
|
||||
final long rnd1 = random1 & HALF_RANDOM_COMPONENT;
|
||||
final long rnd2 = random2 & HALF_RANDOM_COMPONENT;
|
||||
|
||||
final long msb = (timestamp << 16) | (rnd1 >>> 24);
|
||||
final long lsb = (rnd1 << 40) | rnd2;
|
||||
|
||||
return new UUID(msb, lsb);
|
||||
final UlidStruct struct = new UlidStruct(this.getTimestamp(), random1, random2);
|
||||
return struct.toUuid();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -144,7 +136,8 @@ public class UlidSpecCreator {
|
|||
* @return a ULID string
|
||||
*/
|
||||
public synchronized String createString() {
|
||||
return UlidConverter.toString(create());
|
||||
final UlidStruct struct = new UlidStruct(this.getTimestamp(), random1, random2);
|
||||
return struct.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -27,8 +27,7 @@ package com.github.f4b6a3.ulid.util;
|
|||
import java.util.UUID;
|
||||
|
||||
import com.github.f4b6a3.ulid.exception.InvalidUlidException;
|
||||
|
||||
import static com.github.f4b6a3.ulid.util.UlidUtil.*;
|
||||
import com.github.f4b6a3.ulid.util.internal.UlidStruct;
|
||||
|
||||
public final class UlidConverter {
|
||||
|
||||
|
@ -43,25 +42,9 @@ public final class UlidConverter {
|
|||
* @param ulid a UUID
|
||||
* @return a ULID
|
||||
*/
|
||||
public static String toString(UUID ulid) {
|
||||
|
||||
final long msb = ulid.getMostSignificantBits();
|
||||
final long lsb = ulid.getLeastSignificantBits();
|
||||
|
||||
final long time = ((msb & 0xffffffffffff0000L) >>> 16);
|
||||
final long random1 = ((msb & 0x000000000000ffffL) << 24) | ((lsb & 0xffffff0000000000L) >>> 40);
|
||||
final long random2 = (lsb & 0x000000ffffffffffL);
|
||||
|
||||
final char[] timeComponent = zerofill(toBase32Crockford(time), 10);
|
||||
final char[] randomComponent1 = zerofill(toBase32Crockford(random1), 8);
|
||||
final char[] randomComponent2 = zerofill(toBase32Crockford(random2), 8);
|
||||
|
||||
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);
|
||||
public static String toString(final UUID ulid) {
|
||||
UlidStruct struct = new UlidStruct(ulid);
|
||||
return struct.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -77,26 +60,8 @@ public final class UlidConverter {
|
|||
* @throws InvalidUlidException if invalid
|
||||
*/
|
||||
public static UUID fromString(final String ulid) {
|
||||
|
||||
UlidValidator.validate(ulid);
|
||||
|
||||
final char[] input = ulid.toCharArray();
|
||||
final char[] timeComponent = new char[10];
|
||||
final char[] randomComponent1 = new char[8];
|
||||
final char[] randomComponent2 = new char[8];
|
||||
|
||||
System.arraycopy(input, 0, timeComponent, 0, 10);
|
||||
System.arraycopy(input, 10, randomComponent1, 0, 8);
|
||||
System.arraycopy(input, 18, randomComponent2, 0, 8);
|
||||
|
||||
final long time = fromBase32Crockford(timeComponent);
|
||||
final long random1 = fromBase32Crockford(randomComponent1);
|
||||
final long random2 = fromBase32Crockford(randomComponent2);
|
||||
|
||||
final long msb = ((time & 0x0000ffffffffffffL) << 16) | ((random1 & 0x000000ffff000000L) >>> 24);
|
||||
final long lsb = ((random1 & 0x0000000000ffffffL) << 40) | (random2 & 0x000000ffffffffffL);
|
||||
|
||||
return new UUID(msb, lsb);
|
||||
UlidStruct struct = new UlidStruct(ulid);
|
||||
return struct.toUuid();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,11 +30,14 @@ public final class UlidUtil {
|
|||
|
||||
protected static final int BASE_32 = 32;
|
||||
|
||||
protected static final int ULID_CHAR_LENGTH = 26;
|
||||
protected static final int ULID_LENGTH = 26;
|
||||
|
||||
// Include 'O'->ZERO, 'I'->ONE and 'L'->ONE
|
||||
protected static final char[] ALPHABET_CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZOIL".toCharArray();
|
||||
|
||||
protected static final char[] ALPHABET_JAVA = "0123456789abcdefghijklmnopqrstuv011".toCharArray();
|
||||
|
||||
|
||||
private UlidUtil() {
|
||||
}
|
||||
|
||||
|
@ -55,7 +58,7 @@ public final class UlidUtil {
|
|||
|
||||
public static String extractRandomnessComponent(String ulid) {
|
||||
UlidValidator.validate(ulid);
|
||||
return ulid.substring(10, ULID_CHAR_LENGTH);
|
||||
return ulid.substring(10, ULID_LENGTH);
|
||||
}
|
||||
|
||||
protected static long extractUnixMilliseconds(String ulid) {
|
||||
|
@ -164,6 +167,10 @@ public final class UlidUtil {
|
|||
return output;
|
||||
}
|
||||
|
||||
protected static String transliterate(String string, char[] alphabet1, char[] alphabet2) {
|
||||
return new String(transliterate(string.toCharArray(), alphabet1, alphabet2));
|
||||
}
|
||||
|
||||
protected static char[] transliterate(char[] chars, char[] alphabet1, char[] alphabet2) {
|
||||
char[] output = chars.clone();
|
||||
for (int i = 0; i < output.length; i++) {
|
||||
|
|
|
@ -26,16 +26,59 @@ package com.github.f4b6a3.ulid.util;
|
|||
|
||||
import com.github.f4b6a3.ulid.exception.InvalidUlidException;
|
||||
|
||||
import static com.github.f4b6a3.ulid.util.UlidUtil.*;
|
||||
import static com.github.f4b6a3.ulid.util.internal.UlidStruct.BASE32_VALUES;
|
||||
|
||||
public final class UlidValidator {
|
||||
|
||||
// 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 int ULID_LENGTH = 26;
|
||||
|
||||
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) {
|
||||
if (ulid == null) {
|
||||
return false;
|
||||
}
|
||||
char[] chars = ulid.toCharArray();
|
||||
return isValidString(chars) && isValidTimestamp(chars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the ULID string is a valid.
|
||||
*
|
||||
* See {@link UlidValidator#isValid(String)}.
|
||||
*
|
||||
* @param ulid a ULID string
|
||||
* @throws InvalidUlidException if invalid
|
||||
*/
|
||||
public static void validate(String ulid) {
|
||||
if (!isValid(ulid)) {
|
||||
throw new InvalidUlidException(String.format("Invalid ULID: %s.", ulid));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the string is a valid ULID.
|
||||
*
|
||||
|
@ -50,40 +93,48 @@ public final class UlidValidator {
|
|||
* - 0123456789-ABCDEFGHIJ-KLMNOP (26 alphanumeric, case insensitive, including OIL, except U, with hyphens)
|
||||
* </pre>
|
||||
*
|
||||
* @param ulid a ULID
|
||||
* @param c a char array
|
||||
* @return boolean true if valid
|
||||
*/
|
||||
public static boolean isValid(String ulid) {
|
||||
|
||||
if (ulid == null) {
|
||||
return false;
|
||||
protected static boolean isValidString(final char[] c) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
char[] chars = removeHyphens(ulid.toCharArray());
|
||||
if (chars.length != ULID_CHAR_LENGTH || !isCrockfordBase32(chars)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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 (c.length - hyphen) == ULID_LENGTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the ULID string is a valid.
|
||||
* Checks if the timestamp is between 0 and 2^48-1
|
||||
*
|
||||
* See {@link TsidValidator#isValid(String)}.
|
||||
*
|
||||
* @param ulid a ULID string
|
||||
* @throws InvalidUlidException if invalid
|
||||
* @param chars a char array
|
||||
* @return false if invalid.
|
||||
*/
|
||||
protected static void validate(String ulid) {
|
||||
if (!isValid(ulid)) {
|
||||
throw new InvalidUlidException(String.format("Invalid ULID: %s.", ulid));
|
||||
}
|
||||
protected static boolean isValidTimestamp(char[] chars) {
|
||||
|
||||
long time = 0;
|
||||
|
||||
time |= BASE32_VALUES[chars[0x00]] << 45;
|
||||
time |= BASE32_VALUES[chars[0x01]] << 40;
|
||||
time |= BASE32_VALUES[chars[0x02]] << 35;
|
||||
time |= BASE32_VALUES[chars[0x03]] << 30;
|
||||
time |= BASE32_VALUES[chars[0x04]] << 25;
|
||||
time |= BASE32_VALUES[chars[0x05]] << 20;
|
||||
time |= BASE32_VALUES[chars[0x06]] << 15;
|
||||
time |= BASE32_VALUES[chars[0x07]] << 10;
|
||||
time |= BASE32_VALUES[chars[0x08]] << 5;
|
||||
time |= BASE32_VALUES[chars[0x09]];
|
||||
|
||||
return time >= 0 && time <= TIMESTAMP_MAX;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,250 @@
|
|||
/*
|
||||
* 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.internal;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.github.f4b6a3.ulid.util.UlidValidator;
|
||||
|
||||
/**
|
||||
* This class represents the structure of a ULID.
|
||||
*
|
||||
* It is for internal use and test cases in this library.
|
||||
*/
|
||||
public final class UlidStruct {
|
||||
|
||||
public final long time;
|
||||
public final long random1;
|
||||
public final long random2;
|
||||
|
||||
protected static final long TIMESTAMP_COMPONENT = 0x0000ffffffffffffL;
|
||||
protected static final long HALF_RANDOM_COMPONENT = 0x000000ffffffffffL;
|
||||
|
||||
public static final char[] BASE32_CHARS = //
|
||||
{ '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' };
|
||||
|
||||
public static final long[] BASE32_VALUES = new long[128];
|
||||
static {
|
||||
|
||||
// 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;
|
||||
|
||||
}
|
||||
|
||||
public UlidStruct(long time, long random1, long random2) {
|
||||
this.time = time & TIMESTAMP_COMPONENT;
|
||||
this.random1 = random1 & HALF_RANDOM_COMPONENT;
|
||||
this.random2 = random2 & HALF_RANDOM_COMPONENT;
|
||||
}
|
||||
|
||||
public UlidStruct(UUID uuid) {
|
||||
final long msb = uuid.getMostSignificantBits();
|
||||
final long lsb = uuid.getLeastSignificantBits();
|
||||
|
||||
this.time = (msb >>> 16);
|
||||
this.random1 = ((msb & 0x000000000000ffffL) << 24) | (lsb >>> 40);
|
||||
this.random2 = (lsb & 0x000000ffffffffffL);
|
||||
}
|
||||
|
||||
public UlidStruct(String string) {
|
||||
|
||||
UlidValidator.validate(string);
|
||||
|
||||
long tm = 0;
|
||||
long r1 = 0;
|
||||
long r2 = 0;
|
||||
final char[] chars = string.toCharArray();
|
||||
|
||||
tm |= BASE32_VALUES[chars[0x00]] << 45;
|
||||
tm |= BASE32_VALUES[chars[0x01]] << 40;
|
||||
tm |= BASE32_VALUES[chars[0x02]] << 35;
|
||||
tm |= BASE32_VALUES[chars[0x03]] << 30;
|
||||
tm |= BASE32_VALUES[chars[0x04]] << 25;
|
||||
tm |= BASE32_VALUES[chars[0x05]] << 20;
|
||||
tm |= BASE32_VALUES[chars[0x06]] << 15;
|
||||
tm |= BASE32_VALUES[chars[0x07]] << 10;
|
||||
tm |= BASE32_VALUES[chars[0x08]] << 5;
|
||||
tm |= BASE32_VALUES[chars[0x09]];
|
||||
|
||||
r1 |= BASE32_VALUES[chars[0x0a]] << 35;
|
||||
r1 |= BASE32_VALUES[chars[0x0b]] << 30;
|
||||
r1 |= BASE32_VALUES[chars[0x0c]] << 25;
|
||||
r1 |= BASE32_VALUES[chars[0x0d]] << 20;
|
||||
r1 |= BASE32_VALUES[chars[0x0e]] << 15;
|
||||
r1 |= BASE32_VALUES[chars[0x0f]] << 10;
|
||||
r1 |= BASE32_VALUES[chars[0x10]] << 5;
|
||||
r1 |= BASE32_VALUES[chars[0x11]];
|
||||
|
||||
r2 |= BASE32_VALUES[chars[0x12]] << 35;
|
||||
r2 |= BASE32_VALUES[chars[0x13]] << 30;
|
||||
r2 |= BASE32_VALUES[chars[0x14]] << 25;
|
||||
r2 |= BASE32_VALUES[chars[0x15]] << 20;
|
||||
r2 |= BASE32_VALUES[chars[0x16]] << 15;
|
||||
r2 |= BASE32_VALUES[chars[0x17]] << 10;
|
||||
r2 |= BASE32_VALUES[chars[0x18]] << 5;
|
||||
r2 |= BASE32_VALUES[chars[0x19]];
|
||||
|
||||
this.time = tm;
|
||||
this.random1 = r1;
|
||||
this.random2 = r2;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
||||
final char[] chars = new char[26];
|
||||
|
||||
chars[0x00] = BASE32_CHARS[(int) (time >>> 45 & 0b11111)];
|
||||
chars[0x01] = BASE32_CHARS[(int) (time >>> 40 & 0b11111)];
|
||||
chars[0x02] = BASE32_CHARS[(int) (time >>> 35 & 0b11111)];
|
||||
chars[0x03] = BASE32_CHARS[(int) (time >>> 30 & 0b11111)];
|
||||
chars[0x04] = BASE32_CHARS[(int) (time >>> 25 & 0b11111)];
|
||||
chars[0x05] = BASE32_CHARS[(int) (time >>> 20 & 0b11111)];
|
||||
chars[0x06] = BASE32_CHARS[(int) (time >>> 15 & 0b11111)];
|
||||
chars[0x07] = BASE32_CHARS[(int) (time >>> 10 & 0b11111)];
|
||||
chars[0x08] = BASE32_CHARS[(int) (time >>> 5 & 0b11111)];
|
||||
chars[0x09] = BASE32_CHARS[(int) (time & 0b11111)];
|
||||
|
||||
chars[0x0a] = BASE32_CHARS[(int) (random1 >>> 35 & 0b11111)];
|
||||
chars[0x0b] = BASE32_CHARS[(int) (random1 >>> 30 & 0b11111)];
|
||||
chars[0x0c] = BASE32_CHARS[(int) (random1 >>> 25 & 0b11111)];
|
||||
chars[0x0d] = BASE32_CHARS[(int) (random1 >>> 20 & 0b11111)];
|
||||
chars[0x0e] = BASE32_CHARS[(int) (random1 >>> 15 & 0b11111)];
|
||||
chars[0x0f] = BASE32_CHARS[(int) (random1 >>> 10 & 0b11111)];
|
||||
chars[0x10] = BASE32_CHARS[(int) (random1 >>> 5 & 0b11111)];
|
||||
chars[0x11] = BASE32_CHARS[(int) (random1 & 0b11111)];
|
||||
|
||||
chars[0x12] = BASE32_CHARS[(int) (random2 >>> 35 & 0b11111)];
|
||||
chars[0x13] = BASE32_CHARS[(int) (random2 >>> 30 & 0b11111)];
|
||||
chars[0x14] = BASE32_CHARS[(int) (random2 >>> 25 & 0b11111)];
|
||||
chars[0x15] = BASE32_CHARS[(int) (random2 >>> 20 & 0b11111)];
|
||||
chars[0x16] = BASE32_CHARS[(int) (random2 >>> 15 & 0b11111)];
|
||||
chars[0x17] = BASE32_CHARS[(int) (random2 >>> 10 & 0b11111)];
|
||||
chars[0x18] = BASE32_CHARS[(int) (random2 >>> 5 & 0b11111)];
|
||||
chars[0x19] = BASE32_CHARS[(int) (random2 & 0b11111)];
|
||||
|
||||
return new String(chars);
|
||||
}
|
||||
|
||||
public UUID toUuid() {
|
||||
|
||||
final long msb = (time << 16) | (random1 >>> 24);
|
||||
final long lsb = (random1 << 40) | random2;
|
||||
|
||||
return new UUID(msb, lsb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + (int) (random1 ^ (random1 >>> 32));
|
||||
result = prime * result + (int) (random2 ^ (random2 >>> 32));
|
||||
result = prime * result + (int) (time ^ (time >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
UlidStruct other = (UlidStruct) obj;
|
||||
if (random1 != other.random1)
|
||||
return false;
|
||||
if (random2 != other.random2)
|
||||
return false;
|
||||
if (time != other.time)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import com.github.f4b6a3.ulid.ulid.UlidCreatorTest;
|
|||
import com.github.f4b6a3.ulid.util.UlidConverterTest;
|
||||
import com.github.f4b6a3.ulid.util.UlidUtilTest;
|
||||
import com.github.f4b6a3.ulid.util.UlidValidatorTest;
|
||||
import com.github.f4b6a3.ulid.util.internal.UlidStructTest;
|
||||
|
||||
@RunWith(Suite.class)
|
||||
@Suite.SuiteClasses({
|
||||
|
@ -16,6 +17,7 @@ import com.github.f4b6a3.ulid.util.UlidValidatorTest;
|
|||
UlidConverterTest.class,
|
||||
UlidUtilTest.class,
|
||||
UlidValidatorTest.class,
|
||||
UlidStructTest.class,
|
||||
})
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package com.github.f4b6a3.ulid.util;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.Test;
|
||||
|
@ -8,6 +10,7 @@ import org.junit.Test;
|
|||
import com.github.f4b6a3.ulid.UlidCreator;
|
||||
import com.github.f4b6a3.ulid.util.UlidConverter;
|
||||
import com.github.f4b6a3.ulid.util.UlidValidator;
|
||||
import com.github.f4b6a3.ulid.util.internal.UlidStruct;
|
||||
|
||||
public class UlidConverterTest {
|
||||
|
||||
|
@ -32,4 +35,84 @@ public class UlidConverterTest {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToString1() {
|
||||
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
|
||||
Random random = new Random();
|
||||
final long time = random.nextLong();
|
||||
final long random1 = random.nextLong();
|
||||
final long random2 = random.nextLong();
|
||||
UlidStruct struct0 = new UlidStruct(time, random1, random2);
|
||||
|
||||
String string1 = struct0.toString();
|
||||
UlidStruct struct1 = new UlidStruct(string1);
|
||||
|
||||
assertEquals(struct0.time, struct1.time);
|
||||
assertEquals(struct0.random1, struct1.random1);
|
||||
assertEquals(struct0.random2, struct1.random2);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToString2() {
|
||||
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
|
||||
UUID ulid0 = UlidCreator.getUlid();
|
||||
UlidStruct struct0 = new UlidStruct(ulid0);
|
||||
|
||||
String string1 = UlidConverter.toString(ulid0);
|
||||
UlidStruct struct1 = new UlidStruct(string1);
|
||||
|
||||
assertEquals(struct0.time, struct1.time);
|
||||
assertEquals(struct0.random1, struct1.random1);
|
||||
assertEquals(struct0.random2, struct1.random2);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToString3() {
|
||||
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
|
||||
UUID ulid0 = UlidCreator.getUlid();
|
||||
UlidStruct struct0 = new UlidStruct(ulid0);
|
||||
|
||||
String string1 = struct0.toString();
|
||||
UlidStruct struct1 = new UlidStruct(string1);
|
||||
|
||||
assertEquals(struct0.time, struct1.time);
|
||||
assertEquals(struct0.random1, struct1.random1);
|
||||
assertEquals(struct0.random2, struct1.random2);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToString4() {
|
||||
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
|
||||
UUID ulid0 = UlidCreator.getUlid();
|
||||
UlidStruct struct0 = new UlidStruct(ulid0);
|
||||
|
||||
String string1 = UlidConverter.toString(ulid0);
|
||||
UlidStruct struct1 = new UlidStruct(string1);
|
||||
|
||||
String string2 = struct0.toString();
|
||||
UlidStruct struct2 = new UlidStruct(string2);
|
||||
|
||||
assertEquals(string1, string2);
|
||||
|
||||
assertEquals(struct0.time, struct1.time);
|
||||
assertEquals(struct0.random1, struct1.random1);
|
||||
assertEquals(struct0.random2, struct1.random2);
|
||||
|
||||
assertEquals(struct0.time, struct2.time);
|
||||
assertEquals(struct0.random1, struct2.random1);
|
||||
assertEquals(struct0.random2, struct2.random2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
package com.github.f4b6a3.ulid.util.internal;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class UlidStructTest {
|
||||
|
||||
private static final int DEFAULT_LOOP_MAX = 100_000;
|
||||
|
||||
protected static final char[] ALPHABET_CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".toCharArray();
|
||||
protected static final char[] ALPHABET_JAVA = "0123456789abcdefghijklmnopqrstuv".toCharArray(); // Long.parseUnsignedLong()
|
||||
|
||||
@Test
|
||||
public void testConstructorLongs() {
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
Random random = new Random();
|
||||
final long time = random.nextLong();
|
||||
final long random1 = random.nextLong();
|
||||
final long random2 = random.nextLong();
|
||||
UlidStruct struct0 = new UlidStruct(time, random1, random2); // <-- under test
|
||||
|
||||
assertEquals(time & 0xffffffffffffL, struct0.time);
|
||||
assertEquals(random1 & 0xffffffffffL, struct0.random1);
|
||||
assertEquals(random2 & 0xffffffffffL, struct0.random2);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructorString() {
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
Random random = new Random();
|
||||
final long time = random.nextLong();
|
||||
final long random1 = random.nextLong();
|
||||
final long random2 = random.nextLong();
|
||||
UlidStruct struct0 = new UlidStruct(time, random1, random2);
|
||||
|
||||
String string1 = toString(struct0);
|
||||
UlidStruct struct1 = new UlidStruct(string1); // <-- under test
|
||||
assertEquals(struct0, struct1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructorUuid() {
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
Random random = new Random();
|
||||
final long msb = random.nextLong();
|
||||
final long lsb = random.nextLong();
|
||||
final UUID uuid0 = new UUID(msb, lsb);
|
||||
UlidStruct struct0 = new UlidStruct(uuid0); // <-- under test
|
||||
|
||||
UUID uuid1 = toUuid(struct0);
|
||||
assertEquals(uuid0, uuid1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToString() {
|
||||
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
Random random = new Random();
|
||||
final long time = random.nextLong();
|
||||
final long random1 = random.nextLong();
|
||||
final long random2 = random.nextLong();
|
||||
UlidStruct struct0 = new UlidStruct(time, random1, random2);
|
||||
|
||||
String string1 = toString(struct0);
|
||||
String string2 = struct0.toString(); // <-- under test
|
||||
assertEquals(string1, string2);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToUuid() {
|
||||
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
Random random = new Random();
|
||||
final long time = random.nextLong();
|
||||
final long random1 = random.nextLong();
|
||||
final long random2 = random.nextLong();
|
||||
UlidStruct struct0 = new UlidStruct(time, random1, random2);
|
||||
|
||||
UUID uuid1 = toUuid(struct0);
|
||||
UUID uuid2 = struct0.toUuid(); // <-- under test
|
||||
assertEquals(uuid1, uuid2);
|
||||
}
|
||||
}
|
||||
|
||||
public UlidStruct fromString(String string) {
|
||||
|
||||
long time = 0;
|
||||
long random1 = 0;
|
||||
long random2 = 0;
|
||||
|
||||
String tm = string.substring(0, 10);
|
||||
String r1 = string.substring(10, 18);
|
||||
String r2 = string.substring(18, 26);
|
||||
|
||||
tm = transliterate(tm, ALPHABET_CROCKFORD, ALPHABET_JAVA);
|
||||
r1 = transliterate(r1, ALPHABET_CROCKFORD, ALPHABET_JAVA);
|
||||
r2 = transliterate(r2, ALPHABET_CROCKFORD, ALPHABET_JAVA);
|
||||
|
||||
time = Long.parseUnsignedLong(tm, 32);
|
||||
random1 = Long.parseUnsignedLong(r1, 32);
|
||||
random2 = Long.parseUnsignedLong(r2, 32);
|
||||
|
||||
return new UlidStruct(time, random1, random2);
|
||||
}
|
||||
|
||||
public UUID toUuid(UlidStruct struct) {
|
||||
|
||||
long time = struct.time & 0xffffffffffffL;
|
||||
long random1 = struct.random1 & 0xffffffffffL;
|
||||
long random2 = struct.random2 & 0xffffffffffL;
|
||||
|
||||
final long msb = (time << 16) | (random1 >>> 24);
|
||||
final long lsb = (random1 << 40) | random2;
|
||||
|
||||
return new UUID(msb, lsb);
|
||||
}
|
||||
|
||||
public String toString(UlidStruct struct) {
|
||||
|
||||
final String tzero = "0000000000";
|
||||
final String rzero = "00000000";
|
||||
|
||||
String time = Long.toUnsignedString(struct.time, 32);
|
||||
String random1 = Long.toUnsignedString(struct.random1, 32);
|
||||
String random2 = Long.toUnsignedString(struct.random2, 32);
|
||||
|
||||
time = tzero.substring(0, tzero.length() - time.length()) + time;
|
||||
random1 = rzero.substring(0, rzero.length() - random1.length()) + random1;
|
||||
random2 = rzero.substring(0, rzero.length() - random2.length()) + random2;
|
||||
|
||||
time = transliterate(time, ALPHABET_JAVA, ALPHABET_CROCKFORD);
|
||||
random1 = transliterate(random1, ALPHABET_JAVA, ALPHABET_CROCKFORD);
|
||||
random2 = transliterate(random2, ALPHABET_JAVA, ALPHABET_CROCKFORD);
|
||||
|
||||
return time + random1 + random2;
|
||||
}
|
||||
|
||||
private static String transliterate(String string, char[] alphabet1, char[] alphabet2) {
|
||||
char[] output = string.toCharArray();
|
||||
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 new String(output);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue