/*
 * Copyright 2006-2021 DLR, Germany
 * 
 * SPDX-License-Identifier: EPL-1.0
 * 
 * https://rcenvironment.de/
 */
package de.rcenvironment.toolkit.modules.concurrency.api.threadcontext;

/**
 * A static holder to set or access the {@link ThreadContext} of the current thread.
 * 
 * @author Robert Mischke
 */
public final class ThreadContextHolder {

    private static final ThreadLocal<ThreadContext> sharedThreadLocal = new ThreadLocal<>();

    /**
     * Internal {@link ThreadContextMemento} implementation.
     * 
     * @author Robert Mischke
     */
    private static final class ThreadContextMementoImpl implements ThreadContextMemento {

        private final ThreadContext savedContext;

        private final ThreadContext newContext;

        ThreadContextMementoImpl(ThreadContext previous, ThreadContext newContext) {
            this.savedContext = previous;
            this.newContext = newContext;
        }

        @Override
        public void restore() {
            if (sharedThreadLocal.get() != newContext) {
                throw new IllegalStateException(
                    "Consistency violation: Found a different "
                        + ThreadContext.class.getSimpleName()
                        + " instance than the one that was set when this "
                        + ThreadContextMemento.class.getSimpleName()
                        + " was created. Most likely, a subsequent context change operation was not unwound "
                        + "after its nested operation was finished.");
            }
            sharedThreadLocal.set(savedContext);
        }

    }

    private ThreadContextHolder() {}

    /**
     * @return the {@link ThreadContext} of the current thread, or null if none exist
     */
    public static ThreadContext getCurrentContext() {
        return sharedThreadLocal.get(); // may be null
    }

    /**
     * Convenience method equivalent to {@link #getCurrentContext()}.{@link #getAspect(Class)}.
     * 
     * @param <T> the type of the aspect object to retrieve
     * @param aspectClass the type of the aspect object to retrieve
     * @return the selected aspect of the current thread's {@link ThreadContext}, or null if no context exists at all, or if it does not
     *         contain the selected aspect
     */
    public static <T> T getCurrentContextAspect(Class<T> aspectClass) {
        final ThreadContext threadContext = sharedThreadLocal.get();
        if (threadContext == null) {
            return null;
        }
        return threadContext.getAspect(aspectClass); // may be null
    }

    /**
     * @param newContext the new {@link ThreadContext} to attach to the current thread
     * @return the previous {@link ThreadContext} of the current thread, or null if none existed
     */
    public static ThreadContextMemento setCurrentContext(ThreadContext newContext) {
        final ThreadContext savedContext = sharedThreadLocal.get(); // may be null
        sharedThreadLocal.set(newContext);
        return new ThreadContextMementoImpl(savedContext, newContext);
    }

}
