
com.anwen.mongo.incrementer.id.ULID Maven / Gradle / Ivy
package com.anwen.mongo.incrementer.id;
import java.io.Serializable;
import java.security.SecureRandom;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
public class ULID {
private static final char[] ENCODING_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',
};
private static final byte[] DECODING_CHARS = {
// 0
-1, -1, -1, -1, -1, -1, -1, -1,
// 8
-1, -1, -1, -1, -1, -1, -1, -1,
// 16
-1, -1, -1, -1, -1, -1, -1, -1,
// 24
-1, -1, -1, -1, -1, -1, -1, -1,
// 32
-1, -1, -1, -1, -1, -1, -1, -1,
// 40
-1, -1, -1, -1, -1, -1, -1, -1,
// 48
0, 1, 2, 3, 4, 5, 6, 7,
// 56
8, 9, -1, -1, -1, -1, -1, -1,
// 64
-1, 10, 11, 12, 13, 14, 15, 16,
// 72
17, 1, 18, 19, 1, 20, 21, 0,
// 80
22, 23, 24, 25, 26, -1, 27, 28,
// 88
29, 30, 31, -1, -1, -1, -1, -1,
// 96
-1, 10, 11, 12, 13, 14, 15, 16,
// 104
17, 1, 18, 19, 1, 20, 21, 0,
// 112
22, 23, 24, 25, 26, -1, 27, 28,
// 120
29, 30, 31,
};
private static final int MASK = 0x1F;
private static final int MASK_BITS = 5;
private static final long TIMESTAMP_OVERFLOW_MASK = 0xFFFF_0000_0000_0000L;
private static final long TIMESTAMP_MSB_MASK = 0xFFFF_FFFF_FFFF_0000L;
private static final long RANDOM_MSB_MASK = 0xFFFFL;
private final Random random;
public ULID()
{
this(new SecureRandom());
}
public ULID(Random random)
{
Objects.requireNonNull(random, "random must not be null!");
this.random = random;
}
public void appendULID(StringBuilder stringBuilder)
{
Objects.requireNonNull(stringBuilder, "stringBuilder must not be null!");
internalAppendULID(stringBuilder, System.currentTimeMillis(), random);
}
public String nextULID()
{
return nextULID(System.currentTimeMillis());
}
public String nextULID(long timestamp)
{
return internalUIDString(timestamp, random);
}
public Value nextValue()
{
return nextValue(System.currentTimeMillis());
}
public Value nextValue(long timestamp)
{
return internalNextValue(timestamp, random);
}
/**
* Returns the next monotonic value. If an overflow happened while incrementing
* the random part of the given previous ULID value then the returned value will
* have a zero random part.
*
* @param previousUlid the previous ULID value.
* @return the next monotonic value.
*/
public Value nextMonotonicValue(Value previousUlid)
{
return nextMonotonicValue(previousUlid, System.currentTimeMillis());
}
/**
* Returns the next monotonic value. If an overflow happened while incrementing
* the random part of the given previous ULID value then the returned value will
* have a zero random part.
*
* @param previousUlid the previous ULID value.
* @param timestamp the timestamp of the next ULID value.
* @return the next monotonic value.
*/
public Value nextMonotonicValue(Value previousUlid, long timestamp)
{
Objects.requireNonNull(previousUlid, "previousUlid must not be null!");
if(previousUlid.timestamp() == timestamp)
{
return previousUlid.increment();
}
return nextValue(timestamp);
}
/**
* Returns the next monotonic value or empty if an overflow happened while incrementing
* the random part of the given previous ULID value.
*
* @param previousUlid the previous ULID value.
* @return the next monotonic value or empty if an overflow happened.
*/
public Optional nextStrictlyMonotonicValue(Value previousUlid)
{
return nextStrictlyMonotonicValue(previousUlid, System.currentTimeMillis());
}
/**
* Returns the next monotonic value or empty if an overflow happened while incrementing
* the random part of the given previous ULID value.
*
* @param previousUlid the previous ULID value.
* @param timestamp the timestamp of the next ULID value.
* @return the next monotonic value or empty if an overflow happened.
*/
public Optional nextStrictlyMonotonicValue(Value previousUlid, long timestamp)
{
Value result = nextMonotonicValue(previousUlid, timestamp);
if(result.compareTo(previousUlid) < 1)
{
return Optional.empty();
}
return Optional.of(result);
}
public static Value parseULID(String ulidString)
{
Objects.requireNonNull(ulidString, "ulidString must not be null!");
if(ulidString.length() != 26)
{
throw new IllegalArgumentException("ulidString must be exactly 26 chars long.");
}
String timeString = ulidString.substring(0, 10);
long time = internalParseCrockford(timeString);
if ((time & TIMESTAMP_OVERFLOW_MASK) != 0)
{
throw new IllegalArgumentException("ulidString must not exceed '7ZZZZZZZZZZZZZZZZZZZZZZZZZ'!");
}
String part1String = ulidString.substring(10, 18);
String part2String = ulidString.substring(18);
long part1 = internalParseCrockford(part1String);
long part2 = internalParseCrockford(part2String);
long most = (time << 16) | (part1 >>> 24);
long least = part2 | (part1 << 40);
return new Value(most, least);
}
public static Value fromBytes(byte[] data)
{
Objects.requireNonNull(data, "data must not be null!");
if(data.length != 16)
{
throw new IllegalArgumentException("data must be 16 bytes in length!");
}
long mostSignificantBits = 0;
long leastSignificantBits = 0;
for (int i=0; i<8; i++)
{
mostSignificantBits = (mostSignificantBits << 8) | (data[i] & 0xff);
}
for (int i=8; i<16; i++)
{
leastSignificantBits = (leastSignificantBits << 8) | (data[i] & 0xff);
}
return new Value(mostSignificantBits, leastSignificantBits);
}
public static class Value
implements Comparable, Serializable
{
private static final long serialVersionUID = -3563159514112487717L;
/*
* The most significant 64 bits of this ULID.
*/
private final long mostSignificantBits;
/*
* The least significant 64 bits of this ULID.
*/
private final long leastSignificantBits;
public Value(long mostSignificantBits, long leastSignificantBits)
{
this.mostSignificantBits = mostSignificantBits;
this.leastSignificantBits = leastSignificantBits;
}
/**
* Returns the most significant 64 bits of this ULID's 128 bit value.
*
* @return The most significant 64 bits of this ULID's 128 bit value
*/
public long getMostSignificantBits() {
return mostSignificantBits;
}
/**
* Returns the least significant 64 bits of this ULID's 128 bit value.
*
* @return The least significant 64 bits of this ULID's 128 bit value
*/
public long getLeastSignificantBits() {
return leastSignificantBits;
}
public long timestamp()
{
return mostSignificantBits >>> 16;
}
public byte[] toBytes()
{
byte[] result=new byte[16];
for (int i=0; i<8; i++)
{
result[i] = (byte)((mostSignificantBits >> ((7-i)*8)) & 0xFF);
}
for (int i=8; i<16; i++)
{
result[i] = (byte)((leastSignificantBits >> ((15-i)*8)) & 0xFF);
}
return result;
}
public Value increment()
{
long lsb = leastSignificantBits;
if(lsb != 0xFFFF_FFFF_FFFF_FFFFL)
{
return new Value(mostSignificantBits, lsb+1);
}
long msb = mostSignificantBits;
if((msb & RANDOM_MSB_MASK) != RANDOM_MSB_MASK)
{
return new Value(msb + 1, 0);
}
return new Value(msb & TIMESTAMP_MSB_MASK, 0);
}
@Override
public int hashCode() {
long hilo = mostSignificantBits ^ leastSignificantBits;
return ((int)(hilo >> 32)) ^ (int) hilo;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Value value = (Value) o;
return mostSignificantBits == value.mostSignificantBits
&& leastSignificantBits == value.leastSignificantBits;
}
@Override
public int compareTo(Value val)
{
// The ordering is intentionally set up so that the ULIDs
// can simply be numerically compared as two numbers
return (this.mostSignificantBits < val.mostSignificantBits ? -1 :
(this.mostSignificantBits > val.mostSignificantBits ? 1 :
(Long.compare(this.leastSignificantBits, val.leastSignificantBits))));
}
@Override
public String toString()
{
char[] buffer = new char[26];
internalWriteCrockford(buffer, timestamp(), 10, 0);
long value = ((mostSignificantBits & 0xFFFFL) << 24);
long interim = (leastSignificantBits >>> 40);
value = value | interim;
internalWriteCrockford(buffer, value, 8, 10);
internalWriteCrockford(buffer, leastSignificantBits, 8, 18);
return new String(buffer);
}
}
/*
* http://crockford.com/wrmg/base32.html
*/
static void internalAppendCrockford(StringBuilder builder, long value, int count)
{
for(int i = count-1; i >= 0; i--)
{
int index = (int)((value >>> (i * MASK_BITS)) & MASK);
builder.append(ENCODING_CHARS[index]);
}
}
static long internalParseCrockford(String input)
{
Objects.requireNonNull(input, "input must not be null!");
int length = input.length();
if(length > 12)
{
throw new IllegalArgumentException("input length must not exceed 12 but was "+length+"!");
}
long result = 0;
for(int i=0;i>> ((count - i - 1) * MASK_BITS)) & MASK);
buffer[offset+i] = ENCODING_CHARS[index];
}
}
static String internalUIDString(long timestamp, Random random)
{
checkTimestamp(timestamp);
char[] buffer = new char[26];
internalWriteCrockford(buffer, timestamp, 10, 0);
// could use nextBytes(byte[] bytes) instead
internalWriteCrockford(buffer, random.nextLong(), 8, 10);
internalWriteCrockford(buffer, random.nextLong(), 8, 18);
return new String(buffer);
}
static void internalAppendULID(StringBuilder builder, long timestamp, Random random)
{
checkTimestamp(timestamp);
internalAppendCrockford(builder, timestamp, 10);
// could use nextBytes(byte[] bytes) instead
internalAppendCrockford(builder, random.nextLong(), 8);
internalAppendCrockford(builder, random.nextLong(), 8);
}
static Value internalNextValue(long timestamp, Random random)
{
checkTimestamp(timestamp);
// could use nextBytes(byte[] bytes) instead
long mostSignificantBits = random.nextLong();
long leastSignificantBits = random.nextLong();
mostSignificantBits &= 0xFFFF;
mostSignificantBits |= (timestamp << 16);
return new Value(mostSignificantBits, leastSignificantBits);
}
private static void checkTimestamp(long timestamp)
{
if((timestamp & TIMESTAMP_OVERFLOW_MASK) != 0)
{
throw new IllegalArgumentException("ULID does not support timestamps after +10889-08-02T05:31:50.655Z!");
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy