Refactored GuidCreator.java

This commit is contained in:
Fabio Lima 2020-03-12 03:13:58 -03:00
parent 4590f46220
commit 5415552032
4 changed files with 140 additions and 70 deletions

View File

@ -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>

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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
}
}
}