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

View File

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

View File

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

View File

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