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