ThreadLocal.java

package uk.org.lidalia.lang;

import static java.lang.Thread.currentThread;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable;

import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;

/**
 * This class was written by Robert Elliot and is originally located at
 * https://github.com/Mahoney/lidalia-lang
 *
 * <p>A ThreadLocal that has no {@link ClassLoader} leaks associated with it and does not permit
 * null.
 *
 * <p>Values for all {@link Thread}s can be reset from any {@link Thread}.
 *
 * @param <T> the type of the thread local variable
 */
public class ThreadLocal<T> {

    private final Map<Thread, T> contents = new ConcurrentHashMap<>();
    private final Supplier<T> initialValueCreator;
    private final Supplier<T> threadValueInitialiser =
            new Supplier<T>() {
                @Override
                public T get() {
                    final T initialValue = initialValueCreator.get();
                    set(initialValue);
                    return initialValue;
                }
            };

    /**
     * @param initialValue the value this thread local will initially have for all {@link Thread}s.
     *     This should not be mutable, as it will be shared between all {@link Thread}s.
     */
    public ThreadLocal(final T initialValue) {
        this(() -> requireNonNull(initialValue));
    }

    /**
     * @param initialValueCreator a {@link Supplier} whose get method is called on a per {@link
     *     Thread} basis in order to establish the initial value for that {@link Thread}, allowing a
     *     different initial instance per {@link Thread}.
     */
    public ThreadLocal(final Supplier<T> initialValueCreator) {
        this.initialValueCreator = requireNonNull(initialValueCreator);
    }

    /**
     * @param value the new value for the calling {@link Thread} - does not affect the value for any
     *     other {@link Thread}.
     */
    public void set(final T value) {
        contents.put(currentThread(), requireNonNull(value));
    }

    /**
     * @return the value for the calling {@link Thread}, or the initial value if this has not been set
     *     or has been removed.
     */
    public T get() {
        return ofNullable(contents.get(currentThread())).orElseGet(threadValueInitialiser);
    }

    /**
     * Removes the value for the calling {@link Thread}. A subsequent call to {@link #get()} will
     * return the initial value.
     */
    public void remove() {
        contents.remove(currentThread());
    }

    /**
     * Removes the values for ALL {@link Thread}s. Subsequent calls to {@link #get()} will return the
     * initial value.
     */
    public void reset() {
        contents.clear();
    }

    public Collection<T> allValues() {
        return contents.values();
    }
}