Development of version 3.0.0 #7
Changed the uuid-creator to generate two types of ULID: default (non-monotonic) and monotonic. Until version 2.x.x this library only created monotonic ULIDs. The version 3.0.0 breaks compatibility. List of changes: Changed UlidCreator Created Ulid Created UlidSpecCreator // abstract Created DefaultUlidSpecCreator // implementation Created MonotonicUlidSpecCreator // implementation Removed UlidConverter Removed UlidUtil Removed UlidStruct Create and update test cases and more... Test coverage: 93.5%
This commit is contained in:
parent
5289a469b2
commit
2ffa7a95dd
|
@ -22,34 +22,33 @@
|
|||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.github.f4b6a3.ulid.util.internal;
|
||||
package com.github.f4b6a3.ulid;
|
||||
|
||||
import java.io.Serializable;
|
||||
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.
|
||||
* This class represents a ULID.
|
||||
*/
|
||||
public final class UlidStruct {
|
||||
public final class Ulid implements Serializable, Comparable<Ulid> {
|
||||
|
||||
public final long time;
|
||||
public final long random1;
|
||||
public final long random2;
|
||||
private final long msb;
|
||||
private final long lsb;
|
||||
|
||||
protected static final long TIMESTAMP_COMPONENT = 0x0000ffffffffffffL;
|
||||
protected static final long HALF_RANDOM_COMPONENT = 0x000000ffffffffffL;
|
||||
protected static final int STRING_LENGTH = 26;
|
||||
|
||||
public static final char[] BASE32_CHARS = //
|
||||
protected 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];
|
||||
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;
|
||||
|
@ -118,28 +117,19 @@ public final class UlidStruct {
|
|||
|
||||
}
|
||||
|
||||
private UlidStruct() {
|
||||
this.time = 0;
|
||||
this.random1 = 0;
|
||||
this.random2 = 0;
|
||||
private static final long serialVersionUID = 2625269413446854731L;
|
||||
|
||||
private Ulid() {
|
||||
this.msb = 0;
|
||||
this.lsb = 0;
|
||||
}
|
||||
|
||||
private UlidStruct(long time, long random1, long random2) {
|
||||
this.time = time & TIMESTAMP_COMPONENT;
|
||||
this.random1 = random1 & HALF_RANDOM_COMPONENT;
|
||||
this.random2 = random2 & HALF_RANDOM_COMPONENT;
|
||||
private Ulid(UUID ulid) {
|
||||
this.msb = ulid.getMostSignificantBits();
|
||||
this.lsb = ulid.getLeastSignificantBits();
|
||||
}
|
||||
|
||||
private UlidStruct(UUID ulid) {
|
||||
final long msb = ulid.getMostSignificantBits();
|
||||
final long lsb = ulid.getLeastSignificantBits();
|
||||
|
||||
this.time = (msb >>> 16);
|
||||
this.random1 = ((msb & 0x000000000000ffffL) << 24) | (lsb >>> 40);
|
||||
this.random2 = (lsb & 0x000000ffffffffffL);
|
||||
}
|
||||
|
||||
private UlidStruct(String ulid) {
|
||||
private Ulid(String ulid) {
|
||||
|
||||
final char[] chars = ulid == null ? new char[0] : ulid.toCharArray();
|
||||
UlidValidator.validate(chars);
|
||||
|
@ -177,26 +167,41 @@ public final class UlidStruct {
|
|||
r2 |= BASE32_VALUES[chars[0x18]] << 5;
|
||||
r2 |= BASE32_VALUES[chars[0x19]];
|
||||
|
||||
this.time = tm;
|
||||
this.random1 = r1;
|
||||
this.random2 = r2;
|
||||
this.msb = (tm << 16) | (r1 >>> 24);
|
||||
this.lsb = (r1 << 40) | (r2 & 0xffffffffffL);
|
||||
}
|
||||
|
||||
public static UlidStruct of(long time, long random1, long random2) {
|
||||
return new UlidStruct(time, random1, random2);
|
||||
private Ulid(long msb, long lsb) {
|
||||
this.msb = msb;
|
||||
this.lsb = lsb;
|
||||
}
|
||||
|
||||
public static UlidStruct of(UUID ulid) {
|
||||
return new UlidStruct(ulid);
|
||||
public static Ulid of(UUID ulid) {
|
||||
return new Ulid(ulid);
|
||||
}
|
||||
|
||||
public static UlidStruct of(String ulid) {
|
||||
return new UlidStruct(ulid);
|
||||
public static Ulid of(String ulid) {
|
||||
return new Ulid(ulid);
|
||||
}
|
||||
|
||||
public static Ulid of(long msb, long lsb) {
|
||||
return new Ulid(msb, lsb);
|
||||
}
|
||||
|
||||
public UUID toUuid() {
|
||||
return new UUID(this.msb, this.lsb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
final char[] chars = new char[26];
|
||||
final char[] chars = new char[STRING_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] = BASE32_CHARS[(int) (time >>> 45 & 0b11111)];
|
||||
chars[0x01] = BASE32_CHARS[(int) (time >>> 40 & 0b11111)];
|
||||
|
@ -230,33 +235,16 @@ public final class UlidStruct {
|
|||
return new String(chars);
|
||||
}
|
||||
|
||||
public String toString4() {
|
||||
// apply RFC-4122 version 4 and variant 2
|
||||
final long random1v4 = ((this.random1 & 0x0fff3fffffL) | 0x4000000000L) | 0x0000800000L;
|
||||
return UlidStruct.of(this.time, random1v4, this.random2).toString();
|
||||
}
|
||||
|
||||
public UUID toUuid() {
|
||||
|
||||
final long msb = (time << 16) | (random1 >>> 24);
|
||||
final long lsb = (random1 << 40) | random2;
|
||||
|
||||
return new UUID(msb, lsb);
|
||||
}
|
||||
|
||||
public UUID toUuid4() {
|
||||
// apply RFC-4122 version 4 and variant 2
|
||||
final long random1v4 = ((this.random1 & 0x0fff3fffffL) | 0x4000000000L) | 0x0000800000L;
|
||||
return UlidStruct.of(this.time, random1v4, this.random2).toUuid();
|
||||
public long getTimestamp() {
|
||||
return this.msb >>> 16;
|
||||
}
|
||||
|
||||
@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));
|
||||
result = prime * result + (int) (lsb ^ (lsb >>> 32));
|
||||
result = prime * result + (int) (msb ^ (msb >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -268,13 +256,29 @@ public final class UlidStruct {
|
|||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
UlidStruct other = (UlidStruct) obj;
|
||||
if (random1 != other.random1)
|
||||
Ulid other = (Ulid) obj;
|
||||
if (lsb != other.lsb)
|
||||
return false;
|
||||
if (random2 != other.random2)
|
||||
return false;
|
||||
if (time != other.time)
|
||||
if (msb != other.msb)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Ulid other) {
|
||||
|
||||
if (this.msb < other.msb)
|
||||
return -1;
|
||||
|
||||
if (this.msb > other.msb)
|
||||
return 1;
|
||||
|
||||
if (this.lsb < other.lsb)
|
||||
return -1;
|
||||
|
||||
if (this.lsb > other.lsb)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -24,116 +24,44 @@
|
|||
|
||||
package com.github.f4b6a3.ulid;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.github.f4b6a3.ulid.creator.UlidSpecCreator;
|
||||
import com.github.f4b6a3.ulid.exception.InvalidUlidException;
|
||||
import com.github.f4b6a3.ulid.util.UlidConverter;
|
||||
import com.github.f4b6a3.ulid.creator.impl.DefaultUlidSpecCreator;
|
||||
import com.github.f4b6a3.ulid.creator.impl.MonotonicUlidSpecCreator;
|
||||
|
||||
/**
|
||||
* A factory for Universally Unique Lexicographically Sortable Identifiers.
|
||||
*
|
||||
* See the ULID spec: https://github.com/ulid/spec
|
||||
*/
|
||||
public final class UlidCreator {
|
||||
|
||||
private UlidCreator() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ULID as GUID from a string.
|
||||
*
|
||||
* The input string must be encoded to Crockford's base32, following the ULID
|
||||
* specification.
|
||||
*
|
||||
* An exception is thrown if the ULID string is invalid.
|
||||
*
|
||||
* @param ulid a ULID string
|
||||
* @return a UUID
|
||||
* @throws InvalidUlidException if invalid
|
||||
*/
|
||||
public static UUID fromString(String ulid) {
|
||||
return UlidConverter.fromString(ulid);
|
||||
public static Ulid getUlid() {
|
||||
return DefaultCreatorHolder.INSTANCE.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a UUID to ULID string
|
||||
*
|
||||
* The returning string is encoded to Crockford's base32.
|
||||
*
|
||||
* @param uuid a UUID
|
||||
* @return a ULID string
|
||||
*/
|
||||
public static String toString(UUID ulid) {
|
||||
return UlidConverter.toString(ulid);
|
||||
public static Ulid getUlid(Long timestamp) {
|
||||
return DefaultCreatorHolder.INSTANCE.create(timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ULID as GUID.
|
||||
*
|
||||
* The random component is generated by a secure random number generator:
|
||||
* {@link java.security.SecureRandom}.
|
||||
*
|
||||
* @return a UUID
|
||||
*/
|
||||
public static UUID getUlid() {
|
||||
return UlidSpecCreatorHolder.INSTANCE.create();
|
||||
public static Ulid getMonotonicUlid() {
|
||||
return MonotonicCreatorHolder.INSTANCE.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ULID as GUID.
|
||||
*
|
||||
* It is compatible with the RFC-4122 UUID v4.
|
||||
*
|
||||
* The random component is generated by a secure random number generator:
|
||||
* {@link java.security.SecureRandom}.
|
||||
*
|
||||
* @return a UUID
|
||||
*/
|
||||
public static UUID getUlid4() {
|
||||
return UlidSpecCreatorHolder.INSTANCE.create4();
|
||||
public static Ulid getMonotonicUlid(Long timestamp) {
|
||||
return MonotonicCreatorHolder.INSTANCE.create(timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ULID string.
|
||||
*
|
||||
* The returning string is encoded to Crockford's base32.
|
||||
*
|
||||
* The random component is generated by a secure random number generator:
|
||||
* {@link java.security.SecureRandom}.
|
||||
*
|
||||
* @return a ULID string
|
||||
*/
|
||||
public static String getUlidString() {
|
||||
return UlidSpecCreatorHolder.INSTANCE.createString();
|
||||
public static DefaultUlidSpecCreator getDefaultCreator() {
|
||||
return new DefaultUlidSpecCreator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ULID string.
|
||||
*
|
||||
* It is compatible with the RFC-4122 UUID v4.
|
||||
*
|
||||
* The returning string is encoded to Crockford's base32.
|
||||
*
|
||||
* The random component is generated by a secure random number generator:
|
||||
* {@link java.security.SecureRandom}.
|
||||
*
|
||||
* @return a ULID string
|
||||
*/
|
||||
public static String getUlidString4() {
|
||||
return UlidSpecCreatorHolder.INSTANCE.createString4();
|
||||
public static MonotonicUlidSpecCreator getMonotonicCreator() {
|
||||
return new MonotonicUlidSpecCreator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a GUID creator for direct use.
|
||||
*
|
||||
* @return a {@link UlidSpecCreator}
|
||||
*/
|
||||
public static UlidSpecCreator getUlidSpecCreator() {
|
||||
return new UlidSpecCreator();
|
||||
private static class DefaultCreatorHolder {
|
||||
static final UlidSpecCreator INSTANCE = getDefaultCreator();
|
||||
}
|
||||
|
||||
private static class UlidSpecCreatorHolder {
|
||||
static final UlidSpecCreator INSTANCE = getUlidSpecCreator();
|
||||
private static class MonotonicCreatorHolder {
|
||||
static final UlidSpecCreator INSTANCE = getMonotonicCreator();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,35 +25,15 @@
|
|||
package com.github.f4b6a3.ulid.creator;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.github.f4b6a3.ulid.Ulid;
|
||||
import com.github.f4b6a3.ulid.strategy.RandomStrategy;
|
||||
import com.github.f4b6a3.ulid.strategy.random.DefaultRandomStrategy;
|
||||
import com.github.f4b6a3.ulid.strategy.random.OtherRandomStrategy;
|
||||
import com.github.f4b6a3.ulid.strategy.TimestampStrategy;
|
||||
import com.github.f4b6a3.ulid.strategy.timestamp.DefaultTimestampStrategy;
|
||||
import com.github.f4b6a3.ulid.util.internal.UlidStruct;
|
||||
|
||||
/**
|
||||
* Factory that creates lexicographically sortable GUIDs, based on the ULID
|
||||
* specification - Universally Unique Lexicographically Sortable Identifier.
|
||||
*
|
||||
* ULID specification: https://github.com/ulid/spec
|
||||
*/
|
||||
public class UlidSpecCreator {
|
||||
|
||||
protected long random1 = 0;
|
||||
protected long random2 = 0;
|
||||
|
||||
protected long randomMax1;
|
||||
protected long randomMax2;
|
||||
|
||||
protected static final long HALF_RANDOM_COMPONENT = 0x000000ffffffffffL;
|
||||
protected static final long INCREMENT_MAX = 0x0000010000000000L;
|
||||
|
||||
protected long previousTimestamp;
|
||||
|
||||
protected static final String OVERRUN_MESSAGE = "The system overran the generator by requesting too many ULIDs.";
|
||||
public abstract class UlidSpecCreator {
|
||||
|
||||
protected TimestampStrategy timestampStrategy;
|
||||
protected RandomStrategy randomStrategy;
|
||||
|
@ -63,164 +43,11 @@ public class UlidSpecCreator {
|
|||
this.randomStrategy = new DefaultRandomStrategy();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Return a GUID based on the ULID specification.
|
||||
*
|
||||
* A ULID has two parts:
|
||||
*
|
||||
* 1. A part of 48 bits that represent the amount of milliseconds since Unix
|
||||
* Epoch, 1 January 1970.
|
||||
*
|
||||
* 2. A part of 80 bits that has a random value generated a secure random
|
||||
* generator.
|
||||
*
|
||||
* The random part is reset to a new value every time the millisecond part
|
||||
* changes.
|
||||
*
|
||||
* If more than one GUID is generated within the same millisecond, the random
|
||||
* part is incremented by one.
|
||||
*
|
||||
* The maximum GUIDs that can be generated per millisecond is 2^80.
|
||||
*
|
||||
* The random part is generated by a secure random number generator:
|
||||
* {@link java.security.SecureRandom}.
|
||||
*
|
||||
* ### Specification of Universally Unique Lexicographically Sortable ID
|
||||
*
|
||||
* #### Components
|
||||
*
|
||||
* ##### Timestamp
|
||||
*
|
||||
* It is a 48 bit integer. UNIX-time in milliseconds. Won't run out of space
|
||||
* 'til the year 10889 AD.
|
||||
*
|
||||
* ##### Randomness
|
||||
*
|
||||
* It is a 80 bits integer. Cryptographically secure source of randomness, if
|
||||
* possible.
|
||||
*
|
||||
* #### Sorting
|
||||
*
|
||||
* The left-most character must be sorted first, and the right-most character
|
||||
* sorted last (lexical order). The default ASCII character set must be used.
|
||||
* Within the same millisecond, sort order is not guaranteed.
|
||||
*
|
||||
* #### Monotonicity
|
||||
*
|
||||
* When generating a ULID within the same millisecond, we can provide some
|
||||
* guarantees regarding sort order. Namely, if the same millisecond is detected,
|
||||
* the random component is incremented by 1 bit in the least significant bit
|
||||
* position (with carrying).
|
||||
*
|
||||
* If, in the extremely unlikely event that, you manage to generate more than
|
||||
* 2^80 ULIDs within the same millisecond, or cause the random component to
|
||||
* overflow with less, the generation will fail.
|
||||
*
|
||||
* @return {@link UUID} a GUID value
|
||||
*/
|
||||
public synchronized UUID create() {
|
||||
return UlidStruct.of(this.getTimestamp(), random1, random2).toUuid();
|
||||
public synchronized Ulid create() {
|
||||
return create(null);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Return a GUID based on the ULID specification.
|
||||
*
|
||||
* It is compatible with the RFC-4122 UUID v4.
|
||||
*
|
||||
* @return {@link UUID} a GUID value
|
||||
*/
|
||||
public synchronized UUID create4() {
|
||||
return UlidStruct.of(this.getTimestamp(), random1, random2).toUuid4();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ULID string.
|
||||
*
|
||||
* The returning string is encoded to Crockford's base32.
|
||||
*
|
||||
* The random component is generated by a secure random number generator:
|
||||
* {@link java.security.SecureRandom}.
|
||||
*
|
||||
* @return a ULID string
|
||||
*/
|
||||
public synchronized String createString() {
|
||||
return UlidStruct.of(this.getTimestamp(), random1, random2).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ULID string.
|
||||
*
|
||||
* It is compatible with the RFC-4122 UUID v4.
|
||||
*
|
||||
* The returning string is encoded to Crockford's base32.
|
||||
*
|
||||
* The random component is generated by a secure random number generator:
|
||||
* {@link java.security.SecureRandom}.
|
||||
*
|
||||
* @return a ULID string
|
||||
*/
|
||||
public synchronized String createString4() {
|
||||
return UlidStruct.of(this.getTimestamp(), random1, random2).toString4();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current timestamp and resets or increments the random part.
|
||||
*
|
||||
* @return timestamp
|
||||
*/
|
||||
protected synchronized long getTimestamp() {
|
||||
|
||||
final long timestamp = this.timestampStrategy.getTimestamp();
|
||||
|
||||
if (timestamp == this.previousTimestamp) {
|
||||
this.increment();
|
||||
} else {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
this.previousTimestamp = timestamp;
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the random part of the GUID.
|
||||
*/
|
||||
protected synchronized void reset() {
|
||||
|
||||
// Get random values
|
||||
final byte[] bytes = new byte[10];
|
||||
this.randomStrategy.nextBytes(bytes);
|
||||
|
||||
this.random1 = (long) (bytes[0x0] & 0xff) << 32;
|
||||
this.random1 |= (long) (bytes[0x1] & 0xff) << 24;
|
||||
this.random1 |= (long) (bytes[0x2] & 0xff) << 16;
|
||||
this.random1 |= (long) (bytes[0x3] & 0xff) << 8;
|
||||
this.random1 |= (long) (bytes[0x4] & 0xff);
|
||||
|
||||
this.random2 = (long) (bytes[0x5] & 0xff) << 32;
|
||||
this.random2 |= (long) (bytes[0x6] & 0xff) << 24;
|
||||
this.random2 |= (long) (bytes[0x7] & 0xff) << 16;
|
||||
this.random2 |= (long) (bytes[0x8] & 0xff) << 8;
|
||||
this.random2 |= (long) (bytes[0x9] & 0xff);
|
||||
|
||||
// Save the random values
|
||||
this.randomMax1 = this.random1 | INCREMENT_MAX;
|
||||
this.randomMax2 = this.random2 | INCREMENT_MAX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the random part of the GUID.
|
||||
*/
|
||||
protected synchronized void increment() {
|
||||
if (++this.random2 >= this.randomMax2) {
|
||||
this.random2 = this.random2 & HALF_RANDOM_COMPONENT;
|
||||
if ((++this.random1 >= this.randomMax1)) {
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
public abstract Ulid create(Long timestamp);
|
||||
|
||||
/**
|
||||
* Used for changing the timestamp strategy.
|
||||
|
@ -266,18 +93,4 @@ public class UlidSpecCreator {
|
|||
this.randomStrategy = new OtherRandomStrategy(random);
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* For unit tests
|
||||
*/
|
||||
protected long extractRandom1(UUID uuid) {
|
||||
return ((uuid.getMostSignificantBits() & 0x000000000000ffff) << 24) | (uuid.getLeastSignificantBits() >>> 40);
|
||||
}
|
||||
|
||||
/**
|
||||
* For unit tests
|
||||
*/
|
||||
protected long extractRandom2(UUID uuid) {
|
||||
return uuid.getLeastSignificantBits() & HALF_RANDOM_COMPONENT;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,43 +22,38 @@
|
|||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.github.f4b6a3.ulid.util;
|
||||
package com.github.f4b6a3.ulid.creator.impl;
|
||||
|
||||
import java.util.UUID;
|
||||
import com.github.f4b6a3.ulid.Ulid;
|
||||
import com.github.f4b6a3.ulid.creator.UlidSpecCreator;
|
||||
|
||||
import com.github.f4b6a3.ulid.exception.InvalidUlidException;
|
||||
import com.github.f4b6a3.ulid.util.internal.UlidStruct;
|
||||
public final class DefaultUlidSpecCreator extends UlidSpecCreator {
|
||||
|
||||
public final class UlidConverter {
|
||||
@Override
|
||||
public synchronized Ulid create(final Long timestamp) {
|
||||
|
||||
private UlidConverter() {
|
||||
}
|
||||
final long time = timestamp != null ? timestamp : this.timestampStrategy.getTimestamp();
|
||||
|
||||
/**
|
||||
* Convert a UUID to ULID string
|
||||
*
|
||||
* The returning string is encoded to Crockford's base32.
|
||||
*
|
||||
* @param ulid a UUID
|
||||
* @return a ULID
|
||||
*/
|
||||
public static String toString(final UUID ulid) {
|
||||
return UlidStruct.of(ulid).toString();
|
||||
}
|
||||
// Get random values
|
||||
final byte[] bytes = new byte[10];
|
||||
this.randomStrategy.nextBytes(bytes);
|
||||
|
||||
/**
|
||||
* Converts a ULID string to a UUID.
|
||||
*
|
||||
* The input string must be encoded to Crockford's base32, following the ULID
|
||||
* specification.
|
||||
*
|
||||
* An exception is thrown if the ULID string is invalid.
|
||||
*
|
||||
* @param ulid a ULID
|
||||
* @return a UUID if valid
|
||||
* @throws InvalidUlidException if invalid
|
||||
*/
|
||||
public static UUID fromString(final String ulid) {
|
||||
return UlidStruct.of(ulid).toUuid();
|
||||
long msb = 0;
|
||||
long lsb = 0;
|
||||
|
||||
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 Ulid.of(msb, lsb);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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 {
|
||||
|
||||
protected long msb = 0;
|
||||
protected long lsb = 0;
|
||||
|
||||
protected long previousTimestamp;
|
||||
|
||||
public synchronized Ulid create(final Long timestamp) {
|
||||
|
||||
final long time = timestamp != null ? timestamp : this.timestampStrategy.getTimestamp();
|
||||
|
||||
if (time == this.previousTimestamp) {
|
||||
this.lsb++;
|
||||
} else {
|
||||
// Get random values
|
||||
final byte[] bytes = new byte[10];
|
||||
this.randomStrategy.nextBytes(bytes);
|
||||
|
||||
msb = 0;
|
||||
lsb = 0;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
this.previousTimestamp = time;
|
||||
return Ulid.of(msb, lsb);
|
||||
}
|
||||
|
||||
}
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
package com.github.f4b6a3.ulid.strategy;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface RandomStrategy {
|
||||
void nextBytes(byte[] bytes);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
package com.github.f4b6a3.ulid.strategy;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface TimestampStrategy {
|
||||
long getTimestamp();
|
||||
}
|
||||
|
|
|
@ -1,83 +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 java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.github.f4b6a3.ulid.util.internal.UlidStruct;
|
||||
|
||||
public final class UlidUtil {
|
||||
|
||||
protected static final int BASE_32 = 32;
|
||||
|
||||
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() {
|
||||
}
|
||||
|
||||
public static long extractUnixMilliseconds(UUID ulid) {
|
||||
return extractTimestamp(ulid);
|
||||
}
|
||||
|
||||
public static long extractUnixMilliseconds(String ulid) {
|
||||
UlidValidator.validate(ulid);
|
||||
return extractTimestamp(ulid);
|
||||
}
|
||||
|
||||
public static Instant extractInstant(UUID ulid) {
|
||||
long milliseconds = extractTimestamp(ulid);
|
||||
return Instant.ofEpochMilli(milliseconds);
|
||||
}
|
||||
|
||||
public static Instant extractInstant(String ulid) {
|
||||
UlidValidator.validate(ulid);
|
||||
long milliseconds = extractTimestamp(ulid);
|
||||
return Instant.ofEpochMilli(milliseconds);
|
||||
}
|
||||
|
||||
protected static long extractTimestamp(UUID ulid) {
|
||||
return (ulid.getMostSignificantBits() >>> 16);
|
||||
}
|
||||
|
||||
protected static long extractTimestamp(String ulid) {
|
||||
return UlidStruct.of(ulid).time;
|
||||
}
|
||||
|
||||
public static String extractTimestampComponent(String ulid) {
|
||||
UlidValidator.validate(ulid);
|
||||
return ulid.substring(0, 10);
|
||||
}
|
||||
|
||||
public static String extractRandomnessComponent(String ulid) {
|
||||
UlidValidator.validate(ulid);
|
||||
return ulid.substring(10, ULID_LENGTH);
|
||||
}
|
||||
}
|
|
@ -26,11 +26,82 @@ package com.github.f4b6a3.ulid.util;
|
|||
|
||||
import com.github.f4b6a3.ulid.exception.InvalidUlidException;
|
||||
|
||||
import static com.github.f4b6a3.ulid.util.internal.UlidStruct.BASE32_VALUES;
|
||||
|
||||
public final class UlidValidator {
|
||||
|
||||
protected static final int ULID_LENGTH = 26;
|
||||
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() {
|
||||
}
|
||||
|
@ -147,6 +218,6 @@ public final class UlidValidator {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
return (c.length - hyphen) == ULID_LENGTH;
|
||||
return (c.length - hyphen) == STRING_LENGTH;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,22 +4,22 @@ import org.junit.runner.RunWith;
|
|||
import org.junit.runners.Suite;
|
||||
|
||||
import com.github.f4b6a3.ulid.creator.UlidSpecCreatorTest;
|
||||
import com.github.f4b6a3.ulid.ulid.UlidCreatorUuidTest;
|
||||
import com.github.f4b6a3.ulid.ulid.UlidCreatorStringTest;
|
||||
import com.github.f4b6a3.ulid.util.UlidConverterTest;
|
||||
import com.github.f4b6a3.ulid.util.UlidUtilTest;
|
||||
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.UlidStructTest;
|
||||
import com.github.f4b6a3.ulid.util.internal.UlidTest;
|
||||
|
||||
@RunWith(Suite.class)
|
||||
@Suite.SuiteClasses({
|
||||
UlidCreatorUuidTest.class,
|
||||
UlidCreatorStringTest.class,
|
||||
UlidSpecCreatorTest.class,
|
||||
UlidConverterTest.class,
|
||||
UlidUtilTest.class,
|
||||
UlidValidatorTest.class,
|
||||
UlidStructTest.class,
|
||||
UlidCreatorDefaultTest.class,
|
||||
UlidCreatorDefaultStringTest.class,
|
||||
UlidCreatorMonotonicTest.class,
|
||||
UlidCreatorMonotonicStringTest.class,
|
||||
UlidSpecCreatorTest.class,
|
||||
UlidValidatorTest.class,
|
||||
UlidTest.class,
|
||||
})
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package com.github.f4b6a3.ulid;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.github.f4b6a3.ulid.UlidCreator;
|
||||
import com.github.f4b6a3.ulid.creator.UlidSpecCreator;
|
||||
import com.github.f4b6a3.ulid.strategy.timestamp.FixedTimestampStretegy;
|
||||
|
@ -21,11 +19,11 @@ public class UniquenessTest {
|
|||
private int requestCount; // Number of requests for thread
|
||||
|
||||
// private long[][] cacheLong; // Store values generated per thread
|
||||
private HashSet<UUID> hashSet;
|
||||
private HashSet<Ulid> hashSet;
|
||||
|
||||
private boolean verbose; // Show progress or not
|
||||
|
||||
// GUID creator based on ULID spec
|
||||
// ULID Spec creator
|
||||
private UlidSpecCreator creator;
|
||||
|
||||
/**
|
||||
|
@ -92,20 +90,20 @@ public class UniquenessTest {
|
|||
for (int i = 0; i < max; i++) {
|
||||
|
||||
// Request a UUID
|
||||
UUID uuid = creator.create();
|
||||
Ulid ulid = creator.create();
|
||||
|
||||
if (verbose) {
|
||||
// Calculate and show progress
|
||||
progress = (int) ((i * 1.0 / max) * 100);
|
||||
if (progress % 10 == 0) {
|
||||
System.out.println(String.format("[Thread %06d] %s %s %s%%", id, uuid, i, (int) progress));
|
||||
System.out.println(String.format("[Thread %06d] %s %s %s%%", id, ulid, i, (int) progress));
|
||||
}
|
||||
}
|
||||
synchronized (hashSet) {
|
||||
// Insert the value in cache, if it does not exist in it.
|
||||
if (!hashSet.add(uuid)) {
|
||||
if (!hashSet.add(ulid)) {
|
||||
System.err.println(
|
||||
String.format("[Thread %06d] %s %s %s%% [DUPLICATE]", id, uuid, i, (int) progress));
|
||||
String.format("[Thread %06d] %s %s %s%% [DUPLICATE]", id, ulid, i, (int) progress));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -118,7 +116,7 @@ public class UniquenessTest {
|
|||
}
|
||||
|
||||
public static void execute(boolean verbose, int threadCount, int requestCount) {
|
||||
UlidSpecCreator creator = UlidCreator.getUlidSpecCreator()
|
||||
UlidSpecCreator creator = UlidCreator.getMonotonicCreator()
|
||||
.withTimestampStrategy(new FixedTimestampStretegy(System.currentTimeMillis()));
|
||||
|
||||
UniquenessTest test = new UniquenessTest(threadCount, requestCount, creator, verbose);
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
package com.github.f4b6a3.ulid.creator;
|
||||
|
||||
import com.github.f4b6a3.ulid.creator.UlidSpecCreator;
|
||||
|
||||
class UlidSpecCreatorMock extends UlidSpecCreator {
|
||||
|
||||
public UlidSpecCreatorMock(long random1, long random2, long randomMax1, long randomMax2, long previousTimestamp) {
|
||||
super();
|
||||
|
||||
this.random1 = random1;
|
||||
this.random2 = random2;
|
||||
|
||||
this.randomMax1 = randomMax1;
|
||||
this.randomMax2 = randomMax2;
|
||||
|
||||
this.previousTimestamp = previousTimestamp;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package com.github.f4b6a3.ulid.creator;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.HashSet;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
@ -8,8 +7,8 @@ import java.util.UUID;
|
|||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.github.f4b6a3.ulid.Ulid;
|
||||
import com.github.f4b6a3.ulid.UlidCreator;
|
||||
import com.github.f4b6a3.ulid.strategy.timestamp.FixedTimestampStretegy;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
@ -17,14 +16,12 @@ public class UlidSpecCreatorTest {
|
|||
|
||||
private static final int DEFAULT_LOOP_MAX = 1_000_000;
|
||||
|
||||
private static final long TIMESTAMP = System.currentTimeMillis();
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
protected static final String DUPLICATE_UUID_MSG = "A duplicate ULID was created.";
|
||||
|
||||
protected static final int THREAD_TOTAL = availableProcessors();
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
private static int availableProcessors() {
|
||||
int processors = Runtime.getRuntime().availableProcessors();
|
||||
if (processors < 4) {
|
||||
|
@ -34,185 +31,53 @@ public class UlidSpecCreatorTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testRandomMostSignificantBits() {
|
||||
|
||||
UlidSpecCreator creator = new UlidSpecCreator();
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||
|
||||
UUID uuid = creator.create();
|
||||
long firstRand1 = creator.extractRandom1(uuid);
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
uuid = creator.create();
|
||||
|
||||
public void testGetUlidTimestamp() {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
long timestamp = RANDOM.nextLong() & 0x0000ffffffffffffL;
|
||||
Ulid ulid = UlidCreator.getUlid(timestamp);
|
||||
assertEquals(timestamp, ulid.getTimestamp());
|
||||
}
|
||||
|
||||
long lastRand1 = creator.extractRandom1(uuid);
|
||||
long expected1 = firstRand1;
|
||||
assertEquals(String.format("The last high random should be iqual to the first %s.", expected1), expected1,
|
||||
lastRand1);
|
||||
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP + 1));
|
||||
uuid = creator.create();
|
||||
lastRand1 = uuid.getMostSignificantBits();
|
||||
assertNotEquals("The last high random should be random after timestamp changed.", firstRand1, lastRand1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRandomLeastSignificantBits() {
|
||||
|
||||
UlidSpecCreator creator = new UlidSpecCreator();
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||
|
||||
UUID uuid = creator.create();
|
||||
long firstRnd2 = creator.extractRandom2(uuid);
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
uuid = creator.create();
|
||||
public void testGetMonotonicUlidTimestamp() {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
long timestamp = RANDOM.nextLong() & 0x0000ffffffffffffL;
|
||||
Ulid ulid = UlidCreator.getMonotonicUlid(timestamp);
|
||||
assertEquals(timestamp, ulid.getTimestamp());
|
||||
}
|
||||
|
||||
long lastRand2 = creator.extractRandom2(uuid);
|
||||
long expected = firstRnd2 + DEFAULT_LOOP_MAX;
|
||||
assertEquals(String.format("The last low random should be iqual to %s.", expected), expected, lastRand2);
|
||||
|
||||
long notExpected = firstRnd2 + DEFAULT_LOOP_MAX + 1;
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP + 1));
|
||||
uuid = creator.create();
|
||||
lastRand2 = uuid.getLeastSignificantBits();
|
||||
assertNotEquals("The last low random should be random after timestamp changed.", notExpected, lastRand2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncrementOfRandomLeastSignificantBits() {
|
||||
|
||||
UlidSpecCreator creator = new UlidSpecCreator();
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||
|
||||
creator.create();
|
||||
long random2 = creator.random2;
|
||||
|
||||
UUID uuid = new UUID(0, 0);
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
uuid = creator.create();
|
||||
}
|
||||
|
||||
long expected2 = random2 + DEFAULT_LOOP_MAX;
|
||||
long rand2 = creator.random2;
|
||||
assertEquals("Wrong low random after loop.", expected2, rand2);
|
||||
|
||||
rand2 = creator.extractRandom2(uuid);
|
||||
assertEquals("Wrong low random after loop.", expected2, rand2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncrementOfRandomMostSignificantBits() {
|
||||
|
||||
UlidSpecCreator creator = new UlidSpecCreator();
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||
|
||||
creator.create();
|
||||
long random1 = creator.random1;
|
||||
|
||||
UUID uuid = new UUID(0, 0);
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
uuid = creator.create();
|
||||
}
|
||||
|
||||
long expected1 = random1;
|
||||
long rand1 = creator.random1;
|
||||
assertEquals("Wrong high random after loop.", expected1, rand1);
|
||||
|
||||
rand1 = creator.extractRandom1(uuid);
|
||||
assertEquals("Wrong high random after loop.", expected1, rand1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncrementRandomComponentMaximum1() {
|
||||
|
||||
long random1 = 0x000000ffffffffffL;
|
||||
long random2 = 0x000000ffffffffffL;
|
||||
|
||||
long max1 = random1 | UlidSpecCreatorMock.INCREMENT_MAX;
|
||||
long max2 = random2 | UlidSpecCreatorMock.INCREMENT_MAX;
|
||||
|
||||
random1 = max1;
|
||||
random2 = max2 - DEFAULT_LOOP_MAX;
|
||||
|
||||
random2--; // Adjust
|
||||
|
||||
UlidSpecCreatorMock creator = new UlidSpecCreatorMock(random1, random2, max1, max2, TIMESTAMP);
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||
|
||||
UUID uuid = new UUID(0, 0);
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
uuid = creator.create();
|
||||
}
|
||||
|
||||
long hi1 = random1 & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT;
|
||||
long lo1 = random2 & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT;
|
||||
String concat1 = (Long.toHexString(hi1) + Long.toHexString(lo1));
|
||||
BigInteger bigint1 = new BigInteger(concat1, 16);
|
||||
long hi2 = creator.extractRandom1(uuid);
|
||||
long lo2 = creator.extractRandom2(uuid);
|
||||
String concat2 = (Long.toHexString(hi2) + Long.toHexString(lo2));
|
||||
BigInteger bigint2 = new BigInteger(concat2, 16);
|
||||
assertEquals(bigint1.add(BigInteger.valueOf(DEFAULT_LOOP_MAX)), bigint2);
|
||||
|
||||
// This line resets the random component
|
||||
uuid = creator.create();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncrementRandomComponentMaximum2() {
|
||||
|
||||
long random1 = (RANDOM.nextLong() & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT);
|
||||
long random2 = (RANDOM.nextLong() & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT);
|
||||
|
||||
long max1 = random1 | UlidSpecCreatorMock.INCREMENT_MAX;
|
||||
long max2 = random2 | UlidSpecCreatorMock.INCREMENT_MAX;
|
||||
|
||||
random1 = max1;
|
||||
random2 = max2 - DEFAULT_LOOP_MAX;
|
||||
|
||||
random2--; // Adjust
|
||||
|
||||
UlidSpecCreatorMock creator = new UlidSpecCreatorMock(random1, random2, max1, max2, TIMESTAMP);
|
||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||
|
||||
UUID uuid = new UUID(0, 0);
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
uuid = creator.create();
|
||||
}
|
||||
|
||||
long rand1 = creator.extractRandom1(uuid);
|
||||
long expected1 = (max1 & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT);
|
||||
assertEquals("Incorrect high random after loop.", expected1, rand1);
|
||||
|
||||
long rand2 = creator.extractRandom2(uuid);
|
||||
long expected2 = (max2 & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT) - 1;
|
||||
assertEquals("Incorrect low random after loop.", expected2, rand2);
|
||||
|
||||
long hi1 = random1 & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT;
|
||||
long lo1 = random2 & UlidSpecCreatorMock.HALF_RANDOM_COMPONENT;
|
||||
String concat1 = (Long.toHexString(hi1) + Long.toHexString(lo1));
|
||||
BigInteger bigint1 = new BigInteger(concat1, 16);
|
||||
long hi2 = creator.extractRandom1(uuid);
|
||||
long lo2 = creator.extractRandom2(uuid);
|
||||
String concat2 = (Long.toHexString(hi2) + Long.toHexString(lo2));
|
||||
BigInteger bigint2 = new BigInteger(concat2, 16);
|
||||
assertEquals(bigint1.add(BigInteger.valueOf(DEFAULT_LOOP_MAX)), bigint2);
|
||||
|
||||
// This line resets the random component
|
||||
creator.create();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUlidParallelGeneratorsShouldCreateUniqueUlids() throws InterruptedException {
|
||||
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.getUlidSpecCreator(), DEFAULT_LOOP_MAX);
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -242,9 +107,10 @@ public class UlidSpecCreatorTest {
|
|||
|
||||
@Override
|
||||
public void run() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
for (int i = 0; i < loopLimit; i++) {
|
||||
synchronized (hashSet) {
|
||||
hashSet.add(creator.create());
|
||||
hashSet.add(creator.create(timestamp).toUuid());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,20 +10,20 @@ public class DemoTest {
|
|||
int max = 100;
|
||||
|
||||
System.out.println(HORIZONTAL_LINE);
|
||||
System.out.println("### ULID string");
|
||||
System.out.println(HORIZONTAL_LINE);
|
||||
|
||||
for (int i = 0; i < max; i++) {
|
||||
System.out.println(UlidCreator.getUlidString());
|
||||
}
|
||||
|
||||
System.out.println(HORIZONTAL_LINE);
|
||||
System.out.println("### ULID-based GUID");
|
||||
System.out.println("### ULID");
|
||||
System.out.println(HORIZONTAL_LINE);
|
||||
|
||||
for (int i = 0; i < max; i++) {
|
||||
System.out.println(UlidCreator.getUlid());
|
||||
}
|
||||
|
||||
System.out.println(HORIZONTAL_LINE);
|
||||
System.out.println("### Monotonic ULID");
|
||||
System.out.println(HORIZONTAL_LINE);
|
||||
|
||||
for (int i = 0; i < max; i++) {
|
||||
System.out.println(UlidCreator.getMonotonicUlid());
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
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).getTimestamp();
|
||||
assertTrue("Creation time was before start time " + creationTime + " " + startTime,
|
||||
creationTime >= startTime);
|
||||
assertTrue("Creation time was after end time", creationTime <= endTime);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
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.getTimestamp();
|
||||
assertTrue("Creation time was before start time " + creationTime + " " + startTime,
|
||||
creationTime >= startTime);
|
||||
assertTrue("Creation time was after end time", creationTime <= endTime);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,17 +2,15 @@ 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.UlidConverter;
|
||||
import com.github.f4b6a3.ulid.util.UlidUtil;
|
||||
import com.github.f4b6a3.ulid.util.UlidValidator;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.UUID;
|
||||
|
||||
public class UlidCreatorStringTest {
|
||||
public class UlidCreatorMonotonicStringTest {
|
||||
|
||||
private static final int ULID_LENGTH = 26;
|
||||
private static final int DEFAULT_LOOP_MAX = 100_000;
|
||||
|
@ -24,7 +22,7 @@ public class UlidCreatorStringTest {
|
|||
long startTime = System.currentTimeMillis();
|
||||
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
list[i] = UlidCreator.getUlidString();
|
||||
list[i] = UlidCreator.getMonotonicUlid().toString();
|
||||
}
|
||||
|
||||
long endTime = System.currentTimeMillis();
|
||||
|
@ -34,25 +32,6 @@ public class UlidCreatorStringTest {
|
|||
checkOrdering(list);
|
||||
checkCreationTime(list, startTime, endTime);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUlid4() {
|
||||
String[] list = new String[DEFAULT_LOOP_MAX];
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
list[i] = UlidCreator.getUlidString4();
|
||||
}
|
||||
|
||||
long endTime = System.currentTimeMillis();
|
||||
|
||||
checkNullOrInvalid(list);
|
||||
checkUniqueness(list);
|
||||
checkOrdering(list);
|
||||
checkCreationTime(list, startTime, endTime);
|
||||
checkVersion4(list);
|
||||
}
|
||||
|
||||
private void checkNullOrInvalid(String[] list) {
|
||||
for (String ulid : list) {
|
||||
|
@ -79,7 +58,7 @@ public class UlidCreatorStringTest {
|
|||
assertTrue("Start time was after end time", startTime <= endTime);
|
||||
|
||||
for (String ulid : list) {
|
||||
long creationTime = UlidUtil.extractUnixMilliseconds(ulid);
|
||||
long creationTime = Ulid.of(ulid).getTimestamp();
|
||||
assertTrue("Creation time was before start time " + creationTime + " " + startTime,
|
||||
creationTime >= startTime);
|
||||
assertTrue("Creation time was after end time", creationTime <= endTime);
|
||||
|
@ -94,12 +73,4 @@ public class UlidCreatorStringTest {
|
|||
assertEquals("The ULID list is not ordered", list[i], other[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkVersion4(String[] list) {
|
||||
for (String ulid : list) {
|
||||
UUID uuid = UlidConverter.fromString(ulid);
|
||||
assertEquals(String.format("ULID is is not RFC-4122 version 4 %s", uuid), 4, uuid.version());
|
||||
assertEquals(String.format("ULID is is not RFC-4122 variant 2 %s", uuid), 2, uuid.variant());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
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.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
public class UlidCreatorMonotonicTest {
|
||||
|
||||
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.getMonotonicUlid();
|
||||
}
|
||||
|
||||
long endTime = System.currentTimeMillis();
|
||||
|
||||
checkNullOrInvalid(list);
|
||||
checkUniqueness(list);
|
||||
checkOrdering(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.getTimestamp();
|
||||
assertTrue("Creation time was before start time " + creationTime + " " + startTime,
|
||||
creationTime >= startTime);
|
||||
assertTrue("Creation time was after end time", creationTime <= endTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkOrdering(Ulid[] list) {
|
||||
Ulid[] 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,98 +0,0 @@
|
|||
package com.github.f4b6a3.ulid.ulid;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.github.f4b6a3.ulid.UlidCreator;
|
||||
import com.github.f4b6a3.ulid.util.UlidUtil;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.UUID;
|
||||
|
||||
public class UlidCreatorUuidTest {
|
||||
|
||||
private static final int DEFAULT_LOOP_MAX = 100_000;
|
||||
|
||||
@Test
|
||||
public void testGetUlid() {
|
||||
UUID[] list = new UUID[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);
|
||||
checkOrdering(list);
|
||||
checkCreationTime(list, startTime, endTime);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUlid4() {
|
||||
UUID[] list = new UUID[DEFAULT_LOOP_MAX];
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
list[i] = UlidCreator.getUlid4();
|
||||
}
|
||||
|
||||
long endTime = System.currentTimeMillis();
|
||||
|
||||
checkNullOrInvalid(list);
|
||||
checkUniqueness(list);
|
||||
checkOrdering(list);
|
||||
checkCreationTime(list, startTime, endTime);
|
||||
checkVersion4(list);
|
||||
}
|
||||
|
||||
private void checkNullOrInvalid(UUID[] list) {
|
||||
for (UUID ulid : list) {
|
||||
assertNotNull("ULID is null", ulid);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkUniqueness(UUID[] list) {
|
||||
|
||||
HashSet<UUID> set = new HashSet<>();
|
||||
|
||||
for (UUID 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(UUID[] list, long startTime, long endTime) {
|
||||
|
||||
assertTrue("Start time was after end time", startTime <= endTime);
|
||||
|
||||
for (UUID ulid : list) {
|
||||
long creationTime = UlidUtil.extractUnixMilliseconds(ulid);
|
||||
assertTrue("Creation time was before start time " + creationTime + " " + startTime,
|
||||
creationTime >= startTime);
|
||||
assertTrue("Creation time was after end time", creationTime <= endTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkOrdering(UUID[] list) {
|
||||
UUID[] 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]);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkVersion4(UUID[] list) {
|
||||
for (UUID uuid : list) {
|
||||
assertEquals(String.format("ULID is is not RFC-4122 version 4 %s", uuid), 4, uuid.version());
|
||||
assertEquals(String.format("ULID is is not RFC-4122 variant 2 %s", uuid), 2, uuid.variant());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
package com.github.f4b6a3.ulid.util;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
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 {
|
||||
|
||||
private static final int ULID_LENGTH = 26;
|
||||
private static final int DEFAULT_LOOP_MAX = 100_000;
|
||||
|
||||
@Test
|
||||
public void testToAndFromUlid() {
|
||||
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
|
||||
UUID uuid1 = UlidCreator.getUlid();
|
||||
String ulid = UlidConverter.toString(uuid1);
|
||||
|
||||
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));
|
||||
|
||||
UUID uuid2 = UlidConverter.fromString(ulid);
|
||||
assertEquals("Result ULID is different from original ULID", uuid1, uuid2);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@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 = UlidStruct.of(time, random1, random2);
|
||||
|
||||
String string1 = struct0.toString();
|
||||
UlidStruct struct1 = UlidStruct.of(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 = UlidStruct.of(ulid0);
|
||||
|
||||
String string1 = UlidConverter.toString(ulid0);
|
||||
UlidStruct struct1 = UlidStruct.of(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 = UlidStruct.of(ulid0);
|
||||
|
||||
String string1 = struct0.toString();
|
||||
UlidStruct struct1 = UlidStruct.of(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 = UlidStruct.of(ulid0);
|
||||
|
||||
String string1 = UlidConverter.toString(ulid0);
|
||||
UlidStruct struct1 = UlidStruct.of(string1);
|
||||
|
||||
String string2 = struct0.toString();
|
||||
UlidStruct struct2 = UlidStruct.of(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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
package com.github.f4b6a3.ulid.util;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import java.time.Instant;
|
||||
import java.util.Random;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.github.f4b6a3.ulid.exception.InvalidUlidException;
|
||||
import com.github.f4b6a3.ulid.util.internal.UlidStructTest;
|
||||
|
||||
import static com.github.f4b6a3.ulid.util.UlidUtil.*;
|
||||
|
||||
public class UlidUtilTest {
|
||||
|
||||
// Date: 10889-08-02T05:31:50.655Z: 281474976710655 (2^48-1)
|
||||
private static final long TIMESTAMP_MAX = 0xffffffffffffL;
|
||||
private static final long HALF_RANDOM_MAX = 0xffffffffffL;
|
||||
|
||||
private static final int DEFAULT_LOOP_MAX = 100_000;
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
@Test
|
||||
public void testExtractTimestamp1() {
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
|
||||
long time = RANDOM.nextLong() & TIMESTAMP_MAX;
|
||||
long random1 = RANDOM.nextLong() & HALF_RANDOM_MAX;
|
||||
long random2 = RANDOM.nextLong() & HALF_RANDOM_MAX;
|
||||
|
||||
String timeComponent = UlidStructTest.toTimeComponent(time);
|
||||
String randomComponent = UlidStructTest.toRandomComponent(random1, random2);
|
||||
|
||||
String ulid = timeComponent + randomComponent;
|
||||
long result = extractTimestamp(ulid);
|
||||
assertEquals(time, result);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractTimestamp2() {
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
|
||||
String ulid;
|
||||
String timeComponent;
|
||||
String randomComponent;
|
||||
|
||||
long time;
|
||||
long random1;
|
||||
long random2;
|
||||
|
||||
time = RANDOM.nextLong() & TIMESTAMP_MAX;
|
||||
random1 = RANDOM.nextLong() & HALF_RANDOM_MAX;
|
||||
random2 = RANDOM.nextLong() & HALF_RANDOM_MAX;
|
||||
|
||||
timeComponent = UlidStructTest.toTimeComponent(time);
|
||||
randomComponent = UlidStructTest.toRandomComponent(random1, random2);
|
||||
|
||||
timeComponent = "7ZZZZZZZZZ";
|
||||
ulid = timeComponent + randomComponent;
|
||||
time = extractTimestamp(ulid);
|
||||
assertEquals(TIMESTAMP_MAX, time);
|
||||
|
||||
timeComponent = "0000000000";
|
||||
ulid = timeComponent + randomComponent;
|
||||
time = extractTimestamp(ulid);
|
||||
assertEquals(0, time);
|
||||
|
||||
try {
|
||||
// Test the first extra bit added by the base32 encoding
|
||||
char[] chars = timeComponent.toCharArray();
|
||||
chars[0] = 'G'; // GZZZZZZZZZ
|
||||
timeComponent = new String(chars);
|
||||
ulid = timeComponent + randomComponent;
|
||||
extractTimestamp(ulid);
|
||||
fail("Should throw an InvalidUlidException");
|
||||
} catch (InvalidUlidException e) {
|
||||
// success
|
||||
}
|
||||
|
||||
try {
|
||||
// Test the second extra bit added by the base32 encoding
|
||||
char[] chars = timeComponent.toCharArray();
|
||||
chars[0] = '8'; // 8ZZZZZZZZZ
|
||||
timeComponent = new String(chars);
|
||||
ulid = timeComponent + randomComponent;
|
||||
extractTimestamp(ulid);
|
||||
fail("Should throw an InvalidUlidException");
|
||||
} catch (InvalidUlidException e) {
|
||||
// success
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractUnixMilliseconds() {
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
long time = RANDOM.nextLong() & TIMESTAMP_MAX;
|
||||
long random1 = RANDOM.nextLong() & HALF_RANDOM_MAX;
|
||||
long random2 = RANDOM.nextLong() & HALF_RANDOM_MAX;
|
||||
String ulid = UlidStructTest.toString(time, random1, random2);
|
||||
long result = extractUnixMilliseconds(ulid);
|
||||
assertEquals(time, result);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractInstant() {
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
long time = RANDOM.nextLong() & TIMESTAMP_MAX;
|
||||
Instant instant = Instant.ofEpochMilli(time);
|
||||
long random1 = RANDOM.nextLong() & HALF_RANDOM_MAX;
|
||||
long random2 = RANDOM.nextLong() & HALF_RANDOM_MAX;
|
||||
String ulid = UlidStructTest.toString(time, random1, random2);
|
||||
Instant result = extractInstant(ulid);
|
||||
assertEquals(instant, result);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractTimestampComponent() {
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
long time = RANDOM.nextLong() & TIMESTAMP_MAX;
|
||||
long random1 = RANDOM.nextLong() & HALF_RANDOM_MAX;
|
||||
long random2 = RANDOM.nextLong() & HALF_RANDOM_MAX;
|
||||
String ulid = UlidStructTest.toString(time, random1, random2);
|
||||
|
||||
char[] chars = ulid.toCharArray();
|
||||
char[] timeComponent = new char[10];
|
||||
System.arraycopy(chars, 0, timeComponent, 0, 10);
|
||||
String expected = new String(timeComponent);
|
||||
|
||||
String result = extractTimestampComponent(ulid);
|
||||
assertEquals(expected, result);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractRandomnessComponent() {
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
long time = RANDOM.nextLong() & TIMESTAMP_MAX;
|
||||
long random1 = RANDOM.nextLong() & HALF_RANDOM_MAX;
|
||||
long random2 = RANDOM.nextLong() & HALF_RANDOM_MAX;
|
||||
String ulid = UlidStructTest.toString(time, random1, random2);
|
||||
|
||||
char[] chars = ulid.toCharArray();
|
||||
char[] randomComponent = new char[16];
|
||||
System.arraycopy(chars, 10, randomComponent, 0, 16);
|
||||
String expected = new String(randomComponent);
|
||||
|
||||
String result = extractRandomnessComponent(ulid);
|
||||
assertEquals(expected, result);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,7 +7,9 @@ import java.util.UUID;
|
|||
|
||||
import org.junit.Test;
|
||||
|
||||
public class UlidStructTest {
|
||||
import com.github.f4b6a3.ulid.Ulid;
|
||||
|
||||
public class UlidTest {
|
||||
|
||||
private static final int DEFAULT_LOOP_MAX = 100_000;
|
||||
|
||||
|
@ -19,43 +21,17 @@ public class UlidStructTest {
|
|||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
UUID uuid0 = UUID.randomUUID();
|
||||
String string0 = toString(uuid0);
|
||||
String string1 = UlidStruct.of(string0).toString();
|
||||
String string1 = Ulid.of(string0).toString();
|
||||
assertEquals(string0, string1);
|
||||
}
|
||||
|
||||
// Test RFC-4122 UUID version 4
|
||||
final long versionMask = 0xffffffffffff0fffL;
|
||||
final long variantMask = 0x3fffffffffffffffL;
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
UUID uuid0 = UUID.randomUUID();
|
||||
String string0 = toString(uuid0);
|
||||
String string1 = UlidStruct.of(string0).toString4(); // UUID v4 in base32
|
||||
UUID uuid1 = toUuid(fromString(string1));
|
||||
assertEquals(uuid0.getMostSignificantBits() & versionMask, uuid1.getMostSignificantBits() & versionMask);
|
||||
assertEquals(uuid0.getLeastSignificantBits() & variantMask, uuid1.getLeastSignificantBits() & variantMask);
|
||||
assertEquals(4, uuid1.version());
|
||||
assertEquals(2, uuid1.variant());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOfAndToUuid() {
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
UUID uuid0 = UUID.randomUUID();
|
||||
UUID uuid1 = UlidStruct.of(uuid0).toUuid();
|
||||
assertEquals(uuid0, uuid1);
|
||||
}
|
||||
|
||||
// Test RFC-4122 UUID version 4
|
||||
final long versionMask = 0xffffffffffff0fffL;
|
||||
final long variantMask = 0x3fffffffffffffffL;
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
UUID uuid0 = UUID.randomUUID();
|
||||
UUID uuid1 = UlidStruct.of(uuid0).toUuid4(); // UUID v4
|
||||
assertEquals(uuid0.getMostSignificantBits() & versionMask, uuid1.getMostSignificantBits() & versionMask);
|
||||
assertEquals(uuid0.getLeastSignificantBits() & variantMask, uuid1.getLeastSignificantBits() & variantMask);
|
||||
assertEquals(4, uuid1.version());
|
||||
assertEquals(2, uuid1.variant());
|
||||
UUID uuid1 = Ulid.of(uuid0).toUuid();
|
||||
assertEquals(uuid0.toString(), uuid1.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,14 +39,12 @@ public class UlidStructTest {
|
|||
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 = UlidStruct.of(time, random1, random2); // <-- under test
|
||||
final long msb = random.nextLong();
|
||||
final long lsb = random.nextLong();
|
||||
Ulid ulid0 = Ulid.of(msb, lsb); // <-- under test
|
||||
|
||||
assertEquals(time & 0xffffffffffffL, struct0.time);
|
||||
assertEquals(random1 & 0xffffffffffL, struct0.random1);
|
||||
assertEquals(random2 & 0xffffffffffL, struct0.random2);
|
||||
assertEquals(msb, ulid0.toUuid().getMostSignificantBits());
|
||||
assertEquals(lsb, ulid0.toUuid().getLeastSignificantBits());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,14 +52,13 @@ public class UlidStructTest {
|
|||
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 = UlidStruct.of(time, random1, random2);
|
||||
Ulid ulid0 = Ulid.of(random1, random2);
|
||||
|
||||
String string1 = toString(struct0);
|
||||
UlidStruct struct1 = UlidStruct.of(string1); // <-- under test
|
||||
assertEquals(struct0, struct1);
|
||||
String string1 = toString(ulid0);
|
||||
Ulid struct1 = Ulid.of(string1); // <-- under test
|
||||
assertEquals(ulid0, struct1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,7 +69,7 @@ public class UlidStructTest {
|
|||
final long msb = random.nextLong();
|
||||
final long lsb = random.nextLong();
|
||||
final UUID uuid0 = new UUID(msb, lsb);
|
||||
UlidStruct struct0 = UlidStruct.of(uuid0); // <-- under test
|
||||
Ulid struct0 = Ulid.of(uuid0); // <-- under test
|
||||
|
||||
UUID uuid1 = toUuid(struct0);
|
||||
assertEquals(uuid0, uuid1);
|
||||
|
@ -108,13 +81,12 @@ public class UlidStructTest {
|
|||
|
||||
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 = UlidStruct.of(time, random1, random2);
|
||||
Ulid ulid0 = Ulid.of(random1, random2);
|
||||
|
||||
String string1 = toString(struct0);
|
||||
String string2 = struct0.toString(); // <-- under test
|
||||
String string1 = toString(ulid0);
|
||||
String string2 = ulid0.toString(); // <-- under test
|
||||
assertEquals(string1, string2);
|
||||
}
|
||||
}
|
||||
|
@ -124,18 +96,17 @@ public class UlidStructTest {
|
|||
|
||||
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 = UlidStruct.of(time, random1, random2);
|
||||
Ulid ulid0 = Ulid.of(random1, random2);
|
||||
|
||||
UUID uuid1 = toUuid(struct0);
|
||||
UUID uuid2 = struct0.toUuid(); // <-- under test
|
||||
UUID uuid1 = toUuid(ulid0);
|
||||
UUID uuid2 = ulid0.toUuid(); // <-- under test
|
||||
assertEquals(uuid1, uuid2);
|
||||
}
|
||||
}
|
||||
|
||||
public static UlidStruct fromString(String string) {
|
||||
public static Ulid fromString(String string) {
|
||||
|
||||
long time = 0;
|
||||
long random1 = 0;
|
||||
|
@ -153,17 +124,16 @@ public class UlidStructTest {
|
|||
random1 = Long.parseUnsignedLong(r1, 32);
|
||||
random2 = Long.parseUnsignedLong(r2, 32);
|
||||
|
||||
return UlidStruct.of(time, random1, random2);
|
||||
long msb = (time << 16) | (random1 >>> 24);
|
||||
long lsb = (random1 << 40) | (random2 & 0xffffffffffL);
|
||||
|
||||
return Ulid.of(msb, lsb);
|
||||
}
|
||||
|
||||
public static UUID toUuid(UlidStruct struct) {
|
||||
public static UUID toUuid(Ulid 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;
|
||||
long msb = struct.toUuid().getMostSignificantBits();
|
||||
long lsb = struct.toUuid().getLeastSignificantBits();
|
||||
|
||||
return new UUID(msb, lsb);
|
||||
}
|
||||
|
@ -180,38 +150,36 @@ public class UlidStructTest {
|
|||
return new UUID(msb, lsb);
|
||||
}
|
||||
|
||||
public static String toString(UlidStruct struct) {
|
||||
return toString(struct.time, struct.random1, struct.random2);
|
||||
public static String toString(Ulid ulid) {
|
||||
return toString(ulid.toUuid().getMostSignificantBits(), ulid.toUuid().getLeastSignificantBits());
|
||||
}
|
||||
|
||||
public static String toString(UUID uuid) {
|
||||
final long msb = uuid.getMostSignificantBits();
|
||||
final long lsb = uuid.getLeastSignificantBits();
|
||||
|
||||
final long time = (msb >>> 16);
|
||||
final long random1 = ((msb & 0xffffL) << 24) | (lsb >>> 40);
|
||||
final long random2 = (lsb & 0xffffffffffL);
|
||||
|
||||
return toString(time, random1, random2);
|
||||
return toString(msb, lsb);
|
||||
}
|
||||
|
||||
public static String toString(final long time, final long random1, final long random2) {
|
||||
String timeComponent = toTimeComponent(time);
|
||||
String randomComponent = toRandomComponent(random1, random2);
|
||||
public static String toString(final long msb, final long lsb) {
|
||||
String timeComponent = toTimeComponent(msb >>> 16);
|
||||
String randomComponent = toRandomComponent(msb, lsb);
|
||||
return timeComponent + randomComponent;
|
||||
}
|
||||
|
||||
|
||||
public static String toTimeComponent(final long time) {
|
||||
final String tzero = "0000000000";
|
||||
String tm = Long.toUnsignedString(time, 32);
|
||||
tm = tzero.substring(0, tzero.length() - tm.length()) + tm;
|
||||
return transliterate(tm, ALPHABET_JAVA, ALPHABET_CROCKFORD);
|
||||
}
|
||||
|
||||
public static String toRandomComponent(final long random1, final long random2) {
|
||||
|
||||
public static String toRandomComponent(final long msb, final long lsb) {
|
||||
|
||||
final String zeros = "00000000";
|
||||
|
||||
final long random1 = ((msb & 0xffffL) << 24) | (lsb >>> 40);
|
||||
final long random2 = (lsb & 0xffffffffffL);
|
||||
|
||||
String r1 = Long.toUnsignedString(random1, 32);
|
||||
String r2 = Long.toUnsignedString(random2, 32);
|
||||
|
Loading…
Reference in New Issue