Refactored GuidCreator.java
This commit is contained in:
parent
4590f46220
commit
5415552032
2
pom.xml
2
pom.xml
|
@ -26,7 +26,7 @@
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<jdk.version>1.7</jdk.version>
|
<jdk.version>8</jdk.version>
|
||||||
<maven.compiler.source>${jdk.version}</maven.compiler.source>
|
<maven.compiler.source>${jdk.version}</maven.compiler.source>
|
||||||
<maven.compiler.target>${jdk.version}</maven.compiler.target>
|
<maven.compiler.target>${jdk.version}</maven.compiler.target>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
|
@ -44,18 +44,19 @@ import com.github.f4b6a3.ulid.timestamp.DefaultTimestampStrategy;
|
||||||
*/
|
*/
|
||||||
public class GuidCreator {
|
public class GuidCreator {
|
||||||
|
|
||||||
protected long low;
|
protected long randomMsb = 0;
|
||||||
protected long high;
|
protected long randomLsb = 0;
|
||||||
|
|
||||||
protected long firstLow;
|
protected long randomLsbMax;
|
||||||
protected long firstHigh;
|
protected long randomMsbMax;
|
||||||
|
|
||||||
|
protected static final long HALF_RANDOM_COMPONENT = 0x000000ffffffffffL;
|
||||||
|
protected static final long INCREMENT_MAX = 0x0000010000000000L;
|
||||||
|
|
||||||
protected long previousTimestamp;
|
protected long previousTimestamp;
|
||||||
|
|
||||||
protected Random random;
|
protected Random random;
|
||||||
|
|
||||||
protected static final long MASK_UNSIGNED_SHORT = 0x000000000000ffffL; // 2^16
|
|
||||||
|
|
||||||
protected static final String OVERRUN_MESSAGE = "The system overran the generator by requesting too many GUIDs.";
|
protected static final String OVERRUN_MESSAGE = "The system overran the generator by requesting too many GUIDs.";
|
||||||
|
|
||||||
protected TimestampStrategy timestampStrategy;
|
protected TimestampStrategy timestampStrategy;
|
||||||
|
@ -126,8 +127,11 @@ public class GuidCreator {
|
||||||
|
|
||||||
final long timestamp = this.getTimestamp();
|
final long timestamp = this.getTimestamp();
|
||||||
|
|
||||||
final long msb = (timestamp << 16) | high;
|
final long randomHi = truncate(randomMsb);
|
||||||
final long lsb = low;
|
final long randomLo = truncate(randomLsb);
|
||||||
|
|
||||||
|
final long msb = (timestamp << 16) | (randomHi >>> 24);
|
||||||
|
final long lsb = (randomHi << 40) | randomLo;
|
||||||
|
|
||||||
return new UUID(msb, lsb);
|
return new UUID(msb, lsb);
|
||||||
}
|
}
|
||||||
|
@ -178,29 +182,29 @@ public class GuidCreator {
|
||||||
|
|
||||||
// Get random values
|
// Get random values
|
||||||
if (random == null) {
|
if (random == null) {
|
||||||
this.low = SecureRandomLazyHolder.INSTANCE.nextLong();
|
this.randomMsb = truncate(SecureRandomLazyHolder.INSTANCE.nextLong());
|
||||||
this.high = SecureRandomLazyHolder.INSTANCE.nextInt() & MASK_UNSIGNED_SHORT;
|
this.randomLsb = truncate(SecureRandomLazyHolder.INSTANCE.nextLong());
|
||||||
} else {
|
} else {
|
||||||
this.low = random.nextLong();
|
this.randomMsb = truncate(random.nextLong());
|
||||||
this.high = random.nextInt() & MASK_UNSIGNED_SHORT;
|
this.randomLsb = truncate(random.nextLong());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the random values
|
// Save the random values
|
||||||
this.firstLow = this.low;
|
this.randomMsbMax = this.randomMsb | INCREMENT_MAX;
|
||||||
this.firstHigh = this.high;
|
this.randomLsbMax = this.randomLsb | INCREMENT_MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increment the random part of the GUID.
|
* 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.
|
||||||
* in the same millisecond.
|
|
||||||
*
|
*
|
||||||
* @throws UlidCreatorException
|
* @throws UlidCreatorException
|
||||||
* if an overrun happens.
|
* if an overrun happens.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
protected synchronized void increment() {
|
protected synchronized void increment() {
|
||||||
if ((++this.low == this.firstLow) && (++this.high == this.firstHigh)) {
|
if ((++this.randomLsb == this.randomLsbMax) && (++this.randomMsb == this.randomMsbMax)) {
|
||||||
this.reset();
|
this.reset();
|
||||||
throw new UlidCreatorException(OVERRUN_MESSAGE);
|
throw new UlidCreatorException(OVERRUN_MESSAGE);
|
||||||
}
|
}
|
||||||
|
@ -258,6 +262,31 @@ public class GuidCreator {
|
||||||
return (T) this;
|
return (T) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncate long to half random component.
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* a value to be truncated.
|
||||||
|
* @return truncated value
|
||||||
|
*/
|
||||||
|
protected synchronized long truncate(final long value) {
|
||||||
|
return (value & HALF_RANDOM_COMPONENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For unit tests
|
||||||
|
*/
|
||||||
|
protected long extractRandomLsb(UUID uuid) {
|
||||||
|
return uuid.getLeastSignificantBits() & HALF_RANDOM_COMPONENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For unit tests
|
||||||
|
*/
|
||||||
|
protected long extractRandomMsb(UUID uuid) {
|
||||||
|
return ((uuid.getMostSignificantBits() & 0xffff) << 24) | (uuid.getLeastSignificantBits() >>> 40);
|
||||||
|
}
|
||||||
|
|
||||||
private static class SecureRandomLazyHolder {
|
private static class SecureRandomLazyHolder {
|
||||||
static final Random INSTANCE = new SecureRandom();
|
static final Random INSTANCE = new SecureRandom();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,21 +2,35 @@ package com.github.f4b6a3.ulid.guid;
|
||||||
|
|
||||||
class GuidCreatorMock extends GuidCreator {
|
class GuidCreatorMock extends GuidCreator {
|
||||||
|
|
||||||
public GuidCreatorMock(long low, long high, long previousTimestamp) {
|
public GuidCreatorMock(long previousTimestamp) {
|
||||||
this(low, high, low, high, previousTimestamp);
|
super();
|
||||||
|
this.previousTimestamp = previousTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GuidCreatorMock(long low, long high, long firstLow, long firstHigh, long previousTimestamp) {
|
public GuidCreatorMock(long randomMsb, long randomLsb, long randomMsbMax, long randomLsbMax, long previousTimestamp) {
|
||||||
super();
|
|
||||||
|
|
||||||
// Set initial values
|
this.randomMsb = randomMsb;
|
||||||
this.low = low;
|
this.randomLsb = randomLsb;
|
||||||
this.high = high;
|
|
||||||
|
|
||||||
// Save the initial values
|
this.randomMsbMax = randomMsbMax;
|
||||||
this.firstLow = firstLow;
|
this.randomLsbMax = randomLsbMax;
|
||||||
this.firstHigh = firstHigh;
|
|
||||||
|
|
||||||
this.previousTimestamp = previousTimestamp;
|
this.previousTimestamp = previousTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getRandomMsb() {
|
||||||
|
return this.randomMsb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRandomLsb() {
|
||||||
|
return this.randomLsb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRandomHiMax() {
|
||||||
|
return this.randomMsb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRandomLoMax() {
|
||||||
|
return this.randomLsb;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -13,7 +13,7 @@ import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class GuidCreatorTest {
|
public class GuidCreatorTest {
|
||||||
|
|
||||||
private static final long DEFAULT_LOOP_MAX = 1_048_576; // 2^20
|
private static final long DEFAULT_LOOP_MAX = 1_000_000;
|
||||||
|
|
||||||
private static final long TIMESTAMP = System.currentTimeMillis();
|
private static final long TIMESTAMP = System.currentTimeMillis();
|
||||||
|
|
||||||
|
@ -22,42 +22,39 @@ public class GuidCreatorTest {
|
||||||
@Test
|
@Test
|
||||||
public void testRandomMostSignificantBits() {
|
public void testRandomMostSignificantBits() {
|
||||||
|
|
||||||
long low = RANDOM.nextLong();
|
GuidCreatorMock creator = new GuidCreatorMock(TIMESTAMP);
|
||||||
long high = (short) (RANDOM.nextInt());
|
|
||||||
|
|
||||||
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 = creator.extractRandomMsb(uuid);
|
||||||
long lastMsb = 0;
|
|
||||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||||
uuid = creator.create();
|
uuid = creator.create();
|
||||||
lastMsb = (short) uuid.getMostSignificantBits();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(String.format("The last MSB should be iqual to the first %s.", firstMsb), firstMsb, lastMsb);
|
long lastMsb = creator.extractRandomMsb(uuid);
|
||||||
|
long expectedMsb = firstMsb;
|
||||||
|
assertEquals(String.format("The last MSB should be iqual to the first %s.", expectedMsb), expectedMsb, lastMsb);
|
||||||
|
|
||||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP + 1));
|
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP + 1));
|
||||||
uuid = creator.create();
|
uuid = creator.create();
|
||||||
lastMsb = (short) uuid.getMostSignificantBits();
|
lastMsb = uuid.getMostSignificantBits();
|
||||||
assertNotEquals("The last MSB should be be random after timestamp changed.", firstMsb, lastMsb);
|
assertNotEquals("The last MSB should be random after timestamp changed.", firstMsb, lastMsb);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRandomLeastSignificantBits() {
|
public void testRandomLeastSignificantBits() {
|
||||||
|
|
||||||
GuidCreator creator = new GuidCreator();
|
GuidCreatorMock creator = new GuidCreatorMock(TIMESTAMP);
|
||||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||||
|
|
||||||
UUID uuid = creator.create();
|
UUID uuid = creator.create();
|
||||||
long firstLsb = uuid.getLeastSignificantBits();
|
long firstLsb = creator.extractRandomLsb(uuid);
|
||||||
long lastLsb = 0;
|
|
||||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||||
uuid = creator.create();
|
uuid = creator.create();
|
||||||
lastLsb = uuid.getLeastSignificantBits();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long lastLsb = creator.extractRandomLsb(uuid);
|
||||||
long expected = firstLsb + DEFAULT_LOOP_MAX;
|
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);
|
||||||
|
|
||||||
|
@ -71,51 +68,81 @@ public class GuidCreatorTest {
|
||||||
@Test
|
@Test
|
||||||
public void testIncrementOfRandomLeastSignificantBits() {
|
public void testIncrementOfRandomLeastSignificantBits() {
|
||||||
|
|
||||||
long low = RANDOM.nextLong();
|
GuidCreatorMock creator = new GuidCreatorMock(TIMESTAMP);
|
||||||
long high = (short) RANDOM.nextInt();
|
|
||||||
|
|
||||||
GuidCreatorMock creator = new GuidCreatorMock(low, high, TIMESTAMP);
|
|
||||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||||
|
|
||||||
|
long lsb = creator.getRandomLsb();
|
||||||
|
|
||||||
UUID uuid = new UUID(0, 0);
|
UUID uuid = new UUID(0, 0);
|
||||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||||
uuid = creator.create();
|
uuid = creator.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
long expectedLsb = low + DEFAULT_LOOP_MAX;
|
long expectedLsb = lsb + DEFAULT_LOOP_MAX;
|
||||||
long randomLsb = uuid.getLeastSignificantBits();
|
long randomLsb = creator.getRandomLsb();
|
||||||
assertEquals(String.format("The LSB should be iqual to %s.", expectedLsb), expectedLsb, randomLsb);
|
assertEquals("Wrong LSB after loop.", expectedLsb, randomLsb);
|
||||||
|
|
||||||
|
randomLsb = creator.extractRandomLsb(uuid);
|
||||||
|
assertEquals("Wrong LSB after loop.", expectedLsb, randomLsb);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIncrementOfRandomMostSignificantBits() {
|
public void testIncrementOfRandomMostSignificantBits() {
|
||||||
|
|
||||||
long low = RANDOM.nextLong();
|
GuidCreatorMock creator = new GuidCreatorMock(TIMESTAMP);
|
||||||
long high = (short) (RANDOM.nextInt());
|
|
||||||
|
|
||||||
GuidCreatorMock creator = new GuidCreatorMock(low, high, TIMESTAMP);
|
|
||||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||||
|
|
||||||
|
long msb = creator.getRandomMsb();
|
||||||
|
|
||||||
UUID uuid = new UUID(0, 0);
|
UUID uuid = new UUID(0, 0);
|
||||||
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
|
||||||
uuid = creator.create();
|
uuid = creator.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
long expected = high;
|
long expectedMsb = msb;
|
||||||
long randomMsb = (short) (uuid.getMostSignificantBits());
|
long randomMsb = creator.getRandomMsb();
|
||||||
assertEquals(String.format("The MSB should be iqual to %s.", expected), expected, randomMsb);
|
assertEquals("Wrong MSB after loop.", expectedMsb, randomMsb);
|
||||||
|
|
||||||
|
randomMsb = creator.extractRandomMsb(uuid);
|
||||||
|
assertEquals("Wrong MSB after loop.", expectedMsb, randomMsb);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testShouldThrowOverflowException() {
|
public void testShouldThrowOverflowException1() {
|
||||||
|
|
||||||
long startLow = RANDOM.nextInt() + DEFAULT_LOOP_MAX;
|
long msbMax = 0x000001ffffffffffL;
|
||||||
long startHigh = (short) (RANDOM.nextInt() + 1);
|
long lsbMax = 0x000001ffffffffffL;
|
||||||
|
|
||||||
long low = startLow - DEFAULT_LOOP_MAX;
|
long msb = msbMax - 1;
|
||||||
long high = (short) (startHigh - 1);
|
long lsb = lsbMax - DEFAULT_LOOP_MAX;
|
||||||
|
|
||||||
GuidCreatorMock creator = new GuidCreatorMock(low, high, startLow, startHigh, TIMESTAMP);
|
GuidCreatorMock creator = new GuidCreatorMock(msb, lsb, msbMax, lsbMax, TIMESTAMP);
|
||||||
|
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||||
|
|
||||||
|
for (int i = 0; i < DEFAULT_LOOP_MAX - 1; i++) {
|
||||||
|
creator.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
creator.create();
|
||||||
|
fail("It should throw an overflow exception.");
|
||||||
|
} catch (UlidCreatorException e) {
|
||||||
|
// success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShouldThrowOverflowException2() {
|
||||||
|
|
||||||
|
long msbMax = (RANDOM.nextLong() & GuidCreatorMock.HALF_RANDOM_COMPONENT)
|
||||||
|
| GuidCreatorMock.INCREMENT_MAX;
|
||||||
|
long lsbMax = (RANDOM.nextLong() & GuidCreatorMock.HALF_RANDOM_COMPONENT)
|
||||||
|
| GuidCreatorMock.INCREMENT_MAX;
|
||||||
|
|
||||||
|
long msb = msbMax - 1;
|
||||||
|
long lsb = lsbMax - DEFAULT_LOOP_MAX;
|
||||||
|
|
||||||
|
GuidCreatorMock creator = new GuidCreatorMock(msb, lsb, msbMax, lsbMax, TIMESTAMP);
|
||||||
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
creator.withTimestampStrategy(new FixedTimestampStretegy(TIMESTAMP));
|
||||||
|
|
||||||
UUID uuid = new UUID(0, 0);
|
UUID uuid = new UUID(0, 0);
|
||||||
|
@ -123,19 +150,19 @@ public class GuidCreatorTest {
|
||||||
uuid = creator.create();
|
uuid = creator.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
long expectedLsb = startLow - 1;
|
long expectedLsb = (lsbMax - 1) & GuidCreatorMock.HALF_RANDOM_COMPONENT;
|
||||||
long randomLsb = uuid.getLeastSignificantBits();
|
long randomLsb = creator.extractRandomLsb(uuid);
|
||||||
assertEquals(String.format("The LSB should be iqual to %s.", expectedLsb), expectedLsb, randomLsb);
|
assertEquals("Incorrect LSB after loop.", expectedLsb, randomLsb);
|
||||||
|
|
||||||
long expectedMsb = startHigh - 1;
|
long expectedMsb = (msbMax - 1) & GuidCreatorMock.HALF_RANDOM_COMPONENT;
|
||||||
long randomMsb = (short) (uuid.getMostSignificantBits());
|
long randomMsb = creator.extractRandomMsb(uuid);
|
||||||
assertEquals(String.format("The MSB should be iqual to %s.", expectedMsb), expectedMsb, randomMsb);
|
assertEquals("Incorrect MSB after loop.", expectedMsb, randomMsb);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
creator.create();
|
creator.create();
|
||||||
fail("It should throw an overflow exception.");
|
fail("It should throw an overflow exception.");
|
||||||
} catch (UlidCreatorException e) {
|
} catch (UlidCreatorException e) {
|
||||||
// Success
|
// success
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue