TimeBasedEpochGenerator.java

package com.fasterxml.uuid.impl;

import java.security.SecureRandom;
import java.util.Objects;
import java.util.Random;
import java.util.UUID;
import java.util.function.Consumer;

import com.fasterxml.uuid.NoArgGenerator;
import com.fasterxml.uuid.UUIDClock;
import com.fasterxml.uuid.UUIDType;

/**
 * Implementation of UUID generator that uses time/location based generation
 * method field from the Unix Epoch timestamp source - the number of 
 * milliseconds seconds since midnight 1 Jan 1970 UTC, leap seconds excluded.
 * This is usually referred to as "Version 7".
 * <p>
 * As all JUG provided implementations, this generator is fully thread-safe.
 * Additionally it can also be made externally synchronized with other instances
 * (even ones running on other JVMs); to do this, use
 * {@link com.fasterxml.uuid.ext.FileBasedTimestampSynchronizer} (or
 * equivalent).
 *
 * @since 4.1
 */
public class TimeBasedEpochGenerator extends NoArgGenerator
{ 
    private static final int ENTROPY_BYTE_LENGTH = 10;

    /*
    /**********************************************************************
    /* Configuration
    /**********************************************************************
     */

    /**
     * Source for random numbers used to fill a byte array with entropy.
     *
     * @since 5.2 (replaced earlier {@code java.util.Random _random})
     */
    protected final Consumer<byte[]> _randomNextBytes;

    /**
     * Underlying {@link UUIDClock} used for accessing current time, to use for
     * generation.
     *
     * @since 4.3
     */
    protected final UUIDClock _clock;

    private long _lastTimestamp = -1;
    private final byte[] _lastEntropy  = new byte[ENTROPY_BYTE_LENGTH];

    /*
    /**********************************************************************
    /* Construction
    /**********************************************************************
     */

    /**
     * @param rnd Random number generator to use for generating UUIDs; if null,
     *   shared default generator is used. Note that it is strongly recommend to
     *   use a <b>good</b> (pseudo) random number generator; for example, JDK's
     *   {@link SecureRandom}.
     */
    public TimeBasedEpochGenerator(Random rnd) {
        this(rnd, UUIDClock.systemTimeClock());
    }

    /**
     * @param rnd Random number generator to use for generating UUIDs; if null,
     *   shared default generator is used. Note that it is strongly recommend to
     *   use a <b>good</b> (pseudo) random number generator; for example, JDK's
     *   {@link SecureRandom}.
     * @param clock clock Object used for accessing current time to use for generation
     */
    public TimeBasedEpochGenerator(Random rnd, UUIDClock clock)
    {
        this((rnd == null ? LazyRandom.sharedSecureRandom() : rnd)::nextBytes, clock);
    }

    /**
     * 
     * @param randomNextBytes Source for random numbers to use for generating UUIDs.
     *  Note that it is strongly recommend to use a <b>good</b> (pseudo) random number source;
     *  for example, JDK's {@code SecureRandom::nextBytes}.
     * @param clock clock Object used for accessing current time to use for generation
     *
     * @since 5.2
     */
    protected TimeBasedEpochGenerator(Consumer<byte[]> randomNextBytes, UUIDClock clock)
    {
        _randomNextBytes = Objects.requireNonNull(randomNextBytes);
        _clock = clock;
    }

    /*
    /**********************************************************************
    /* Access to config
    /**********************************************************************
     */

    @Override
    public UUIDType getType() { return UUIDType.TIME_BASED_EPOCH; }

    /*
    /**********************************************************************
    /* UUID generation
    /**********************************************************************
     */

    @Override
    public UUID generate()
    {
        return construct(_clock.currentTimeMillis());
    }

    /**
     * Method that will construct actual {@link UUID} instance for given
     * unix epoch timestamp: called by {@link #generate()} but may alternatively be
     * called directly to construct an instance with known timestamp.
     * NOTE: calling this method directly produces somewhat distinct UUIDs as
     * "entropy" value is still generated as necessary to avoid producing same
     * {@link UUID} even if same timestamp is being passed.
     *
     * @param rawTimestamp unix epoch millis
     *
     * @return unix epoch time based UUID
     *
     * @since 4.3
     */
    public UUID construct(long rawTimestamp)
    {
        final long mostSigBits, leastSigBits;
        synchronized (_lastEntropy) {
            if (rawTimestamp == _lastTimestamp) {
                carry:
                {
                    for (int i = ENTROPY_BYTE_LENGTH - 1; i > 0; i--) {
                        _lastEntropy[i] = (byte) (_lastEntropy[i] + 1);
                        if (_lastEntropy[i] != 0x00) {
                            break carry;
                        }
                    }
                    _lastEntropy[0] = (byte) (_lastEntropy[0] + 1);
                    if (_lastEntropy[0] >= 0x04) {
                        throw new IllegalStateException("overflow on same millisecond");
                    }
                }
            } else {
                _lastTimestamp = rawTimestamp;
                _randomNextBytes.accept(_lastEntropy);
                // In the most significant byte, only 2 bits will fit in the UUID, and one of those should be cleared
                // to guard against overflow.
                _lastEntropy[0] &= 0x01;
            }
            mostSigBits = rawTimestamp << 16 |
                    (long) UUIDType.TIME_BASED_EPOCH.raw() << 12 |
                    Byte.toUnsignedLong(_lastEntropy[0]) << 10 |
                    Byte.toUnsignedLong(_lastEntropy[1]) << 2 |
                    Byte.toUnsignedLong(_lastEntropy[2]) >>> 6;
            long right62Mask = (1L << 62) - 1;
            long variant = 0x02;
            leastSigBits = variant << 62 |
                    _toLong(_lastEntropy, 2) & right62Mask;
        }
        return new UUID(mostSigBits, leastSigBits);
    }
}