Rearranging the code
This commit is contained in:
parent
63bee56534
commit
f885a03125
153
README.md
153
README.md
|
@ -1,2 +1,153 @@
|
||||||
|
|
||||||
# ulid-creator
|
# ulid-creator
|
||||||
A Java library for generating and handling ULIDs
|
|
||||||
|
A Java library for generating and handling ULIDs - _Universally Unique Lexicographically Sortable Identifiers_.
|
||||||
|
|
||||||
|
How to Use
|
||||||
|
------------------------------------------------------
|
||||||
|
|
||||||
|
Create a ULID:
|
||||||
|
|
||||||
|
```java
|
||||||
|
String ulid = UlidCreator.getUlid();
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a fast ULID:
|
||||||
|
|
||||||
|
```java
|
||||||
|
String ulid = UlidCreator.getFastUlid();
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a ULID as GUID object:
|
||||||
|
|
||||||
|
```java
|
||||||
|
UUID ulid = UlidCreator.getGuid();
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a fast ULID as GUID object:
|
||||||
|
|
||||||
|
```java
|
||||||
|
UUID ulid = UlidCreator.getFastGuid();
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a ULID as byte sequence:
|
||||||
|
|
||||||
|
```java
|
||||||
|
byte[] ulid = UlidCreator.getBytes();
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a fast ULID as byte sequence:
|
||||||
|
|
||||||
|
```java
|
||||||
|
byte[] ulid = UlidCreator.getFastBytes();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Maven dependency
|
||||||
|
|
||||||
|
Work in progress.
|
||||||
|
|
||||||
|
Implementation
|
||||||
|
------------------------------------------------------
|
||||||
|
|
||||||
|
### ULID
|
||||||
|
|
||||||
|
The ULID is a unique and sortable 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();
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples of ULIDs:
|
||||||
|
|
||||||
|
```text
|
||||||
|
01E1PPRTMSQ34W7JR5YSND6B8T
|
||||||
|
01E1PPRTMSQ34W7JR5YSND6B8V
|
||||||
|
01E1PPRTMSQ34W7JR5YSND6B8W
|
||||||
|
01E1PPRTMSQ34W7JR5YSND6B8X
|
||||||
|
01E1PPRTMSQ34W7JR5YSND6B8Y
|
||||||
|
01E1PPRTMSQ34W7JR5YSND6B8Z
|
||||||
|
01E1PPRTMSQ34W7JR5YSND6B90
|
||||||
|
01E1PPRTMSQ34W7JR5YSND6B91
|
||||||
|
01E1PPRTMTYMX8G17TWSJJZMEE < millisecond changed
|
||||||
|
01E1PPRTMTYMX8G17TWSJJZMEF
|
||||||
|
01E1PPRTMTYMX8G17TWSJJZMEG
|
||||||
|
01E1PPRTMTYMX8G17TWSJJZMEH
|
||||||
|
01E1PPRTMTYMX8G17TWSJJZMEJ
|
||||||
|
01E1PPRTMTYMX8G17TWSJJZMEK
|
||||||
|
01E1PPRTMTYMX8G17TWSJJZMEM
|
||||||
|
01E1PPRTMTYMX8G17TWSJJZMEN
|
||||||
|
^ look ^ look
|
||||||
|
|
||||||
|
|---------|--------------|
|
||||||
|
milli randomness
|
||||||
|
```
|
||||||
|
|
||||||
|
### GUID
|
||||||
|
|
||||||
|
The GUIDs in this library are based on the ULID specification <sup>[9]</sup>. 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`.
|
||||||
|
|
||||||
|
```java
|
||||||
|
// GUID based on ULID spec
|
||||||
|
UUID guid = UlidCreator.getGuid();
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples of GUIDs based on ULID spec:
|
||||||
|
|
||||||
|
```text
|
||||||
|
01706d6c-6aac-80bd-7ff5-f660c2dd58ea
|
||||||
|
01706d6c-6aac-80bd-7ff5-f660c2dd58eb
|
||||||
|
01706d6c-6aac-80bd-7ff5-f660c2dd58ec
|
||||||
|
01706d6c-6aac-80bd-7ff5-f660c2dd58ed
|
||||||
|
01706d6c-6aac-80bd-7ff5-f660c2dd58ee
|
||||||
|
01706d6c-6aac-80bd-7ff5-f660c2dd58ef
|
||||||
|
01706d6c-6aac-80bd-7ff5-f660c2dd58f0
|
||||||
|
01706d6c-6aac-80bd-7ff5-f660c2dd58f1
|
||||||
|
01706d6c-6aad-c795-370c-98d0be881bb8 < millisecond changed
|
||||||
|
01706d6c-6aad-c795-370c-98d0be881bb9
|
||||||
|
01706d6c-6aad-c795-370c-98d0be881bba
|
||||||
|
01706d6c-6aad-c795-370c-98d0be881bbb
|
||||||
|
01706d6c-6aad-c795-370c-98d0be881bbc
|
||||||
|
01706d6c-6aad-c795-370c-98d0be881bbd
|
||||||
|
01706d6c-6aad-c795-370c-98d0be881bbe
|
||||||
|
01706d6c-6aad-c795-370c-98d0be881bbf
|
||||||
|
^ look ^ look
|
||||||
|
|
||||||
|
|------------|---------------------|
|
||||||
|
millisecs randomness
|
||||||
|
```
|
||||||
|
|
||||||
|
#### How use the `GuidCreator` directly
|
||||||
|
|
||||||
|
These are some examples of using the `GuidCreator` to create ULIDs:
|
||||||
|
|
||||||
|
```java
|
||||||
|
|
||||||
|
// with java random generator (java.util.Random)
|
||||||
|
String ulid = UlidCreator.getGuidCreator().createUlid();
|
||||||
|
|
||||||
|
// with java random generator (java.util.Random)
|
||||||
|
String ulid = UlidCreator.getGuidCreator()
|
||||||
|
.withRandomGenerator(new Random())
|
||||||
|
.createUlid();
|
||||||
|
|
||||||
|
// with fast random generator (Xorshift128Plus)
|
||||||
|
String ulid = UlidCreator.getGuidCreator()
|
||||||
|
.withFastRandomGenerator()
|
||||||
|
.createUlid();
|
||||||
|
|
||||||
|
// with fast random generator (Xorshift128Plus with salt)
|
||||||
|
int salt = (int) FingerprintUtil.getFingerprint();
|
||||||
|
Random random = new Xorshift128PlusRandom(salt);
|
||||||
|
String ulid = UlidCreator.getGuidCreator()
|
||||||
|
.withRandomGenerator(random)
|
||||||
|
.createUlid();
|
||||||
|
```
|
||||||
|
|
||||||
|
If you use the `GuidCreator` directly, you need do handle the `UlidCreatorException`, in the case that too many ULIDs are requested within the same millisecond.
|
||||||
|
|
|
@ -26,47 +26,95 @@ package com.github.f4b6a3.ulid;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.factory.LexicalOrderGuidCreator;
|
import com.github.f4b6a3.ulid.exception.UlidCreatorException;
|
||||||
|
import com.github.f4b6a3.ulid.guid.GuidCreator;
|
||||||
import com.github.f4b6a3.ulid.util.UlidUtil;
|
import com.github.f4b6a3.ulid.util.UlidUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A factory for Universally Unique Lexicographically Sortable Identifiers.
|
* A factory for Universally Unique Lexicographically Sortable Identifiers.
|
||||||
*
|
*
|
||||||
* @see The ULID spec; https://github.com/ulid/spec
|
* @see The ULID spec; https://github.com/ulid/spec
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public class UlidCreator {
|
public class UlidCreator {
|
||||||
|
|
||||||
private UlidCreator() {
|
private UlidCreator() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a ULID.
|
||||||
|
*
|
||||||
|
* @return a ULID
|
||||||
|
*/
|
||||||
public static String getUlid() {
|
public static String getUlid() {
|
||||||
UUID guid = getLexicalOrderGuid();
|
return GuidCreatorLazyHolder.INSTANCE.createUlid();
|
||||||
return UlidUtil.fromUuidToUlid(guid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a Lexical Order GUID based on the ULID specification.
|
* Returns a fast ULID.
|
||||||
*
|
*
|
||||||
* If you need a ULID string instead of a GUID, use
|
* @return a ULID
|
||||||
* {@link UlidCreator#getUlid()}.
|
|
||||||
*
|
|
||||||
* @return a Lexical Order GUID
|
|
||||||
*/
|
*/
|
||||||
public static UUID getLexicalOrderGuid() {
|
public static String getFastUlid() {
|
||||||
return LexicalOrderCreatorLazyHolder.INSTANCE.create();
|
return FastGuidCreatorLazyHolder.INSTANCE.createUlid();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link LexicalOrderGuidCreator}.
|
* Returns ULID as GUID object.
|
||||||
*
|
*
|
||||||
* @return {@link LexicalOrderGuidCreator}
|
* @return a GUID
|
||||||
*/
|
*/
|
||||||
public static LexicalOrderGuidCreator getLexicalOrderCreator() {
|
public static UUID getGuid() {
|
||||||
return new LexicalOrderGuidCreator();
|
return GuidCreatorLazyHolder.INSTANCE.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class LexicalOrderCreatorLazyHolder {
|
/**
|
||||||
static final LexicalOrderGuidCreator INSTANCE = getLexicalOrderCreator().withoutOverflowException();
|
* Returns fast ULID as GUID object.
|
||||||
|
*
|
||||||
|
* @return a GUID
|
||||||
|
*/
|
||||||
|
public static UUID getFastGuid() {
|
||||||
|
return FastGuidCreatorLazyHolder.INSTANCE.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns ULID as byte sequence.
|
||||||
|
*
|
||||||
|
* @return a GUID
|
||||||
|
*/
|
||||||
|
public static byte[] getBytes() {
|
||||||
|
UUID guid = getGuid();
|
||||||
|
return UlidUtil.fromUuidToBytes(guid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns fast ULID as byte sequence.
|
||||||
|
*
|
||||||
|
* @return a GUID
|
||||||
|
*/
|
||||||
|
public static byte[] getFastBytes() {
|
||||||
|
UUID guid = getFastGuid();
|
||||||
|
return UlidUtil.fromUuidToBytes(guid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a GUID creator for direct use.
|
||||||
|
*
|
||||||
|
* This library uses the {@link GuidCreator} internally to generate ULIDs.
|
||||||
|
*
|
||||||
|
* The {@link GuidCreator} throws a {@link UlidCreatorException} when too
|
||||||
|
* many values are requested in the same millisecond.
|
||||||
|
*
|
||||||
|
* @return a {@link GuidCreator}
|
||||||
|
*/
|
||||||
|
public static GuidCreator getGuidCreator() {
|
||||||
|
return new GuidCreator();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class GuidCreatorLazyHolder {
|
||||||
|
static final GuidCreator INSTANCE = getGuidCreator().withoutOverflowException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FastGuidCreatorLazyHolder {
|
||||||
|
static final GuidCreator INSTANCE = getGuidCreator().withFastRandomGenerator().withoutOverflowException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,13 +22,16 @@
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.github.f4b6a3.ulid.factory;
|
package com.github.f4b6a3.ulid.guid;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.timestamp.TimestampStrategy;
|
import com.github.f4b6a3.ulid.timestamp.TimestampStrategy;
|
||||||
|
import com.github.f4b6a3.ulid.util.FingerprintUtil;
|
||||||
|
import com.github.f4b6a3.ulid.util.UlidUtil;
|
||||||
|
import com.github.f4b6a3.ulid.exception.UlidCreatorException;
|
||||||
import com.github.f4b6a3.ulid.random.Xorshift128PlusRandom;
|
import com.github.f4b6a3.ulid.random.Xorshift128PlusRandom;
|
||||||
import com.github.f4b6a3.ulid.timestamp.DefaultTimestampStrategy;
|
import com.github.f4b6a3.ulid.timestamp.DefaultTimestampStrategy;
|
||||||
|
|
||||||
|
@ -38,7 +41,7 @@ import com.github.f4b6a3.ulid.timestamp.DefaultTimestampStrategy;
|
||||||
*
|
*
|
||||||
* ULID specification: https://github.com/ulid/spec
|
* ULID specification: https://github.com/ulid/spec
|
||||||
*/
|
*/
|
||||||
public class LexicalOrderGuidCreator {
|
public class GuidCreator {
|
||||||
|
|
||||||
protected static final long MAX_LOW = 0xffffffffffffffffL; // unsigned
|
protected static final long MAX_LOW = 0xffffffffffffffffL; // unsigned
|
||||||
protected static final long MAX_HIGH = 0x000000000000ffffL;
|
protected static final long MAX_HIGH = 0x000000000000ffffL;
|
||||||
|
@ -51,18 +54,18 @@ public class LexicalOrderGuidCreator {
|
||||||
protected long low;
|
protected long low;
|
||||||
protected long high;
|
protected long high;
|
||||||
|
|
||||||
protected static final String OVERFLOW_MESSAGE = "The system caused an overflow in the generator by requesting too many GUIDs.";
|
protected static final String OVERFLOW_MESSAGE = "The system caused an overflow in the generator by requesting too many IDs.";
|
||||||
|
|
||||||
protected TimestampStrategy timestampStrategy;
|
protected TimestampStrategy timestampStrategy;
|
||||||
|
|
||||||
public LexicalOrderGuidCreator() {
|
public GuidCreator() {
|
||||||
this.reset();
|
this.reset();
|
||||||
this.timestampStrategy = new DefaultTimestampStrategy();
|
this.timestampStrategy = new DefaultTimestampStrategy();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Return a Lexical Order GUID.
|
* Return a GUID based on the ULID specification.
|
||||||
*
|
*
|
||||||
* It has two parts:
|
* It has two parts:
|
||||||
*
|
*
|
||||||
|
@ -126,6 +129,26 @@ public class LexicalOrderGuidCreator {
|
||||||
return new UUID(msb, lsb);
|
return new UUID(msb, lsb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a ULID.
|
||||||
|
*
|
||||||
|
* @return a ULID string
|
||||||
|
*/
|
||||||
|
public synchronized String createUlid() {
|
||||||
|
UUID guid = create();
|
||||||
|
return UlidUtil.fromUuidToUlid(guid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a ULID as byte sequence.
|
||||||
|
*
|
||||||
|
* @return a byte sequence
|
||||||
|
*/
|
||||||
|
public synchronized byte[] createBytes() {
|
||||||
|
UUID guid = create();
|
||||||
|
return UlidUtil.fromUuidToBytes(guid);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the current timestamp and resets or increments the random part.
|
* Return the current timestamp and resets or increments the random part.
|
||||||
*
|
*
|
||||||
|
@ -169,7 +192,7 @@ public class LexicalOrderGuidCreator {
|
||||||
this.high = 0L;
|
this.high = 0L;
|
||||||
// Too many requests
|
// Too many requests
|
||||||
if (enableOverflowException) {
|
if (enableOverflowException) {
|
||||||
throw new LexicalOrderGuidException(OVERFLOW_MESSAGE);
|
throw new UlidCreatorException(OVERFLOW_MESSAGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,11 +202,10 @@ public class LexicalOrderGuidCreator {
|
||||||
*
|
*
|
||||||
* @param timestampStrategy
|
* @param timestampStrategy
|
||||||
* a timestamp strategy
|
* a timestamp strategy
|
||||||
* @return {@link LexicalOrderGuidCreator}
|
* @return {@link GuidCreator}
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public synchronized <T extends LexicalOrderGuidCreator> T withTimestampStrategy(
|
public synchronized <T extends GuidCreator> T withTimestampStrategy(TimestampStrategy timestampStrategy) {
|
||||||
TimestampStrategy timestampStrategy) {
|
|
||||||
this.timestampStrategy = timestampStrategy;
|
this.timestampStrategy = timestampStrategy;
|
||||||
return (T) this;
|
return (T) this;
|
||||||
}
|
}
|
||||||
|
@ -201,10 +223,10 @@ public class LexicalOrderGuidCreator {
|
||||||
*
|
*
|
||||||
* @param random
|
* @param random
|
||||||
* a random generator
|
* a random generator
|
||||||
* @return {@link LexicalOrderGuidCreator}
|
* @return {@link GuidCreator}
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public synchronized <T extends LexicalOrderGuidCreator> T withRandomGenerator(Random random) {
|
public synchronized <T extends GuidCreator> T withRandomGenerator(Random random) {
|
||||||
this.random = random;
|
this.random = random;
|
||||||
return (T) this;
|
return (T) this;
|
||||||
}
|
}
|
||||||
|
@ -218,11 +240,12 @@ public class LexicalOrderGuidCreator {
|
||||||
* See {@link Xorshift128PlusRandom} and
|
* See {@link Xorshift128PlusRandom} and
|
||||||
* {@link FingerprintUtil#getFingerprint()}
|
* {@link FingerprintUtil#getFingerprint()}
|
||||||
*
|
*
|
||||||
* @return {@link LexicalOrderGuidCreator}
|
* @return {@link GuidCreator}
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public synchronized <T extends LexicalOrderGuidCreator> T withFastRandomGenerator() {
|
public synchronized <T extends GuidCreator> T withFastRandomGenerator() {
|
||||||
this.random = new Xorshift128PlusRandom();
|
final int salt = (int) FingerprintUtil.getFingerprint();
|
||||||
|
this.random = new Xorshift128PlusRandom(salt);
|
||||||
return (T) this;
|
return (T) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,22 +255,14 @@ public class LexicalOrderGuidCreator {
|
||||||
* An exception thrown when too many requests within the same millisecond
|
* An exception thrown when too many requests within the same millisecond
|
||||||
* causes an overflow while incrementing the random bits of the GUID.
|
* causes an overflow while incrementing the random bits of the GUID.
|
||||||
*
|
*
|
||||||
* @return {@link LexicalOrderGuidCreator}
|
* @return {@link GuidCreator}
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public synchronized <T extends LexicalOrderGuidCreator> T withoutOverflowException() {
|
public synchronized <T extends GuidCreator> T withoutOverflowException() {
|
||||||
this.enableOverflowException = false;
|
this.enableOverflowException = false;
|
||||||
return (T) this;
|
return (T) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class LexicalOrderGuidException extends RuntimeException {
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
public LexicalOrderGuidException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class SecureRandomLazyHolder {
|
private static class SecureRandomLazyHolder {
|
||||||
static final Random INSTANCE = new SecureRandom();
|
static final Random INSTANCE = new SecureRandom();
|
||||||
}
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
/*
|
||||||
|
* 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.nio.charset.Charset;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
public class FingerprintUtil {
|
||||||
|
|
||||||
|
private static MessageDigest messageDigest;
|
||||||
|
|
||||||
|
private FingerprintUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns long value representing a host fingerprint.
|
||||||
|
*
|
||||||
|
* The fingerprint is calculated from a list of system properties: OS + JVM
|
||||||
|
* + network details + system resources + locale + timezone.
|
||||||
|
*
|
||||||
|
* It uses these information to generate the fingerprint: operating system
|
||||||
|
* (name, version, arch), java virtual machine (vendor, version, runtime,
|
||||||
|
* VM), network settings (IP, MAC, host name, domain name), system resources
|
||||||
|
* (CPU cores, memory), locale (language, charset) and timezone. These
|
||||||
|
* information are concatenated and passed to a SHA-256 message digest.
|
||||||
|
*
|
||||||
|
* It returns the last 8 bytes of the resulting hash as long value.
|
||||||
|
*
|
||||||
|
* Read: https://en.wikipedia.org/wiki/Device_fingerprint
|
||||||
|
*
|
||||||
|
* @return a fingerprint as long value
|
||||||
|
*/
|
||||||
|
public static long getFingerprint() {
|
||||||
|
String hash = getSystemDataHash();
|
||||||
|
return ByteUtil.toNumber(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a SHA-256 hash string generated from all the system data: OS +
|
||||||
|
* JVM + network details + system resources.
|
||||||
|
*
|
||||||
|
* @return a string
|
||||||
|
*/
|
||||||
|
protected static String getSystemDataHash() {
|
||||||
|
|
||||||
|
if (messageDigest == null) {
|
||||||
|
messageDigest = getMessageDigest();
|
||||||
|
}
|
||||||
|
|
||||||
|
String os = getOperatingSystem();
|
||||||
|
String jvm = getJavaVirtualMachine();
|
||||||
|
String net = getNetwork();
|
||||||
|
String loc = getLocalization();
|
||||||
|
String res = getResources();
|
||||||
|
String string = String.join(" ", os, jvm, net, loc, res);
|
||||||
|
|
||||||
|
byte[] bytes = string.getBytes();
|
||||||
|
byte[] hash = messageDigest.digest(bytes);
|
||||||
|
|
||||||
|
return ByteUtil.toHexadecimal(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string of the OS details.
|
||||||
|
*
|
||||||
|
* @return a string
|
||||||
|
*/
|
||||||
|
protected static String getOperatingSystem() {
|
||||||
|
String name = System.getProperty("os.name");
|
||||||
|
String version = System.getProperty("os.version");
|
||||||
|
String arch = System.getProperty("os.arch");
|
||||||
|
return String.join(" ", name, version, arch);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string of the JVM details.
|
||||||
|
*
|
||||||
|
* @return a string
|
||||||
|
*/
|
||||||
|
protected static String getJavaVirtualMachine() {
|
||||||
|
String vendor = System.getProperty("java.vendor");
|
||||||
|
String version = System.getProperty("java.version");
|
||||||
|
String rtName = System.getProperty("java.runtime.name");
|
||||||
|
String rtVersion = System.getProperty("java.runtime.version");
|
||||||
|
String vmName = System.getProperty("java.vm.name");
|
||||||
|
String vmVersion = System.getProperty("java.vm.version");
|
||||||
|
return String.join(" ", vendor, version, rtName, rtVersion, vmName, vmVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a string with locale, charset, encoding and timezone.
|
||||||
|
*
|
||||||
|
* @return a string
|
||||||
|
*/
|
||||||
|
protected static String getLocalization() {
|
||||||
|
String locale = Locale.getDefault().toString();
|
||||||
|
String charset = Charset.defaultCharset().toString();
|
||||||
|
String encoding = System.getProperty("file.encoding");
|
||||||
|
String timezone = TimeZone.getDefault().getID();
|
||||||
|
return String.join(" ", locale, charset, encoding, timezone);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string of CPU cores and maximum memory available.
|
||||||
|
*
|
||||||
|
* @return a string
|
||||||
|
*/
|
||||||
|
protected static String getResources() {
|
||||||
|
int procs = Runtime.getRuntime().availableProcessors();
|
||||||
|
long memory = Runtime.getRuntime().maxMemory();
|
||||||
|
return String.join(" ", procs + " processors", memory + " bytes");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string of the network details.
|
||||||
|
*
|
||||||
|
* It's done in three two steps:
|
||||||
|
*
|
||||||
|
* 1. it tries to find the network data associated with the host name;
|
||||||
|
*
|
||||||
|
* 2. otherwise, it iterates through all interfaces to return the first one
|
||||||
|
* that is up and running.
|
||||||
|
*
|
||||||
|
* @return a string
|
||||||
|
*/
|
||||||
|
protected static String getNetwork() {
|
||||||
|
|
||||||
|
NetworkData networkData = NetworkData.getNetworkData();
|
||||||
|
|
||||||
|
if (networkData == null) {
|
||||||
|
List<NetworkData> networkDataList = NetworkData.getNetworkDataList();
|
||||||
|
if (networkDataList != null && !networkDataList.isEmpty()) {
|
||||||
|
networkData = networkDataList.get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (networkData == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return networkData.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MessageDigest getMessageDigest() {
|
||||||
|
try {
|
||||||
|
return MessageDigest.getInstance("SHA-256");
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new InternalError("Message digest algorithm not supported.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,198 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018-2019 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.net.InetAddress;
|
||||||
|
import java.net.InterfaceAddress;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class NetworkData {
|
||||||
|
|
||||||
|
private String hostName;
|
||||||
|
private String hostCanonicalName;
|
||||||
|
private String interfaceName;
|
||||||
|
private String interfaceDisplayName;
|
||||||
|
private String interfaceHardwareAddress;
|
||||||
|
private List<String> interfaceAddresses;
|
||||||
|
|
||||||
|
public String getHostName() {
|
||||||
|
return hostName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHostName(String hostName) {
|
||||||
|
this.hostName = hostName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHostCanonicalName() {
|
||||||
|
return hostCanonicalName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHostCanonicalName(String hostCanonicalName) {
|
||||||
|
this.hostCanonicalName = hostCanonicalName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInterfaceName() {
|
||||||
|
return interfaceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInterfaceName(String interfaceName) {
|
||||||
|
this.interfaceName = interfaceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInterfaceDisplayName() {
|
||||||
|
return interfaceDisplayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInterfaceDisplayName(String interfaceDisplayName) {
|
||||||
|
this.interfaceDisplayName = interfaceDisplayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInterfaceHardwareAddress() {
|
||||||
|
return interfaceHardwareAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInterfaceHardwareAddress(String interfaceHardwareAddress) {
|
||||||
|
this.interfaceHardwareAddress = interfaceHardwareAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getInterfaceAddresses() {
|
||||||
|
return interfaceAddresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInterfaceAddresses(List<String> interfaceAddresses) {
|
||||||
|
this.interfaceAddresses = interfaceAddresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
|
||||||
|
String interfaceAddressesString = null;
|
||||||
|
if (this.interfaceAddresses != null) {
|
||||||
|
interfaceAddressesString = String.join(" ", this.interfaceAddresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
return String.join(" ", this.interfaceHardwareAddress, this.hostName, this.hostCanonicalName,
|
||||||
|
this.interfaceName, this.interfaceDisplayName, interfaceAddressesString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link NetworkData}.
|
||||||
|
*
|
||||||
|
* This method returns the network data associated to the host name.
|
||||||
|
*
|
||||||
|
* @return a {@link NetworkData}
|
||||||
|
*/
|
||||||
|
public static NetworkData getNetworkData() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
InetAddress inetAddress = InetAddress.getLocalHost();
|
||||||
|
NetworkInterface networkInterface = NetworkInterface.getByInetAddress(inetAddress);
|
||||||
|
return buildNetworkData(networkInterface, inetAddress);
|
||||||
|
} catch (UnknownHostException | SocketException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of {@link NetworkData}.
|
||||||
|
*
|
||||||
|
* This method iterates over all the network interfaces to return those that
|
||||||
|
* are up and running.
|
||||||
|
*
|
||||||
|
* NOTE: it may be VERY EXPENSIVE on Windows systems, because that OS
|
||||||
|
* creates a lot of virtual network interfaces.
|
||||||
|
*
|
||||||
|
* @return a list of {@link NetworkData}
|
||||||
|
*/
|
||||||
|
public static List<NetworkData> getNetworkDataList() {
|
||||||
|
try {
|
||||||
|
InetAddress inetAddress = InetAddress.getLocalHost();
|
||||||
|
List<NetworkInterface> networkInterfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
|
||||||
|
|
||||||
|
HashSet<NetworkData> networkDataHashSet = new HashSet<>();
|
||||||
|
for (NetworkInterface networkInterface : networkInterfaces) {
|
||||||
|
NetworkData networkData = buildNetworkData(networkInterface, inetAddress);
|
||||||
|
if (networkData != null) {
|
||||||
|
networkDataHashSet.add(networkData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ArrayList<>(networkDataHashSet);
|
||||||
|
} catch (SocketException | NullPointerException | UnknownHostException e) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NetworkData buildNetworkData(NetworkInterface networkInterface, InetAddress inetAddress)
|
||||||
|
throws SocketException {
|
||||||
|
if (isPhysicalNetworkInterface(networkInterface)) {
|
||||||
|
|
||||||
|
String hostName = inetAddress != null ? inetAddress.getHostName() : null;
|
||||||
|
String hostCanonicalName = inetAddress != null ? inetAddress.getCanonicalHostName() : null;
|
||||||
|
String interfaceName = networkInterface.getName();
|
||||||
|
String interfaceDisplayName = networkInterface.getDisplayName();
|
||||||
|
String interfaceHardwareAddress = ByteUtil.toHexadecimal(networkInterface.getHardwareAddress());
|
||||||
|
List<String> interfaceAddresses = getInterfaceAddresses(networkInterface);
|
||||||
|
|
||||||
|
NetworkData networkData = new NetworkData();
|
||||||
|
networkData.setHostName(hostName);
|
||||||
|
networkData.setHostCanonicalName(hostCanonicalName);
|
||||||
|
networkData.setInterfaceName(interfaceName);
|
||||||
|
networkData.setInterfaceDisplayName(interfaceDisplayName);
|
||||||
|
networkData.setInterfaceHardwareAddress(interfaceHardwareAddress);
|
||||||
|
networkData.setInterfaceAddresses(interfaceAddresses);
|
||||||
|
|
||||||
|
return networkData;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPhysicalNetworkInterface(NetworkInterface networkInterface) {
|
||||||
|
try {
|
||||||
|
return networkInterface != null && networkInterface.isUp()
|
||||||
|
&& !(networkInterface.isLoopback() || networkInterface.isVirtual());
|
||||||
|
} catch (SocketException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> getInterfaceAddresses(NetworkInterface networkInterface) {
|
||||||
|
HashSet<String> addresses = new HashSet<>();
|
||||||
|
List<InterfaceAddress> interfaceAddresses = networkInterface.getInterfaceAddresses();
|
||||||
|
if (interfaceAddresses != null && !interfaceAddresses.isEmpty()) {
|
||||||
|
for (InterfaceAddress addr : interfaceAddresses) {
|
||||||
|
if (addr.getAddress() != null) {
|
||||||
|
addresses.add(addr.getAddress().getHostAddress());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ArrayList<>(addresses);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.Suite;
|
import org.junit.runners.Suite;
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.UlidCreatorTest;
|
import com.github.f4b6a3.ulid.UlidCreatorTest;
|
||||||
import com.github.f4b6a3.ulid.factory.LexicalOrderGuidCreatorTest;
|
import com.github.f4b6a3.ulid.guid.GuidCreatorTest;
|
||||||
import com.github.f4b6a3.ulid.random.NaiveRandomTest;
|
import com.github.f4b6a3.ulid.random.NaiveRandomTest;
|
||||||
import com.github.f4b6a3.ulid.timestamp.DefaultTimestampStrategyTest;
|
import com.github.f4b6a3.ulid.timestamp.DefaultTimestampStrategyTest;
|
||||||
import com.github.f4b6a3.ulid.util.Base32UtilTest;
|
import com.github.f4b6a3.ulid.util.Base32UtilTest;
|
||||||
|
@ -16,11 +16,18 @@ import com.github.f4b6a3.ulid.util.UlidUtilTest;
|
||||||
DefaultTimestampStrategyTest.class,
|
DefaultTimestampStrategyTest.class,
|
||||||
ByteUtilTest.class,
|
ByteUtilTest.class,
|
||||||
NaiveRandomTest.class,
|
NaiveRandomTest.class,
|
||||||
LexicalOrderGuidCreatorTest.class,
|
GuidCreatorTest.class,
|
||||||
Base32UtilTest.class,
|
Base32UtilTest.class,
|
||||||
UlidUtilTest.class,
|
UlidUtilTest.class,
|
||||||
UlidCreatorTest.class,
|
UlidCreatorTest.class,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* It bundles all JUnit test cases.
|
||||||
|
*
|
||||||
|
* Also see {@link UniquenesTest}.
|
||||||
|
*
|
||||||
|
*/
|
||||||
public class TestSuite {
|
public class TestSuite {
|
||||||
}
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
package com.github.f4b6a3;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.github.f4b6a3.ulid.UlidCreator;
|
||||||
|
import com.github.f4b6a3.ulid.exception.UlidCreatorException;
|
||||||
|
import com.github.f4b6a3.ulid.guid.GuidCreator;
|
||||||
|
import com.github.f4b6a3.ulid.timestamp.FixedTimestampStretegy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* This test starts many threads that keep requesting thousands of ULIDs to a
|
||||||
|
* single generator.
|
||||||
|
*
|
||||||
|
* This is is not included in the {@link TestSuite} because it takes a long time
|
||||||
|
* to finish.
|
||||||
|
*/
|
||||||
|
public class UniquenessTest {
|
||||||
|
|
||||||
|
private int threadCount; // Number of threads to run
|
||||||
|
private int requestCount; // Number of requests for thread
|
||||||
|
|
||||||
|
// private long[][] cacheLong; // Store values generated per thread
|
||||||
|
private HashSet<UUID> hashSet;
|
||||||
|
|
||||||
|
private boolean verbose; // Show progress or not
|
||||||
|
|
||||||
|
// GUID creator based on ULID spec
|
||||||
|
private GuidCreator creator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the test.
|
||||||
|
*
|
||||||
|
* @param threadCount
|
||||||
|
* @param requestCount
|
||||||
|
* @param creator
|
||||||
|
*/
|
||||||
|
public UniquenessTest(int threadCount, int requestCount, GuidCreator creator, boolean progress) {
|
||||||
|
this.threadCount = threadCount;
|
||||||
|
this.requestCount = requestCount;
|
||||||
|
this.creator = creator;
|
||||||
|
this.verbose = progress;
|
||||||
|
this.initCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initCache() {
|
||||||
|
this.hashSet = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize and start the threads.
|
||||||
|
*/
|
||||||
|
public void start() {
|
||||||
|
|
||||||
|
Thread[] threads = new Thread[this.threadCount];
|
||||||
|
|
||||||
|
// Instantiate and start many threads
|
||||||
|
for (int i = 0; i < this.threadCount; i++) {
|
||||||
|
threads[i] = new Thread(new UniquenessTestThread(i, verbose));
|
||||||
|
threads[i].start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait all the threads to finish
|
||||||
|
for (Thread thread : threads) {
|
||||||
|
try {
|
||||||
|
thread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UniquenessTestThread implements Runnable {
|
||||||
|
|
||||||
|
private int id;
|
||||||
|
private boolean verbose;
|
||||||
|
|
||||||
|
public UniquenessTestThread(int id, boolean verbose) {
|
||||||
|
this.id = id;
|
||||||
|
this.verbose = verbose;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the test.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
double progress = 0;
|
||||||
|
int max = requestCount;
|
||||||
|
|
||||||
|
for (int i = 0; i < max; i++) {
|
||||||
|
|
||||||
|
// Request a UUID
|
||||||
|
UUID uuid = null;
|
||||||
|
try {
|
||||||
|
uuid = creator.create();
|
||||||
|
} catch (UlidCreatorException e) {
|
||||||
|
// Ignore the overrun exception and try again
|
||||||
|
uuid = creator.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
// Calculate and show progress
|
||||||
|
progress = (i * 1.0 / max) * 100;
|
||||||
|
if (progress % 1 == 0) {
|
||||||
|
System.out.println(String.format("[Thread %06d] %s %s %s%%", id, uuid, i, (int) progress));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
synchronized (hashSet) {
|
||||||
|
// Insert the value in cache, if it does not exist in it.
|
||||||
|
if (!hashSet.add(uuid)) {
|
||||||
|
throw new UlidCreatorException(
|
||||||
|
String.format("[DUPLICATE][Thread %s] %s %s %s%%", id, uuid, i, (int) progress));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
// Finished
|
||||||
|
System.out.println(String.format("[Thread %s] Done.", id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void execute(boolean verbose, int threadCount, int requestCount) {
|
||||||
|
GuidCreator creator = UlidCreator.getGuidCreator()
|
||||||
|
.withTimestampStrategy(new FixedTimestampStretegy(System.currentTimeMillis()));
|
||||||
|
|
||||||
|
UniquenessTest test = new UniquenessTest(threadCount, requestCount, creator, verbose);
|
||||||
|
test.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
boolean verbose = true;
|
||||||
|
int threadCount = 16; // Number of threads to run
|
||||||
|
int requestCount = 1_000_000; // Number of requests for thread
|
||||||
|
execute(verbose, threadCount, requestCount);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package com.github.f4b6a3.demo;
|
||||||
|
|
||||||
|
import com.github.f4b6a3.ulid.UlidCreator;
|
||||||
|
|
||||||
|
public class DemoTest {
|
||||||
|
|
||||||
|
private static final String HORIZONTAL_LINE = "----------------------------------------";
|
||||||
|
|
||||||
|
public static void printList() {
|
||||||
|
int max = 1_000;
|
||||||
|
|
||||||
|
System.out.println(HORIZONTAL_LINE);
|
||||||
|
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("### GUID");
|
||||||
|
System.out.println(HORIZONTAL_LINE);
|
||||||
|
|
||||||
|
for (int i = 0; i < max; i++) {
|
||||||
|
System.out.println(UlidCreator.getGuid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
printList();
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ import java.util.HashSet;
|
||||||
public class UlidCreatorTest {
|
public class UlidCreatorTest {
|
||||||
|
|
||||||
private static final int ULID_LENGTH = 26;
|
private static final int ULID_LENGTH = 26;
|
||||||
private static final int DEFAULT_LOOP_MAX = 10_000;
|
private static final int DEFAULT_LOOP_MAX = 100_000;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetUlid() {
|
public void testGetUlid() {
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
package com.github.f4b6a3.ulid.factory;
|
|
||||||
|
|
||||||
class LexicalOrderGuidCreatorMock extends LexicalOrderGuidCreator {
|
|
||||||
public LexicalOrderGuidCreatorMock(long low, long high, long previousTimestamp) {
|
|
||||||
super();
|
|
||||||
this.low = low;
|
|
||||||
this.high = high;
|
|
||||||
this.previousTimestamp = previousTimestamp;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.github.f4b6a3.ulid.guid;
|
||||||
|
|
||||||
|
import com.github.f4b6a3.ulid.guid.GuidCreator;
|
||||||
|
|
||||||
|
class GuidCreatorMock extends GuidCreator {
|
||||||
|
public GuidCreatorMock(long low, long high, long previousTimestamp) {
|
||||||
|
super();
|
||||||
|
this.low = low;
|
||||||
|
this.high = high;
|
||||||
|
this.previousTimestamp = previousTimestamp;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package com.github.f4b6a3.ulid.factory;
|
package com.github.f4b6a3.ulid.guid;
|
||||||
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
@ -6,19 +6,19 @@ import java.util.UUID;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.exception.UlidCreatorException;
|
import com.github.f4b6a3.ulid.exception.UlidCreatorException;
|
||||||
import com.github.f4b6a3.ulid.factory.LexicalOrderGuidCreator.LexicalOrderGuidException;
|
import com.github.f4b6a3.ulid.guid.GuidCreator;
|
||||||
import com.github.f4b6a3.ulid.random.Xorshift128PlusRandom;
|
import com.github.f4b6a3.ulid.random.Xorshift128PlusRandom;
|
||||||
import com.github.f4b6a3.ulid.timestamp.FixedTimestampStretegy;
|
import com.github.f4b6a3.ulid.timestamp.FixedTimestampStretegy;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class LexicalOrderGuidCreatorTest {
|
public class GuidCreatorTest {
|
||||||
|
|
||||||
private static final long DEFAULT_LOOP = 1000;
|
private static final long DEFAULT_LOOP_MAX = 100_000;
|
||||||
|
|
||||||
private static final long TIMESTAMP = System.currentTimeMillis();
|
private static final long TIMESTAMP = System.currentTimeMillis();
|
||||||
private static final long MAX_LOW = LexicalOrderGuidCreator.MAX_LOW;
|
private static final long MAX_LOW = GuidCreator.MAX_LOW;
|
||||||
private static final long MAX_HIGH = LexicalOrderGuidCreator.MAX_HIGH;
|
private static final long MAX_HIGH = GuidCreator.MAX_HIGH;
|
||||||
|
|
||||||
private static final Random RANDOM = new Xorshift128PlusRandom();
|
private static final Random RANDOM = new Xorshift128PlusRandom();
|
||||||
|
|
||||||
|
@ -28,13 +28,13 @@ public class LexicalOrderGuidCreatorTest {
|
||||||
long low = RANDOM.nextInt();
|
long low = RANDOM.nextInt();
|
||||||
long high = RANDOM.nextInt(Short.MAX_VALUE);
|
long high = RANDOM.nextInt(Short.MAX_VALUE);
|
||||||
|
|
||||||
LexicalOrderGuidCreatorMock creator = new LexicalOrderGuidCreatorMock(low, high, TIMESTAMP);
|
GuidCreatorMock creator = new GuidCreatorMock(low, high, TIMESTAMP);
|
||||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||||
|
|
||||||
UUID uuid = creator.create();
|
UUID uuid = creator.create();
|
||||||
long firstMsb = (short) uuid.getMostSignificantBits();
|
long firstMsb = (short) uuid.getMostSignificantBits();
|
||||||
long lastMsb = 0;
|
long lastMsb = 0;
|
||||||
for (int i = 0; i <= DEFAULT_LOOP; i++) {
|
for (int i = 0; i <= DEFAULT_LOOP_MAX; i++) {
|
||||||
uuid = creator.create();
|
uuid = creator.create();
|
||||||
lastMsb = (short) uuid.getMostSignificantBits();
|
lastMsb = (short) uuid.getMostSignificantBits();
|
||||||
}
|
}
|
||||||
|
@ -50,21 +50,21 @@ public class LexicalOrderGuidCreatorTest {
|
||||||
@Test
|
@Test
|
||||||
public void testRandomLeastSignificantBits() {
|
public void testRandomLeastSignificantBits() {
|
||||||
|
|
||||||
LexicalOrderGuidCreator creator = new LexicalOrderGuidCreator();
|
GuidCreator creator = new GuidCreator();
|
||||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||||
|
|
||||||
UUID uuid = creator.create();
|
UUID uuid = creator.create();
|
||||||
long firstLsb = uuid.getLeastSignificantBits();
|
long firstLsb = uuid.getLeastSignificantBits();
|
||||||
long lastLsb = 0;
|
long lastLsb = 0;
|
||||||
for (int i = 0; i < DEFAULT_LOOP; i++) {
|
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||||
uuid = creator.create();
|
uuid = creator.create();
|
||||||
lastLsb = uuid.getLeastSignificantBits();
|
lastLsb = uuid.getLeastSignificantBits();
|
||||||
}
|
}
|
||||||
|
|
||||||
long expected = firstLsb + DEFAULT_LOOP;
|
long expected = firstLsb + DEFAULT_LOOP_MAX;
|
||||||
assertEquals(String.format("The last LSB should be iqual to %s.", expected), expected, lastLsb);
|
assertEquals(String.format("The last LSB should be iqual to %s.", expected), expected, lastLsb);
|
||||||
|
|
||||||
long notExpected = firstLsb + DEFAULT_LOOP + 1;
|
long notExpected = firstLsb + DEFAULT_LOOP_MAX + 1;
|
||||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP + 1));
|
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP + 1));
|
||||||
uuid = creator.create();
|
uuid = creator.create();
|
||||||
lastLsb = uuid.getLeastSignificantBits();
|
lastLsb = uuid.getLeastSignificantBits();
|
||||||
|
@ -77,15 +77,15 @@ public class LexicalOrderGuidCreatorTest {
|
||||||
long low = RANDOM.nextInt();
|
long low = RANDOM.nextInt();
|
||||||
long high = RANDOM.nextInt(Short.MAX_VALUE);
|
long high = RANDOM.nextInt(Short.MAX_VALUE);
|
||||||
|
|
||||||
LexicalOrderGuidCreatorMock creator = new LexicalOrderGuidCreatorMock(low, high, TIMESTAMP);
|
GuidCreatorMock creator = new GuidCreatorMock(low, high, TIMESTAMP);
|
||||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||||
|
|
||||||
UUID uuid = new UUID(0, 0);
|
UUID uuid = new UUID(0, 0);
|
||||||
for (int i = 0; i < DEFAULT_LOOP; i++) {
|
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||||
uuid = creator.create();
|
uuid = creator.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
long expected = low + DEFAULT_LOOP;
|
long expected = low + DEFAULT_LOOP_MAX;
|
||||||
long randomLsb = uuid.getLeastSignificantBits();
|
long randomLsb = uuid.getLeastSignificantBits();
|
||||||
assertEquals(String.format("The LSB should be iqual to %s.", expected), expected, randomLsb);
|
assertEquals(String.format("The LSB should be iqual to %s.", expected), expected, randomLsb);
|
||||||
}
|
}
|
||||||
|
@ -96,11 +96,11 @@ public class LexicalOrderGuidCreatorTest {
|
||||||
long low = MAX_LOW;
|
long low = MAX_LOW;
|
||||||
long high = RANDOM.nextInt(Short.MAX_VALUE);
|
long high = RANDOM.nextInt(Short.MAX_VALUE);
|
||||||
|
|
||||||
LexicalOrderGuidCreatorMock creator = new LexicalOrderGuidCreatorMock(low, high, TIMESTAMP);
|
GuidCreatorMock creator = new GuidCreatorMock(low, high, TIMESTAMP);
|
||||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||||
|
|
||||||
UUID uuid = new UUID(0, 0);
|
UUID uuid = new UUID(0, 0);
|
||||||
for (int i = 0; i < DEFAULT_LOOP; i++) {
|
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||||
uuid = creator.create();
|
uuid = creator.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,17 +109,17 @@ public class LexicalOrderGuidCreatorTest {
|
||||||
assertEquals(String.format("The MSB should be iqual to %s.", expected), expected, randomMsb);
|
assertEquals(String.format("The MSB should be iqual to %s.", expected), expected, randomMsb);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = LexicalOrderGuidException.class)
|
@Test(expected = UlidCreatorException.class)
|
||||||
public void testShouldThrowOverflowException() {
|
public void testShouldThrowOverflowException() {
|
||||||
|
|
||||||
long low = MAX_LOW - DEFAULT_LOOP;
|
long low = MAX_LOW - DEFAULT_LOOP_MAX;
|
||||||
long high = MAX_HIGH;
|
long high = MAX_HIGH;
|
||||||
|
|
||||||
LexicalOrderGuidCreatorMock creator = new LexicalOrderGuidCreatorMock(low, high, TIMESTAMP);
|
GuidCreatorMock creator = new GuidCreatorMock(low, high, TIMESTAMP);
|
||||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||||
|
|
||||||
UUID uuid = new UUID(0, 0);
|
UUID uuid = new UUID(0, 0);
|
||||||
for (int i = 0; i < DEFAULT_LOOP; i++) {
|
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||||
uuid = creator.create();
|
uuid = creator.create();
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class NaiveRandomTest {
|
public class NaiveRandomTest {
|
||||||
|
|
||||||
private static final int DEFAULT_LOOP_LIMIT = 10_000;
|
private static final int DEFAULT_LOOP_LIMIT = 100_000;
|
||||||
private static final String EXPECTED_BIT_COUNT_RANDOM_LONG = "The average bit count expected for random long values is 32";
|
private static final String EXPECTED_BIT_COUNT_RANDOM_LONG = "The average bit count expected for random long values is 32";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -7,7 +7,7 @@ import java.util.UUID;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.github.f4b6a3.ulid.util.UlidUtil.UlidUtilException;
|
import com.github.f4b6a3.ulid.util.UlidUtil.UlidUtilException;
|
||||||
import com.github.f4b6a3.ulid.util.ByteUtil;
|
import com.github.f4b6a3.ulid.UlidCreator;
|
||||||
|
|
||||||
public class UlidUtilTest {
|
public class UlidUtilTest {
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ public class UlidUtilTest {
|
||||||
private static final long TIMESTAMP_MAX = 281474976710655l; // 2^48 - 1
|
private static final long TIMESTAMP_MAX = 281474976710655l; // 2^48 - 1
|
||||||
|
|
||||||
private static final int ULID_LENGTH = 26;
|
private static final int ULID_LENGTH = 26;
|
||||||
private static final int DEFAULT_LOOP_MAX = 10_000;
|
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",
|
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" };
|
"2001-09-09T01:46:40.456Z", "2020-01-15T14:30:33.789Z", "2038-01-19T03:14:07.321Z" };
|
||||||
|
@ -177,28 +177,25 @@ public class UlidUtilTest {
|
||||||
assertFalse("ULID with timestamp greater than (2^48)-1 should be invalid.", UlidUtil.isValid(ulid));
|
assertFalse("ULID with timestamp greater than (2^48)-1 should be invalid.", UlidUtil.isValid(ulid));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
@Test
|
||||||
// @Test
|
public void testToAndFromUlid() {
|
||||||
// public void testToAndFromUlid() {
|
|
||||||
//
|
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||||
// for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
|
||||||
//
|
UUID uuid = UlidCreator.getFastGuid();
|
||||||
// // Use random values
|
String ulid = UlidUtil.fromUuidToUlid(uuid);
|
||||||
// UUID uuid = UuidCreator.getFastRandom();
|
|
||||||
// String ulid = UlidUtil.fromUuidToUlid(uuid);
|
assertTrue("ULID is null", ulid != null);
|
||||||
//
|
assertTrue("ULID is empty", !ulid.isEmpty());
|
||||||
// assertTrue("ULID is null", ulid != null);
|
assertTrue("ULID length is wrong ", ulid.length() == ULID_LENGTH);
|
||||||
// assertTrue("ULID is empty", !ulid.isEmpty());
|
assertTrue("ULID is not valid", UlidUtil.isValid(ulid, /* strict */
|
||||||
// assertTrue("ULID length is wrong ", ulid.length() == ULID_LENGTH);
|
true));
|
||||||
// assertTrue("ULID is not valid", UlidUtil.isValid(ulid, /* strict */
|
|
||||||
// true));
|
UUID result = UlidUtil.fromUlidToUuid(ulid);
|
||||||
//
|
assertEquals("Result ULID is different from original ULID", uuid, result);
|
||||||
// UUID result = UlidUtil.fromUlidToUuid(ulid);
|
|
||||||
// assertEquals("Result ULID is different from original ULID", uuid,
|
}
|
||||||
// result);
|
}
|
||||||
//
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
private String leftPad(String unpadded) {
|
private String leftPad(String unpadded) {
|
||||||
return "0000000000".substring(unpadded.length()) + unpadded;
|
return "0000000000".substring(unpadded.length()) + unpadded;
|
||||||
|
|
Loading…
Reference in New Issue