ProductEventStateStack.java

package net.finmath.finitedifference.assetderivativevaluation.products.internal;

import java.util.ArrayDeque;

/**
 * Thread-local stack for temporary product valuation state.
 *
 * <p>
 * This class is intended for product-local state that is needed only during a
 * finite-difference valuation, for example event-condition state used by
 * discretely monitored products. The state is stored in a {@link ThreadLocal}
 * stack so that concurrent valuations of the same product instance and nested
 * valuations within the same thread do not overwrite each other.
 * </p>
 *
 * <p>
 * State is pushed by calling {@link #push(Object)}. The returned {@link Scope}
 * should be closed when the state is no longer needed, preferably by using a
 * try-with-resources block. Closing the scope removes the most recently pushed
 * state from the current thread's stack and clears the thread-local storage
 * when the stack becomes empty.
 * </p>
 *
 * @param <T> The type of valuation state stored in the stack.
 * @author Alessandro Gnoatto
 */
public final class ProductEventStateStack<T> {

	/**
	 * The stack.
	 */
	private transient ThreadLocal<ArrayDeque<T>> stack;

	/**
	 * Pushes a state object onto the current thread's stack.
	 *
	 * <p>
	 * The returned {@link Scope} closes over the pushed state. Calling
	 * {@link Scope#close()} pops the state again. This allows callers to use
	 * the
	 * following pattern:
	 * </p>
	 *
	 * <pre>
	 * try(ProductEventStateStack.Scope ignored = stack.push(state)) {
	 *     // perform valuation using state
	 * }
	 * </pre>
	 *
	 * @param state The state to push.
	 * @return A scope whose {@link Scope#close()} method pops the pushed state.
	 * @throws IllegalArgumentException Thrown if {@code state} is {@code null}.
	 */
	public Scope push(final T state) {
		if (state == null) {
			throw new IllegalArgumentException("state must not be null.");
		}

		getStack().get().push(state);

		return this::pop;
	}

	/**
	 * Returns the current state for the calling thread.
	 *
	 * @return The state at the top of the current thread's stack, or
	 *         {@code null} if the stack is empty.
	 */
	public T currentOrNull() {
		final ArrayDeque<T> currentStack = getStack().get();
		return currentStack.isEmpty() ? null : currentStack.peek();
	}

	/**
	 * Pops the current state from the calling thread's stack.
	 *
	 * <p>
	 * If the stack becomes empty after the pop operation, the thread-local
	 * value
	 * is removed.
	 * </p>
	 *
	 * @throws IllegalStateException Thrown if the current thread's stack is
	 *         empty.
	 */
	private void pop() {
		final ArrayDeque<T> currentStack = getStack().get();

		if (currentStack.isEmpty()) {
			throw new IllegalStateException("No product event state to pop.");
		}

		currentStack.pop();

		if (currentStack.isEmpty()) {
			getStack().remove();
		}
	}

	/**
	 * Returns the current thread-local stack, creating it lazily if necessary.
	 *
	 * @return The thread-local stack.
	 */
	private ThreadLocal<ArrayDeque<T>> getStack() {
		if (stack == null) {
			stack = ThreadLocal.withInitial(ArrayDeque::new);
		}

		return stack;
	}

	/**
	 * Scope object used to release a pushed product event state.
	 *
	 * <p>
	 * Instances are returned by {@link #push(Object)} and are intended for use
	 * with try-with-resources. Closing a scope pops the corresponding state
	 * from
	 * the current thread's stack.
	 * </p>
	 */
	@FunctionalInterface
	public interface Scope extends AutoCloseable {

		/**
		 * Pops the state associated with this scope.
		 */
		@Override
		void close();
	}
}