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