Preparing v1.1.0
This commit is contained in:
parent
1826fd960e
commit
c59d1c61f0
59
README.md
59
README.md
|
@ -1,23 +1,24 @@
|
|||
|
||||
# ULID Creator
|
||||
|
||||
A Java library for generating and handling ULIDs - _Universally Unique Lexicographically Sortable Identifiers_.
|
||||
A Java library for generating ULIDs.
|
||||
|
||||
How to Use
|
||||
------------------------------------------------------
|
||||
|
||||
Create a ULID:
|
||||
Create a ULID as GUID:
|
||||
|
||||
```java
|
||||
String ulid = UlidCreator.getUlid();
|
||||
UUID ulid = UlidCreator.getUlid();
|
||||
```
|
||||
|
||||
Create a ULID as GUID object:
|
||||
Create a ULID string:
|
||||
|
||||
```java
|
||||
UUID ulid = UlidCreator.getGuid();
|
||||
String ulid = UlidCreator.getUlidString();
|
||||
```
|
||||
|
||||
|
||||
### Maven dependency
|
||||
|
||||
Add these lines to your `pom.xml`.
|
||||
|
@ -27,7 +28,7 @@ Add these lines to your `pom.xml`.
|
|||
<dependency>
|
||||
<groupId>com.github.f4b6a3</groupId>
|
||||
<artifactId>ulid-creator</artifactId>
|
||||
<version>1.0.2</version>
|
||||
<version>1.1.0</version>
|
||||
</dependency>
|
||||
```
|
||||
See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator) and [mvnrepository.com](https://mvnrepository.com/artifact/com.github.f4b6a3/ulid-creator).
|
||||
|
@ -35,15 +36,15 @@ See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b
|
|||
Implementation
|
||||
------------------------------------------------------
|
||||
|
||||
### ULID
|
||||
### ULID string
|
||||
|
||||
The ULID is a unique and sortable 26 char sequence. See the [ULID specification](https://github.com/ulid/spec) for more information.
|
||||
The ULID is a 26 char sequence. See the [ULID specification](https://github.com/ulid/spec) for more information.
|
||||
|
||||
See the section on GUIDs to know how the 128 bits are generated in this library.
|
||||
|
||||
```java
|
||||
// ULIDs
|
||||
String ulid = UlidCreator.getUlid();
|
||||
String ulid = UlidCreator.getUlidString();
|
||||
```
|
||||
|
||||
Examples of ULIDs:
|
||||
|
@ -71,17 +72,17 @@ Examples of ULIDs:
|
|||
milli randomness
|
||||
```
|
||||
|
||||
### GUID
|
||||
### Ulid-based GUID
|
||||
|
||||
The GUIDs in this library are based on the [ULID specification](https://github.com/ulid/spec). The first 48 bits represent the count of milliseconds since Unix Epoch, 1 January 1970. The remaining 60 bits are generated by a secure random number generator.
|
||||
|
||||
Every time the timestamp changes the random part is reset to a new random value. If the current timestamp is equal to the previous one, the random bits are incremented by 1.
|
||||
|
||||
The default random number generator is `SecureRandom`, but it's possible to use any RNG that extends `Random`.
|
||||
The default random number generator is `java.security.SecureRandom`, but it's possible to use any RNG that extends `java.util.Random`.
|
||||
|
||||
```java
|
||||
// GUID based on ULID spec
|
||||
UUID guid = UlidCreator.getGuid();
|
||||
UUID ulid = UlidCreator.getUlid();
|
||||
```
|
||||
|
||||
Examples of GUIDs based on ULID spec:
|
||||
|
@ -109,38 +110,28 @@ Examples of GUIDs based on ULID spec:
|
|||
millisecs randomness
|
||||
```
|
||||
|
||||
#### How use the `GuidCreator` directly
|
||||
#### How use the `UlidBasedGuidCreator` directly
|
||||
|
||||
These are some examples of using the `GuidCreator` to create ULIDs:
|
||||
These are some examples of using the `UlidBasedGuidCreator` to create ULIDs strings:
|
||||
|
||||
```java
|
||||
|
||||
// with fixed timestamp strategy (for test cases)
|
||||
String ulid = UlidCreator.getGuidCreator()
|
||||
.withTimestampStrategy(new FixedTimestampStretegy())
|
||||
.createUlid();
|
||||
|
||||
// with your custom timestamp strategy
|
||||
String ulid = UlidCreator.getGuidCreator()
|
||||
.withTimestampStrategy(new MyCustomTimestampStrategy())
|
||||
.createUlid();
|
||||
TimestampStrategy customStrategy = new CustomTimestampStrategy();
|
||||
String ulid = UlidCreator.getUlidBasedGuidCreator()
|
||||
.withTimestampStrategy(customStrategy)
|
||||
.createString();
|
||||
|
||||
// with your custom random number generator
|
||||
String ulid = UlidCreator.getGuidCreator()
|
||||
.withRandomGenerator(new MyCustomRandom())
|
||||
.createUlid();
|
||||
|
||||
// with fast random generator (Xorshift128Plus with salt)
|
||||
int salt = (int) FingerprintUtil.getFingerprint();
|
||||
Random random = new Xorshift128PlusRandom(salt);
|
||||
String ulid = UlidCreator.getGuidCreator()
|
||||
// with `java.util.Random` number generator
|
||||
Random random = new Random();
|
||||
String ulid = UlidCreator.getUlidBasedGuidCreator()
|
||||
.withRandomGenerator(random)
|
||||
.createUlid();
|
||||
.createString();
|
||||
|
||||
// with fast random generator (the same as above)
|
||||
String ulid = UlidCreator.getGuidCreator()
|
||||
String ulid = UlidCreator.getUlidBasedGuidCreator()
|
||||
.withFastRandomGenerator()
|
||||
.createUlid();
|
||||
.createString();
|
||||
|
||||
```
|
||||
|
||||
|
|
|
@ -26,8 +26,8 @@ package com.github.f4b6a3.ulid;
|
|||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.github.f4b6a3.commons.random.Xorshift128PlusRandom;
|
||||
import com.github.f4b6a3.ulid.creator.UlidBasedGuidCreator;
|
||||
import com.github.f4b6a3.ulid.exception.UlidCreatorException;
|
||||
|
||||
/**
|
||||
* A factory for Universally Unique Lexicographically Sortable Identifiers.
|
||||
|
@ -40,61 +40,71 @@ public class UlidCreator {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns ULID as GUID object.
|
||||
* Returns a ULID as GUID.
|
||||
*
|
||||
* @return a GUID
|
||||
* The random component is generated by a secure random number generator:
|
||||
* {@link java.security.SecureRandom}.
|
||||
*
|
||||
* @return a UUID
|
||||
*/
|
||||
public static UUID getUlid() {
|
||||
return GuidCreatorLazyHolder.INSTANCE.create();
|
||||
return UlidBasedGuidCreatorHolder.INSTANCE.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns fast ULID as GUID object.
|
||||
* Returns a ULID string.
|
||||
*
|
||||
* @return a GUID
|
||||
*/
|
||||
public static UUID getFastUlid() {
|
||||
return FastGuidCreatorLazyHolder.INSTANCE.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ULID.
|
||||
* 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
|
||||
*/
|
||||
public static String getUlidString() {
|
||||
return GuidCreatorLazyHolder.INSTANCE.createString();
|
||||
return UlidBasedGuidCreatorHolder.INSTANCE.createString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a fast ULID.
|
||||
* Returns a ULID as GUID.
|
||||
*
|
||||
* The random component is generated by a fast random number generator:
|
||||
* {@link Xorshift128PlusRandom}.
|
||||
*
|
||||
* @return a UUID
|
||||
*/
|
||||
public static UUID getFastUlid() {
|
||||
return FastUlidBasedGuidCreatorHolder.INSTANCE.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a fast ULID string.
|
||||
*
|
||||
* The returning string is encoded to Crockford's base32.
|
||||
*
|
||||
* The random component is generated by a fast random number generator:
|
||||
* {@link Xorshift128PlusRandom}.
|
||||
*
|
||||
* @return a ULID
|
||||
*/
|
||||
public static String getFastUlidString() {
|
||||
return FastGuidCreatorLazyHolder.INSTANCE.createString();
|
||||
return FastUlidBasedGuidCreatorHolder.INSTANCE.createString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a GUID creator for direct use.
|
||||
*
|
||||
* This library uses the {@link UlidBasedGuidCreator} internally to generate
|
||||
* ULIDs.
|
||||
*
|
||||
* The {@link UlidBasedGuidCreator} throws a {@link UlidCreatorException} when
|
||||
* too many values are requested in the same millisecond.
|
||||
*
|
||||
* @return a {@link UlidBasedGuidCreator}
|
||||
*/
|
||||
public static UlidBasedGuidCreator getUlidBasedCreator() {
|
||||
return new UlidBasedGuidCreator();
|
||||
}
|
||||
|
||||
private static class GuidCreatorLazyHolder {
|
||||
private static class UlidBasedGuidCreatorHolder {
|
||||
static final UlidBasedGuidCreator INSTANCE = getUlidBasedCreator();
|
||||
}
|
||||
|
||||
private static class FastGuidCreatorLazyHolder {
|
||||
private static class FastUlidBasedGuidCreatorHolder {
|
||||
static final UlidBasedGuidCreator INSTANCE = getUlidBasedCreator().withFastRandomGenerator();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,13 +27,13 @@ package com.github.f4b6a3.ulid.creator;
|
|||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.github.f4b6a3.ulid.timestamp.TimestampStrategy;
|
||||
import com.github.f4b6a3.ulid.util.UlidUtil;
|
||||
import com.github.f4b6a3.ulid.util.UlidConverter;
|
||||
import com.github.f4b6a3.commons.random.Xorshift128PlusRandom;
|
||||
import com.github.f4b6a3.commons.util.FingerprintUtil;
|
||||
import com.github.f4b6a3.commons.util.RandomUtil;
|
||||
import com.github.f4b6a3.ulid.exception.UlidCreatorException;
|
||||
import com.github.f4b6a3.ulid.timestamp.DefaultTimestampStrategy;
|
||||
import com.github.f4b6a3.ulid.strategy.TimestampStrategy;
|
||||
import com.github.f4b6a3.ulid.strategy.timestamp.DefaultTimestampStrategy;
|
||||
|
||||
/**
|
||||
* Factory that creates lexicographically sortable GUIDs, based on the ULID
|
||||
|
@ -69,7 +69,7 @@ public class UlidBasedGuidCreator {
|
|||
*
|
||||
* Return a GUID based on the ULID specification.
|
||||
*
|
||||
* It has two parts:
|
||||
* A ULID has two parts:
|
||||
*
|
||||
* 1. A part of 48 bits that represent the amount of milliseconds since Unix
|
||||
* Epoch, 1 January 1970.
|
||||
|
@ -85,6 +85,9 @@ public class UlidBasedGuidCreator {
|
|||
*
|
||||
* 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
|
||||
|
@ -116,7 +119,7 @@ public class UlidBasedGuidCreator {
|
|||
* 2^80 ULIDs within the same millisecond, or cause the random component to
|
||||
* overflow with less, the generation will fail.
|
||||
*
|
||||
* @return {@link UUID} a UUID value
|
||||
* @return {@link UUID} a GUID value
|
||||
*
|
||||
* @throws UlidCreatorException an overrun exception if too many requests are
|
||||
* made within the same millisecond.
|
||||
|
@ -135,13 +138,14 @@ public class UlidBasedGuidCreator {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return a ULID.
|
||||
* Returns a ULID string.
|
||||
*
|
||||
* The returning string is encoded to Crockford's base32.
|
||||
*
|
||||
* @return a ULID string
|
||||
*/
|
||||
public synchronized String createString() {
|
||||
UUID guid = create();
|
||||
return UlidUtil.fromUuidToUlid(guid);
|
||||
return UlidConverter.toString(create());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -185,7 +189,8 @@ public class UlidBasedGuidCreator {
|
|||
/**
|
||||
* Increment the random part of the GUID.
|
||||
*
|
||||
* An exception is thrown when more than 2^80 increment operations are made.
|
||||
* An exception is thrown when more than 2^80 increment operations are made,
|
||||
* although it's extremely unlikely to occur.
|
||||
*
|
||||
* @throws UlidCreatorException if an overrun happens.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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.exception;
|
||||
|
||||
public class InvalidUlidException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public InvalidUlidException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -26,7 +26,7 @@ package com.github.f4b6a3.ulid.exception;
|
|||
|
||||
public class UlidCreatorException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 6755381080404981234L;
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public UlidCreatorException(String message) {
|
||||
super(message);
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.github.f4b6a3.ulid.timestamp;
|
||||
package com.github.f4b6a3.ulid.strategy;
|
||||
|
||||
public interface TimestampStrategy {
|
||||
long getTimestamp();
|
|
@ -22,7 +22,9 @@
|
|||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.github.f4b6a3.ulid.timestamp;
|
||||
package com.github.f4b6a3.ulid.strategy.timestamp;
|
||||
|
||||
import com.github.f4b6a3.ulid.strategy.TimestampStrategy;
|
||||
|
||||
public class DefaultTimestampStrategy implements TimestampStrategy {
|
||||
|
|
@ -22,7 +22,9 @@
|
|||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.github.f4b6a3.ulid.timestamp;
|
||||
package com.github.f4b6a3.ulid.strategy.timestamp;
|
||||
|
||||
import com.github.f4b6a3.ulid.strategy.TimestampStrategy;
|
||||
|
||||
public class FixedTimestampStretegy implements TimestampStrategy {
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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.util.UUID;
|
||||
|
||||
import com.github.f4b6a3.commons.util.Base32Util;
|
||||
import com.github.f4b6a3.commons.util.ByteUtil;
|
||||
|
||||
public class UlidConverter {
|
||||
|
||||
private UlidConverter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a UUID to ULID string
|
||||
*
|
||||
* The returning string is encoded to Crockford's base32.
|
||||
*
|
||||
* The timestamp and random components are encoded separated.
|
||||
*
|
||||
* @param uuid a UUID
|
||||
* @return a ULID
|
||||
*/
|
||||
public static String toString(UUID uuid) {
|
||||
|
||||
final long msb = uuid.getMostSignificantBits();
|
||||
final long lsb = uuid.getLeastSignificantBits();
|
||||
|
||||
// Extract timestamp component
|
||||
final long timeNumber = (msb >>> 16);
|
||||
String timestampComponent = leftPad(Base32Util.toBase32Crockford(timeNumber));
|
||||
|
||||
// Extract randomness component
|
||||
byte[] randBytes = new byte[10];
|
||||
randBytes[0] = (byte) (msb >>> 8);
|
||||
randBytes[1] = (byte) (msb);
|
||||
byte[] lsbBytes = ByteUtil.toBytes(lsb);
|
||||
System.arraycopy(lsbBytes, 0, randBytes, 2, 8);
|
||||
String randomnessComponent = Base32Util.toBase32Crockford(randBytes);
|
||||
|
||||
return timestampComponent + randomnessComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a ULID string to a UUID.
|
||||
*
|
||||
* The input string must be encoded to Crockford's base32, following the ULID
|
||||
* specification.
|
||||
*
|
||||
* The timestamp and random components are decoded separated.
|
||||
*
|
||||
* An exception is thrown if the ULID string is invalid.
|
||||
*
|
||||
* @param ulid a ULID
|
||||
* @return a UUID if valid
|
||||
*/
|
||||
public static UUID fromString(final String ulid) {
|
||||
|
||||
UlidValidator.validate(ulid);
|
||||
|
||||
// Extract timestamp component
|
||||
final String timestampComponent = ulid.substring(0, 10);
|
||||
final long timeNumber = Base32Util.fromBase32CrockfordAsLong(timestampComponent);
|
||||
|
||||
// Extract randomness component
|
||||
final String randomnessComponent = ulid.substring(10, 26);
|
||||
byte[] randBytes = Base32Util.fromBase32Crockford(randomnessComponent);
|
||||
byte[] lsbBytes = new byte[8];
|
||||
System.arraycopy(randBytes, 2, lsbBytes, 0, 8);
|
||||
|
||||
final long msb = (timeNumber << 16) | ((randBytes[0] << 8) & 0x0000ff00L) | ((randBytes[1]) & 0x000000ffL);
|
||||
final long lsb = ByteUtil.toNumber(lsbBytes);
|
||||
|
||||
return new UUID(msb, lsb);
|
||||
}
|
||||
|
||||
private static String leftPad(String unpadded) {
|
||||
return "0000000000".substring(unpadded.length()) + unpadded;
|
||||
}
|
||||
}
|
|
@ -25,237 +25,16 @@
|
|||
package com.github.f4b6a3.ulid.util;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.github.f4b6a3.commons.util.Base32Util;
|
||||
import com.github.f4b6a3.commons.util.ByteUtil;
|
||||
|
||||
public class UlidUtil {
|
||||
|
||||
// Date: 10889-08-02T05:31:50.655Z
|
||||
protected static final long TIMESTAMP_MAX = (long) Math.pow(2, 48) - 1;
|
||||
|
||||
protected static final String ULID_PATTERN_STRICT = "^[0-9a-hjkmnp-tv-zA-HJKMNP-TV-Z]{26}$";
|
||||
protected static final String ULID_PATTERN_LOOSE = "^[0-9a-tv-zA-TV-Z]{26}$";
|
||||
|
||||
private UlidUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a UUID to ULID string
|
||||
*
|
||||
* @param uuid
|
||||
* a UUID
|
||||
* @return a ULID
|
||||
*/
|
||||
public static String fromUuidToUlid(UUID uuid) {
|
||||
|
||||
final long msb = uuid.getMostSignificantBits();
|
||||
final long lsb = uuid.getLeastSignificantBits();
|
||||
|
||||
// Extract timestamp component
|
||||
final long timeNumber = (msb >>> 16);
|
||||
String timestampComponent = leftPad(Base32Util.toBase32Crockford(timeNumber));
|
||||
|
||||
// Extract randomness component
|
||||
byte[] randBytes = new byte[10];
|
||||
randBytes[0] = (byte) (msb >>> 8);
|
||||
randBytes[1] = (byte) (msb);
|
||||
byte[] lsbBytes = ByteUtil.toBytes(lsb);
|
||||
System.arraycopy(lsbBytes, 0, randBytes, 2, 8);
|
||||
String randomnessComponent = Base32Util.toBase32Crockford(randBytes);
|
||||
|
||||
return timestampComponent + randomnessComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a ULID string to a UUID.
|
||||
*
|
||||
* An exception is thrown if the ULID string is invalid.
|
||||
*
|
||||
* @param ulid
|
||||
* a ULID
|
||||
* @return a UUID if valid
|
||||
*/
|
||||
public static UUID fromUlidToUuid(final String ulid) {
|
||||
|
||||
UlidUtil.validate(ulid);
|
||||
|
||||
// Extract timestamp component
|
||||
final String timestampComponent = ulid.substring(0, 10);
|
||||
final long timeNumber = Base32Util.fromBase32CrockfordAsLong(timestampComponent);
|
||||
|
||||
// Extract randomness component
|
||||
final String randomnessComponent = ulid.substring(10, 26);
|
||||
byte[] randBytes = Base32Util.fromBase32Crockford(randomnessComponent);
|
||||
byte[] lsbBytes = new byte[8];
|
||||
System.arraycopy(randBytes, 2, lsbBytes, 0, 8);
|
||||
|
||||
final long msb = (timeNumber << 16) | ((randBytes[0] << 8) & 0x0000ff00L) | ((randBytes[1]) & 0x000000ffL);
|
||||
final long lsb = ByteUtil.toNumber(lsbBytes);
|
||||
|
||||
return new UUID(msb, lsb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of bytes from a UUID.
|
||||
*
|
||||
* @param uuid
|
||||
* a UUID
|
||||
* @return an array of bytes
|
||||
*/
|
||||
public static byte[] fromUuidToBytes(final UUID uuid) {
|
||||
final long msb = uuid.getMostSignificantBits();
|
||||
final long lsb = uuid.getLeastSignificantBits();
|
||||
final byte[] msbBytes = ByteUtil.toBytes(msb);
|
||||
final byte[] lsbBytes = ByteUtil.toBytes(lsb);
|
||||
return ByteUtil.concat(msbBytes, lsbBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a UUID from an array of bytes;
|
||||
*
|
||||
* @param bytes
|
||||
* an array of bytes
|
||||
* @return a UUID
|
||||
*/
|
||||
public static UUID fromBytesToUuid(byte[] bytes) {
|
||||
byte[] msbBytes = new byte[8];
|
||||
System.arraycopy(bytes, 0, msbBytes, 0, 8);
|
||||
byte[] lsbBytes = new byte[8];
|
||||
System.arraycopy(bytes, 8, lsbBytes, 0, 8);
|
||||
final long msb = ByteUtil.toNumber(msbBytes);
|
||||
final long lsb = ByteUtil.toNumber(lsbBytes);
|
||||
return new UUID(msb, lsb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an array of bytes to a ULID string.
|
||||
*
|
||||
* @param bytes
|
||||
* a byte array
|
||||
* @return a ULID string
|
||||
*/
|
||||
public static String fromBytesToUlid(byte[] bytes) {
|
||||
|
||||
byte[] timeBytes = new byte[6];
|
||||
System.arraycopy(bytes, 0, timeBytes, 0, 6);
|
||||
final long timeNumber = ByteUtil.toNumber(timeBytes);
|
||||
final String timestampComponent = leftPad(Base32Util.toBase32Crockford(timeNumber));
|
||||
|
||||
byte[] randBytes = new byte[10];
|
||||
System.arraycopy(bytes, 6, randBytes, 0, 10);
|
||||
final String randomnessComponent = Base32Util.toBase32Crockford(randBytes);
|
||||
|
||||
return timestampComponent + randomnessComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a ULID string to an array of bytes.
|
||||
*
|
||||
* @param ulid
|
||||
* a ULID string
|
||||
* @return an array of bytes
|
||||
*/
|
||||
public static byte[] fromUlidToBytes(final String ulid) {
|
||||
UlidUtil.validate(ulid);
|
||||
byte[] bytes = new byte[16];
|
||||
|
||||
final String timestampComponent = ulid.substring(0, 10);
|
||||
final long timeNumber = Base32Util.fromBase32CrockfordAsLong(timestampComponent);
|
||||
byte[] timeBytes = ByteUtil.toBytes(timeNumber);
|
||||
System.arraycopy(timeBytes, 2, bytes, 0, 6);
|
||||
|
||||
final String randomnessComponent = ulid.substring(10, 26);
|
||||
byte[] randBytes = Base32Util.fromBase32Crockford(randomnessComponent);
|
||||
System.arraycopy(randBytes, 0, bytes, 6, 10);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the ULID string is a valid.
|
||||
*
|
||||
* The validation mode is not strict.
|
||||
*
|
||||
* See {@link UlidUtil#validate(String, boolean)}.
|
||||
*
|
||||
* @param ulid
|
||||
* a ULID
|
||||
*/
|
||||
protected static void validate(String ulid) {
|
||||
validate(ulid, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the ULID string is a valid.
|
||||
*
|
||||
* See {@link UlidUtil#validate(String, boolean)}.
|
||||
*
|
||||
* @param ulid
|
||||
* a ULID
|
||||
*/
|
||||
protected static void validate(String ulid, boolean strict) {
|
||||
if (!isValid(ulid, strict)) {
|
||||
throw new UlidUtilException(String.format("Invalid ULID: %s.", ulid));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the string is a valid ULID.
|
||||
*
|
||||
* The validation mode is not strict.
|
||||
*
|
||||
* See {@link UlidUtil#validate(String, boolean)}.
|
||||
*/
|
||||
public static boolean isValid(String ulid) {
|
||||
return isValid(ulid, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the string is a valid ULID.
|
||||
*
|
||||
* <pre>
|
||||
* Strict validation: checks if the string is in the ULID specification format:
|
||||
*
|
||||
* - 0123456789ABCDEFGHJKMNPKRS (26 alphanumeric, case insensitive, except iI, lL, oO and uU)
|
||||
*
|
||||
* Loose validation: checks if the string is in one of these formats:
|
||||
*
|
||||
* - 0123456789ABCDEFGHIJKLMNOP (26 alphanumeric, case insensitive, except uU)
|
||||
* </pre>
|
||||
*
|
||||
* @param ulid
|
||||
* a ULID
|
||||
* @param strict
|
||||
* true for strict validation, false for loose validation
|
||||
* @return boolean true if valid
|
||||
*/
|
||||
public static boolean isValid(String ulid, boolean strict) {
|
||||
|
||||
if (ulid == null || ulid.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean matches = false;
|
||||
|
||||
if (strict) {
|
||||
matches = ulid.matches(ULID_PATTERN_STRICT);
|
||||
} else {
|
||||
String u = ulid.replaceAll("-", "");
|
||||
matches = u.matches(ULID_PATTERN_LOOSE);
|
||||
}
|
||||
|
||||
if (!matches) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long timestamp = extractUnixMilliseconds(ulid);
|
||||
return timestamp >= 0 && timestamp <= TIMESTAMP_MAX;
|
||||
}
|
||||
|
||||
public static long extractTimestamp(String ulid) {
|
||||
UlidUtil.validate(ulid);
|
||||
UlidValidator.validate(ulid);
|
||||
return extractUnixMilliseconds(ulid);
|
||||
}
|
||||
|
||||
|
@ -265,12 +44,12 @@ public class UlidUtil {
|
|||
}
|
||||
|
||||
public static String extractTimestampComponent(String ulid) {
|
||||
UlidUtil.validate(ulid);
|
||||
UlidValidator.validate(ulid);
|
||||
return ulid.substring(0, 10);
|
||||
}
|
||||
|
||||
public static String extractRandomnessComponent(String ulid) {
|
||||
UlidUtil.validate(ulid);
|
||||
UlidValidator.validate(ulid);
|
||||
return ulid.substring(10, 26);
|
||||
}
|
||||
|
||||
|
@ -278,16 +57,4 @@ public class UlidUtil {
|
|||
String milliseconds = ulid.substring(0, 10);
|
||||
return Base32Util.fromBase32CrockfordAsLong(milliseconds);
|
||||
}
|
||||
|
||||
private static String leftPad(String unpadded) {
|
||||
return "0000000000".substring(unpadded.length()) + unpadded;
|
||||
}
|
||||
|
||||
public static class UlidUtilException extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public UlidUtilException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020 Fabio Lima
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.github.f4b6a3.ulid.util;
|
||||
|
||||
import com.github.f4b6a3.ulid.exception.InvalidUlidException;
|
||||
|
||||
public class UlidValidator {
|
||||
|
||||
protected static final String ULID_PATTERN = "^[0-9a-tv-zA-TV-Z]{26}$";
|
||||
|
||||
// Date: 10889-08-02T05:31:50.655Z
|
||||
protected static final long TIMESTAMP_MAX = (long) Math.pow(2, 48) - 1;
|
||||
|
||||
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.
|
||||
*
|
||||
* Dashes are ignored by this validator.
|
||||
*
|
||||
* <pre>
|
||||
* Examples of valid ULID strings:
|
||||
* - 0123456789ABCDEFGHJKMNPKRS (26 alphanumeric, case insensitive, except iI, lL, oO and uU)
|
||||
* - 0123456789ABCDEFGHIJKLMNOP (26 alphanumeric, case insensitive, except uU)
|
||||
* - 0123456789-ABCDEFGHJK-MNPKRS (26 alphanumeric, case insensitive, except iI, lL, oO and uU)
|
||||
* - 0123456789-ABCDEFGHIJ-KLMNOP (26 alphanumeric, case insensitive, except uU, with dashes)
|
||||
* </pre>
|
||||
*
|
||||
* @param ulid a ULID
|
||||
* @return boolean true if valid
|
||||
*/
|
||||
public static boolean isValid(String ulid) {
|
||||
|
||||
if (ulid == null || ulid.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String u = ulid.replaceAll("-", "");
|
||||
if (!u.matches(ULID_PATTERN)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long timestamp = UlidUtil.extractUnixMilliseconds(ulid);
|
||||
return timestamp >= 0 && timestamp <= TIMESTAMP_MAX;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the ULID string is a valid.
|
||||
*
|
||||
* See {@link TsidValidator#isValid(String)}.
|
||||
*
|
||||
* @param ulid a ULID string
|
||||
* @throws InvalidUlidException if invalid
|
||||
*/
|
||||
protected static void validate(String ulid) {
|
||||
if (!isValid(ulid)) {
|
||||
throw new InvalidUlidException(String.format("Invalid ULID: %s.", ulid));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +1,21 @@
|
|||
package com.github.f4b6a3;
|
||||
package com.github.f4b6a3.ulid;
|
||||
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Suite;
|
||||
|
||||
import com.github.f4b6a3.ulid.UlidCreatorTest;
|
||||
import com.github.f4b6a3.ulid.creator.UlidBasedGuidCreatorTest;
|
||||
import com.github.f4b6a3.ulid.timestamp.DefaultTimestampStrategyTest;
|
||||
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;
|
||||
|
||||
@RunWith(Suite.class)
|
||||
@Suite.SuiteClasses({
|
||||
DefaultTimestampStrategyTest.class,
|
||||
UlidBasedGuidCreatorTest.class,
|
||||
UlidUtilTest.class,
|
||||
UlidCreatorTest.class,
|
||||
UlidBasedGuidCreatorTest.class,
|
||||
UlidConverterTest.class,
|
||||
UlidUtilTest.class,
|
||||
UlidValidatorTest.class,
|
||||
})
|
||||
|
||||
/**
|
|
@ -1,4 +1,4 @@
|
|||
package com.github.f4b6a3;
|
||||
package com.github.f4b6a3.ulid;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.UUID;
|
||||
|
@ -6,7 +6,7 @@ import java.util.UUID;
|
|||
import com.github.f4b6a3.ulid.UlidCreator;
|
||||
import com.github.f4b6a3.ulid.creator.UlidBasedGuidCreator;
|
||||
import com.github.f4b6a3.ulid.exception.UlidCreatorException;
|
||||
import com.github.f4b6a3.ulid.timestamp.FixedTimestampStretegy;
|
||||
import com.github.f4b6a3.ulid.strategy.timestamp.FixedTimestampStretegy;
|
||||
|
||||
/**
|
||||
*
|
|
@ -1,4 +1,4 @@
|
|||
package com.github.f4b6a3;
|
||||
package com.github.f4b6a3.ulid.bench;
|
||||
|
||||
// Add theese dependencies to pom.xml:
|
||||
//
|
|
@ -7,7 +7,7 @@ import org.junit.Test;
|
|||
|
||||
import com.github.f4b6a3.commons.random.Xorshift128PlusRandom;
|
||||
import com.github.f4b6a3.ulid.exception.UlidCreatorException;
|
||||
import com.github.f4b6a3.ulid.timestamp.FixedTimestampStretegy;
|
||||
import com.github.f4b6a3.ulid.strategy.timestamp.FixedTimestampStretegy;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package com.github.f4b6a3.demo;
|
||||
package com.github.f4b6a3.ulid.demo;
|
||||
|
||||
import com.github.f4b6a3.ulid.UlidCreator;
|
||||
|
||||
|
@ -10,7 +10,7 @@ public class DemoTest {
|
|||
int max = 100;
|
||||
|
||||
System.out.println(HORIZONTAL_LINE);
|
||||
System.out.println("### ULID");
|
||||
System.out.println("### ULID string");
|
||||
System.out.println(HORIZONTAL_LINE);
|
||||
|
||||
for (int i = 0; i < max; i++) {
|
||||
|
@ -18,7 +18,7 @@ public class DemoTest {
|
|||
}
|
||||
|
||||
System.out.println(HORIZONTAL_LINE);
|
||||
System.out.println("### GUID");
|
||||
System.out.println("### ULID-based GUID");
|
||||
System.out.println(HORIZONTAL_LINE);
|
||||
|
||||
for (int i = 0; i < max; i++) {
|
|
@ -1,12 +0,0 @@
|
|||
package com.github.f4b6a3.ulid.timestamp;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class DefaultTimestampStrategyTest {
|
||||
@Test
|
||||
public void testVoid() {
|
||||
assertTrue("void test", true);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.github.f4b6a3.ulid;
|
||||
package com.github.f4b6a3.ulid.ulid;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
@ -6,6 +6,7 @@ import org.junit.Test;
|
|||
import com.github.f4b6a3.ulid.UlidCreator;
|
||||
import com.github.f4b6a3.ulid.creator.UlidBasedGuidCreator;
|
||||
import com.github.f4b6a3.ulid.util.UlidUtil;
|
||||
import com.github.f4b6a3.ulid.util.UlidValidator;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import java.util.Arrays;
|
||||
|
@ -54,7 +55,7 @@ public class UlidCreatorTest {
|
|||
assertTrue("ULID is null", ulid != null);
|
||||
assertTrue("ULID is empty", !ulid.isEmpty());
|
||||
assertTrue("ULID length is wrong ", ulid.length() == ULID_LENGTH);
|
||||
assertTrue("ULID is not valid", UlidUtil.isValid(ulid, /* strict */ true));
|
||||
assertTrue("ULID is not valid", UlidValidator.isValid(ulid));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.github.f4b6a3.ulid.util;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
assertTrue("ULID is null", ulid != null);
|
||||
assertTrue("ULID is empty", !ulid.isEmpty());
|
||||
assertTrue("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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,14 +2,13 @@ package com.github.f4b6a3.ulid.util;
|
|||
|
||||
import static org.junit.Assert.*;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.github.f4b6a3.ulid.util.UlidUtil.UlidUtilException;
|
||||
import com.github.f4b6a3.commons.util.Base32Util;
|
||||
import com.github.f4b6a3.commons.util.ByteUtil;
|
||||
import com.github.f4b6a3.ulid.UlidCreator;
|
||||
import com.github.f4b6a3.ulid.exception.InvalidUlidException;
|
||||
import com.github.f4b6a3.ulid.util.UlidUtil;
|
||||
|
||||
public class UlidUtilTest {
|
||||
|
||||
|
@ -19,13 +18,10 @@ public class UlidUtilTest {
|
|||
|
||||
private static final long TIMESTAMP_MAX = 281474976710655l; // 2^48 - 1
|
||||
|
||||
private static final int ULID_LENGTH = 26;
|
||||
private static final int DEFAULT_LOOP_MAX = 100_000;
|
||||
|
||||
private static final String[] EXAMPLE_DATES = { "1970-01-01T00:00:00.000Z", "1985-10-26T01:16:00.123Z",
|
||||
"2001-09-09T01:46:40.456Z", "2020-01-15T14:30:33.789Z", "2038-01-19T03:14:07.321Z" };
|
||||
|
||||
@Test(expected = UlidUtilException.class)
|
||||
@Test(expected = InvalidUlidException.class)
|
||||
public void testExtractTimestamp() {
|
||||
|
||||
String ulid = "0000000000" + EXAMPLE_RANDOMNESS;
|
||||
|
@ -95,155 +91,6 @@ public class UlidUtilTest {
|
|||
assertEquals(expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsValidLoose() {
|
||||
|
||||
String ulid = null; // Null
|
||||
assertFalse("Null ULID should be invalid.", UlidUtil.isValid(ulid));
|
||||
|
||||
ulid = ""; // length: 0
|
||||
assertFalse("ULID with empty string should be invalid.", UlidUtil.isValid(ulid));
|
||||
|
||||
ulid = EXAMPLE_ULID; // All upper case
|
||||
assertTrue("Ulid in upper case should valid.", UlidUtil.isValid(ulid));
|
||||
|
||||
ulid = "0123456789abcdefghjklmnpqr"; // All lower case
|
||||
assertTrue("ULID in lower case should be valid.", UlidUtil.isValid(ulid));
|
||||
|
||||
ulid = "0123456789AbCdEfGhJkMnPqRs"; // Mixed case
|
||||
assertTrue("Ulid in upper and lower case should valid.", UlidUtil.isValid(ulid));
|
||||
|
||||
ulid = "0123456789ABCDEFGHJKLMNPQ"; // length: 25
|
||||
assertFalse("ULID length lower than 26 should be invalid.", UlidUtil.isValid(ulid));
|
||||
|
||||
ulid = "0123456789ABCDEFGHJKMNPQZZZ"; // length: 27
|
||||
assertFalse("ULID length greater than 26 should be invalid.", UlidUtil.isValid(ulid));
|
||||
|
||||
ulid = "u123456789ABCDEFGHJKMNPQRS"; // Letter u
|
||||
assertFalse("ULID with 'u' or 'U' should be invalid.", UlidUtil.isValid(ulid));
|
||||
|
||||
ulid = "#123456789ABCDEFGHJKMNPQRS"; // Special char
|
||||
assertFalse("ULID with special chars should be invalid.", UlidUtil.isValid(ulid));
|
||||
|
||||
ulid = "01234-56789-ABCDEFGHJKMNPQRS"; // Hiphens
|
||||
assertTrue("ULID with hiphens should be valid.", UlidUtil.isValid(ulid));
|
||||
|
||||
ulid = "8ZZZZZZZZZABCDEFGHJKMNPQRS"; // timestamp > (2^48)-1
|
||||
assertFalse("ULID with timestamp greater than (2^48)-1 should be invalid.", UlidUtil.isValid(ulid));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsValidStrict() {
|
||||
boolean strict = true;
|
||||
|
||||
String ulid = null; // Null
|
||||
assertFalse("Null ULID should be invalid in strict mode.", UlidUtil.isValid(ulid, strict));
|
||||
|
||||
ulid = ""; // length: 0
|
||||
assertFalse("ULID with empty string should be invalid in strict mode.", UlidUtil.isValid(ulid, strict));
|
||||
|
||||
ulid = EXAMPLE_ULID; // All upper case
|
||||
assertTrue("ULID in upper case should valid in strict mode.", UlidUtil.isValid(ulid, strict));
|
||||
|
||||
ulid = "0123456789abcdefghjkmnpqrs"; // All lower case
|
||||
assertTrue("ULID in lower case should be valid in strict mode.", UlidUtil.isValid(ulid, strict));
|
||||
|
||||
ulid = "0123456789AbCdEfGhJkMnPqRs"; // Mixed case
|
||||
assertTrue("ULID in upper and lower case should valid in strict mode.", UlidUtil.isValid(ulid, strict));
|
||||
|
||||
ulid = "0123456789ABCDEFGHJKLMNPQ"; // length: 25
|
||||
assertFalse("ULID length lower than 26 should be invalid in strict mode.", UlidUtil.isValid(ulid, strict));
|
||||
|
||||
ulid = "0123456789ABCDEFGHJKMNPQZZZ"; // length: 27
|
||||
assertFalse("ULID length greater than 26 should be invalid in strict mode.", UlidUtil.isValid(ulid, strict));
|
||||
|
||||
ulid = "i123456789ABCDEFGHJKMNPQRS"; // Letter i
|
||||
assertFalse("ULID with 'i' or 'I' should be invalid in strict mode.", UlidUtil.isValid(ulid, strict));
|
||||
|
||||
ulid = "L123456789ABCDEFGHJKMNPQRS"; // letter L
|
||||
assertFalse("ULID with 'l' or 'L' should be invalid in strict mode.", UlidUtil.isValid(ulid, strict));
|
||||
|
||||
ulid = "o123456789ABCDEFGHJKMNPQRS"; // letter o
|
||||
assertFalse("ULID with 'o' or 'O' should be invalid in strict mode.", UlidUtil.isValid(ulid, strict));
|
||||
|
||||
ulid = "u123456789ABCDEFGHJKMNPQRS"; // letter u
|
||||
assertFalse("ULID with 'u' or 'U' should be invalid in strict mode.", UlidUtil.isValid(ulid, strict));
|
||||
|
||||
ulid = "#123456789ABCDEFGHJKMNPQRS"; // Special char
|
||||
assertFalse("ULID with special chars should be invalid in strict mode.", UlidUtil.isValid(ulid, strict));
|
||||
|
||||
ulid = "01234-56789-ABCDEFGHJKMNPQRS"; // Hyphens
|
||||
assertFalse("ULID with hiphens should be invalid in strict mode.", UlidUtil.isValid(ulid, strict));
|
||||
|
||||
ulid = "8ZZZZZZZZZABCDEFGHJKMNPQRS"; // timestamp > (2^48)-1
|
||||
assertFalse("ULID with timestamp greater than (2^48)-1 should be invalid.", UlidUtil.isValid(ulid));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToAndFromUlid() {
|
||||
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
|
||||
UUID uuid1 = UlidCreator.getUlid();
|
||||
String ulid = UlidUtil.fromUuidToUlid(uuid1);
|
||||
|
||||
assertTrue("ULID is null", ulid != null);
|
||||
assertTrue("ULID is empty", !ulid.isEmpty());
|
||||
assertTrue("ULID length is wrong ", ulid.length() == ULID_LENGTH);
|
||||
assertTrue("ULID is not valid", UlidUtil.isValid(ulid, /* strict */
|
||||
true));
|
||||
|
||||
UUID uuid2 = UlidUtil.fromUlidToUuid(ulid);
|
||||
assertEquals("Result ULID is different from original ULID", uuid1, uuid2);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToAndFromBytes() {
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
String ulid1 = UlidCreator.getUlidString();
|
||||
byte[] bytes = UlidUtil.fromUlidToBytes(ulid1);
|
||||
String ulid2 = UlidUtil.fromBytesToUlid(bytes);
|
||||
|
||||
// Check ULID 1
|
||||
assertTrue(ulid1 != null);
|
||||
assertTrue(!ulid1.isEmpty());
|
||||
assertTrue(ulid1.length() == ULID_LENGTH);
|
||||
assertTrue(UlidUtil.isValid(ulid1, /* strict */ true));
|
||||
|
||||
// Check ULID 2
|
||||
assertTrue(ulid2 != null);
|
||||
assertTrue(!ulid2.isEmpty());
|
||||
assertTrue(ulid2.length() == ULID_LENGTH);
|
||||
assertTrue(UlidUtil.isValid(ulid2, /* strict */ true));
|
||||
|
||||
assertEquals(ulid1, ulid2);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromUuidToBytes() {
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
UUID uuid1 = UlidCreator.getUlid();
|
||||
byte[] bytes = UlidUtil.fromUuidToBytes(uuid1);
|
||||
long msb = ByteUtil.toNumber(ByteUtil.copy(bytes, 0, 8));
|
||||
long lsb = ByteUtil.toNumber(ByteUtil.copy(bytes, 8, 16));
|
||||
UUID uuid2 = new UUID(msb, lsb);
|
||||
assertEquals(uuid1, uuid2);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromBytesToUuid() {
|
||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||
UUID uuid1 = UlidCreator.getUlid();
|
||||
byte[] bytes = UlidUtil.fromUuidToBytes(uuid1);
|
||||
UUID uuid2 = UlidUtil.fromBytesToUuid(bytes);
|
||||
assertEquals(uuid1, uuid2);
|
||||
}
|
||||
}
|
||||
|
||||
private String leftPad(String unpadded) {
|
||||
return "0000000000".substring(unpadded.length()) + unpadded;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package com.github.f4b6a3.ulid.util;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.github.f4b6a3.ulid.util.UlidValidator;
|
||||
|
||||
public class UlidValidatorTest {
|
||||
|
||||
@Test
|
||||
public void testIsValidStrict() {
|
||||
|
||||
String ulid = null; // Null
|
||||
assertFalse("Null ULID should be invalid.", UlidValidator.isValid(ulid));
|
||||
|
||||
ulid = ""; // length: 0
|
||||
assertFalse("ULID with empty string should be invalid .", UlidValidator.isValid(ulid));
|
||||
|
||||
ulid = "0123456789ABCDEFGHJKMNPQRS"; // All upper case
|
||||
assertTrue("ULID in upper case should valid.", UlidValidator.isValid(ulid));
|
||||
|
||||
ulid = "0123456789abcdefghjklmnpqr"; // All lower case
|
||||
assertTrue("ULID in lower case should be valid.", UlidValidator.isValid(ulid));
|
||||
|
||||
ulid = "0123456789AbCdEfGhJkMnPqRs"; // Mixed case
|
||||
assertTrue("Ulid in upper and lower case should valid.", UlidValidator.isValid(ulid));
|
||||
|
||||
ulid = "0123456789ABCDEFGHJKLMNPQ"; // length: 25
|
||||
assertFalse("ULID length lower than 26 should be invalid.", UlidValidator.isValid(ulid));
|
||||
|
||||
ulid = "0123456789ABCDEFGHJKMNPQZZZ"; // length: 27
|
||||
assertFalse("ULID length greater than 26 should be invalid.", UlidValidator.isValid(ulid));
|
||||
|
||||
ulid = "u123456789ABCDEFGHJKMNPQRS"; // Letter u
|
||||
assertFalse("ULID with 'u' or 'U' should be invalid.", UlidValidator.isValid(ulid));
|
||||
|
||||
ulid = "#123456789ABCDEFGHJKMNPQRS"; // Special char
|
||||
assertFalse("ULID with special chars should be invalid.", UlidValidator.isValid(ulid));
|
||||
|
||||
ulid = "01234-56789-ABCDEFGHJKMNPQRS"; // Hyphens
|
||||
assertTrue("ULID with hiphens should be valid.", UlidValidator.isValid(ulid));
|
||||
|
||||
ulid = "8ZZZZZZZZZABCDEFGHJKMNPQRS"; // timestamp > (2^48)-1
|
||||
assertFalse("ULID with timestamp greater than (2^48)-1 should be invalid.", UlidValidator.isValid(ulid));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue