TimeBasedEpochGeneratorTest.java

package com.fasterxml.uuid.impl;

import java.math.BigInteger;
import java.util.Arrays;
import java.util.UUID;
import java.util.function.Consumer;

import com.fasterxml.uuid.UUIDClock;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @since 5.2
 */
public class TimeBasedEpochGeneratorTest
{
    @Test
    public void testFormat() {
        BigInteger minEntropy = BigInteger.ZERO;
        long minTimestamp = 0;
        TimeBasedEpochGenerator generatorEmpty = new TimeBasedEpochGenerator(staticEntropy(minEntropy), staticClock(minTimestamp));
        UUID uuidEmpty = generatorEmpty.generate();
        assertEquals(0x07, uuidEmpty.version());
        assertEquals(0x02, uuidEmpty.variant());
        assertEquals(minTimestamp, getTimestamp(uuidEmpty));
        assertEquals(minEntropy, getEntropy(uuidEmpty));

        Consumer<byte[]> entropyFull = bytes -> Arrays.fill(bytes, (byte) 0xFF);
        long maxTimestamp = rightBitmask(48);
        TimeBasedEpochGenerator generatorFull = new TimeBasedEpochGenerator(entropyFull, staticClock(maxTimestamp));
        UUID uuidFull = generatorFull.generate();
        assertEquals(0x07, uuidFull.version());
        assertEquals(0x02, uuidFull.variant());
        assertEquals(maxTimestamp, getTimestamp(uuidFull));
        assertEquals(BigInteger.ONE.shiftLeft(73).subtract(BigInteger.ONE), getEntropy(uuidFull));
    }

    @Test
    public void testIncrement() {
        TimeBasedEpochGenerator generator = new TimeBasedEpochGenerator(staticEntropy(BigInteger.ZERO), staticClock(0));
        assertEquals(BigInteger.valueOf(0), getEntropy(generator.generate()));
        assertEquals(BigInteger.valueOf(1), getEntropy(generator.generate()));
        assertEquals(BigInteger.valueOf(2), getEntropy(generator.generate()));
        assertEquals(BigInteger.valueOf(3), getEntropy(generator.generate()));
    }

    @Test
    public void testCarryOnce() {
        TimeBasedEpochGenerator generator = new TimeBasedEpochGenerator(staticEntropy(BigInteger.valueOf(0xFF)), staticClock(0));
        assertEquals(BigInteger.valueOf(0xFF), getEntropy(generator.generate()));
        assertEquals(BigInteger.valueOf(0x100), getEntropy(generator.generate()));
    }

    @Test
    public void testCarryAll() {
        BigInteger largeEntropy = BigInteger.ONE.shiftLeft(73).subtract(BigInteger.ONE);
        TimeBasedEpochGenerator generator = new TimeBasedEpochGenerator(staticEntropy(largeEntropy), staticClock(0));
        assertEquals(largeEntropy, getEntropy(generator.generate()));
        assertEquals(BigInteger.ONE.shiftLeft(73), getEntropy(generator.generate()));
    }

    private long getTimestamp(UUID uuid) {
        return uuid.getMostSignificantBits() >>> 16;
    }

    private BigInteger getEntropy(UUID uuid) {
        return BigInteger.valueOf(uuid.getMostSignificantBits() & rightBitmask(12)).shiftLeft(62).or(
                BigInteger.valueOf(uuid.getLeastSignificantBits() & rightBitmask(62)));
    }

    private Consumer<byte[]> staticEntropy(BigInteger entropy) {
        byte[] entropyBytes = entropy.toByteArray();
        return bytes -> {
            int offset = bytes.length - entropyBytes.length;
            Arrays.fill(bytes, 0, offset, (byte) 0x00);
            System.arraycopy(entropyBytes, 0, bytes, offset, entropyBytes.length);
        };
    }

    private UUIDClock staticClock(long timestamp) {
        return new UUIDClock() {
            @Override
            public long currentTimeMillis() {
                return timestamp;
            }
        };
    }

    private long rightBitmask(int bits) {
        return (1L << bits) - 1;
    }
}