
/*
 * Copyright (C) 2011 Archie L. Cobbs. All rights reserved.
 *
 * $Id$
 */

package org.dellroad.stuff.java;

import java.util.concurrent.Callable;

/**
 * Manages a thread local whose lifetime matches the duration of some method call.
 *
 * <p>
 * This class is useful for this common pattern:
 * <ul>
 *  <li>A thread local variable is instantiated by some initial method call and has an intended
 *      lifetime matching the duration of that method call;</li>
 *  <li>The thread local variable is accessible from other nested method calls in the same thread,
 *      as long as the initial method call is still executing;</li>
 *  <li>The thread local variable is removed (and optionally cleaned up) when the initial method call exits,
 *      whether successfully or not.</li>
 * </ul>
 *
 * @param <T> the type of the thread local variable
 */
public class ThreadLocalHolder<T> {

    private final ThreadLocal<T> threadLocal = new ThreadLocal<T>();

    /**
     * Invoke the given action while making the given thread local variable available via {@link #get} and {@link #require}.
     *
     * <p>
     * If there is already a thread local variable set for the current thread (i.e., we are already executing within
     * an invocation of <code>ThreadLocalHolder.invoke()</code>, then if {@code value} is the exact same Java object
     * (using object equality, not <code>equals()</code>), execution proceeds normally, otherwise an exception is thrown.
     *
     * @param value value for the thread local variable
     * @param action action to invoke
     * @throws IllegalArgumentException if either {@code action} or {@code value} is null
     * @throws IllegalStateException if there is already a thread local variable <code>previous</code>
     *  associated with the current thread and <code>value != previous</code>
     */
    public void invoke(final T value, Runnable action) {
        if (action == null)
            throw new IllegalArgumentException("null action");
        if (value == null)
            throw new IllegalArgumentException("null value");
        final T previousValue = this.threadLocal.get();
        final boolean topLevel = previousValue == null;
        if (!topLevel) {
            if (value != previousValue) {
                throw new IllegalStateException("already executing within an invocation of ThreadLocalHolder.invoke()"
                  + " but with a different value");
            }
        } else
            this.threadLocal.set(value);
        try {
            action.run();
        } finally {
            if (topLevel) {
                this.threadLocal.remove();
                this.destroy(value);
            }
        }
    }

    /**
     * Invoke the given action while making the given thread local variable available via {@link #get} and {@link #require}.
     *
     * <p>
     * If there is already a thread local variable set for the current thread (i.e., we are already executing within
     * an invocation of <code>ThreadLocalHolder.invoke()</code>, then if {@code value} is the exact same Java object
     * (using object equality, not <code>equals()</code>), execution proceeds normally, otherwise an exception is thrown.
     *
     * @param value value for the thread local variable
     * @param action action to invoke
     * @throws IllegalArgumentException if either {@code action} or {@code value} is null
     * @throws IllegalStateException if there is already a thread local variable <code>previous</code>
     *  associated with the current thread and <code>value != previous</code>
     * @throws Exception if {@code action} throws an {@link Exception}
     */
    public <R> R invoke(final T value, Callable<R> action) throws Exception {
        if (action == null)
            throw new IllegalArgumentException("null action");
        if (value == null)
            throw new IllegalArgumentException("null value");
        final T previousValue = this.threadLocal.get();
        final boolean topLevel = previousValue == null;
        if (!topLevel) {
            if (value != previousValue) {
                throw new IllegalStateException("already executing within an invocation of ThreadLocalHolder.invoke()"
                  + " but with a different value");
            }
        } else
            this.threadLocal.set(value);
        try {
            return action.call();
        } finally {
            if (topLevel) {
                this.threadLocal.remove();
                this.destroy(value);
            }
        }
    }

    /**
     * Get the thread local value associated with the current thread, if any.
     *
     * @return the current thread local variable value, or null if not executing
     *  within an invocation of <code>ThreadLocalHolder.invoke()</code>
     */
    public T get() {
        return this.threadLocal.get();
    }

    /**
     * Get the thread local value associated with the current thread; there must be one.
     *
     * @return the current thread local variable value, never null
     * @throws IllegalStateException if the current thread is not running
     *  within an invocation of <code>ThreadLocalHolder.invoke()</code>
     */
    public T require() {
        T value = this.threadLocal.get();
        if (value == null) {
            throw new IllegalStateException("no value associated with the current thread;"
              + " are we executing within an invocation of ThreadLocalHolder.invoke()?");
        }
        return value;
    }

    /**
     * Clean up the thread local value when no longer needed.
     *
     * <p>
     * The implementation in {@link ThreadLocalHolder} does nothing.
     */
    protected void destroy(T value) {
    }
}


@@ -514,16 +514,21 @@
 le from 
+some 
 other ne
@@ -565,16 +565,16 @@
 thread,%0A
-
  *      
@@ -937,32 +937,657 @@
 ocal
- = new ThreadLocal%3CT%3E();
+;%0A%0A    /**%0A     * Conveninece constructor.%0A     *%0A     * %3Cp%3E%0A     * Equivalent to:%0A     *  %3Cblockquote%3E%3Ccode%3Enew ThreadLocalHolder&lt;T&gt;(new ThreadLocal&lt;T&gt;())%3C/code%3E%3C/blockquote%3E%0A     */%0A    public ThreadLocalHolder() %7B%0A        this(new ThreadLocal%3CT%3E());%0A    %7D%0A%0A    /**%0A     * Primary constructor.%0A     *%0A     * @param threadLocal the thread local to use%0A     * @throws IllegalArgumentException if %7B@code threadLocal%7D is null%0A     */%0A    public ThreadLocalHolder(ThreadLocal%3CT%3E threadLocal) %7B%0A        if (threadLocal == null)%0A            throw new IllegalArgumentException(%22null threadLocal%22);%0A        this.threadLocal = threadLocal;%0A    %7D
 %0A%0A  
@@ -6288,16 +6288,54 @@
 nothing.
+ Subclasses may override if necessary.
 %0A     */

Add constructor variant.