# PooledIdentifierProvider.java

    package org.codefilarete.stalactite.engine.idprovider;

    import java.time.Duration;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.Executor;
    import java.util.concurrent.LinkedBlockingQueue;
    import java.util.concurrent.TimeUnit;
    import java.util.stream.Collectors;

    /**
     * Simple provider that maintains a set of identifiers to prevent from a time consuming creation of those identifiers.
     * For instance, used for database based generated identifiers since the retrieval of a bunch of values may be time consuming.
     * Thought for table-sequence-generated identifiers like hi-lo ones.
     *
     * Thread-safe because based on a {@link BlockingQueue}
     *
     * @author Guillaume Mary
     */
    public abstract class PooledIdentifierProvider<T> implements IdentifierProvider<T> {

    	private final BlockingQueue<T> queue;
    	private final int threshold;
    	private final Executor executor;
    	private final Duration timeOut;

    	/**
    	 *
    	 * @param initialValues the initial values to fill this queue. Can be empty, not null.
    	 * @param threshold the threshold below (excluded) which the background service is called to fill this queue
    	 * @param executor the executor capable of running a background task of filling this queue
    	 * @param timeOut the timeout after which the {@link #giveNewIdentifier()} gives up and returns null because of an empty queue
    	 */
    	public PooledIdentifierProvider(Collection<T> initialValues, int threshold, Executor executor, Duration timeOut) {
    		// we use LinkedBlockingQueue because it's not bounded (with this constructor) : we need it because our implementation
    		// of giveNewIdentifier doesn't guarantee the number of elements in the stack
    		this.queue = initialValues.stream().collect(Collectors.toCollection(LinkedBlockingQueue::new));
    		this.threshold = threshold;
    		this.executor = executor;
    		this.timeOut = timeOut;
    	}

    	/**
    	 * Pops the queue. If there's not any more it will wait for any value until timeout (defined at construction time).
    	 */
    	private T pop() {
    		try {
    			return queue.poll(timeOut.getSeconds(), TimeUnit.SECONDS);
    		} catch (InterruptedException e) {
    			Thread.currentThread().interrupt();
    			// unreachable
    			return null;
    		}
    	}

    	/**
    	 * Warn: may return null if timeout (defined at construction time) is reached when trying to pop this queue
    	 * @return the identifier on the "top" of this queue
    	 */
    	@Override
    	public T giveNewIdentifier() {
    		// We ask for a queue filling if we reached the given threshold (below which we must refuel)
    		// Since this code block is not synchronized, we could have several refueling tasks added to the executor. I don't think it's a problem
    		// because the consequence is that we'll get some "extra" values reserved : the drawback is memory consumption due to extra identifiers
    		// which should be very small, and of course a extra values can be lost if JVM crashes.
    		// (we do it before pop() to take account of very first call : queue can be empty)
    		boolean refueling = ensureMinimalPool();
    		T toReturn = pop();
    		// we do it again for future calls to pop (if not previously done)
    		if (!refueling) {
    			ensureMinimalPool();
    		}
    		return toReturn;
    	}

    	private boolean ensureMinimalPool() {
    		if (queue.size() < threshold) {
    			executor.execute(this::fillQueue);
    			return true;
    		} else {
    			return false;
    		}
    	}

    	private void fillQueue() {
    		this.queue.addAll(new ArrayList<>(retrieveSomeValues()));
    	}

    	/**
    	 * Should return an unspecified number of values that will be pooled. Implementations are free to give more or less values depending on what
    	 * they want !
    	 * @return some non null values
    	 */
    	protected abstract Collection<T> retrieveSomeValues();
    }