/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2017 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jboss.threads;

import java.lang.reflect.Field;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ScheduledExecutorService;
import java.security.PrivilegedAction;
import java.security.AccessController;

import org.jboss.logging.Logger;
import org.wildfly.common.Assert;
import sun.misc.Unsafe;

/**
 * JBoss thread- and executor-related utility and factory methods.
 */
public final class JBossExecutors {

    private static final Logger THREAD_ERROR_LOGGER = Logger.getLogger("org.jboss.threads.errors");

    private JBossExecutors() {}

    private static final RuntimePermission COPY_CONTEXT_CLASSLOADER_PERMISSION = new RuntimePermission("copyClassLoader");

    private static final ExecutorService REJECTING_EXECUTOR_SERVICE = new DelegatingExecutorService(RejectingExecutor.INSTANCE);
    private static final ExecutorService DISCARDING_EXECUTOR_SERVICE = new DelegatingExecutorService(DiscardingExecutor.INSTANCE);

    // ==================================================
    // DIRECT EXECUTORS
    // ==================================================

    /**
     * Get the direct executor.  This executor will immediately run any task it is given, and propagate back any
     * run-time exceptions thrown.
     *
     * @return the direct executor instance
     */
    public static Executor directExecutor() {
        return SimpleDirectExecutor.INSTANCE;
    }

    /**
     * Get the rejecting executor.  This executor will reject any task submitted to it.
     *
     * @return the rejecting executor instance
     */
    public static Executor rejectingExecutor() {
        return RejectingExecutor.INSTANCE;
    }

    /**
     * Get a rejecting executor.  This executor will reject any task submitted to it with the given message.
     *
     * @param message the reject message
     * @return the rejecting executor instance
     */
    public static Executor rejectingExecutor(final String message) {
        return new RejectingExecutor(message);
    }

    /**
     * Get the rejecting executor service.  This executor will reject any task submitted to it.  It cannot be shut down.
     *
     * @return the rejecting executor service instance
     */
    public static ExecutorService rejectingExecutorService() {
        return REJECTING_EXECUTOR_SERVICE;
    }

    /**
     * Get the rejecting executor service.  This executor will reject any task submitted to it with the given message.
     * It cannot be shut down.
     *
     * @param message the reject message
     * @return the rejecting executor service instance
     */
    public static ExecutorService rejectingExecutorService(final String message) {
        return protectedExecutorService(rejectingExecutor(message));
    }

    /**
     * Get the discarding executor.  This executor will silently discard any task submitted to it.
     *
     * @return the discarding executor instance
     */
    public static Executor discardingExecutor() {
        return DiscardingExecutor.INSTANCE;
    }

    /**
     * Get the discarding executor service.  This executor will silently discard any task submitted to it.  It cannot
     * be shut down.
     *
     * @return the discarding executor service instance
     */
    public static ExecutorService discardingExecutorService() {
        return DISCARDING_EXECUTOR_SERVICE;
    }

    /**
     * Create an executor which runs tasks with the given context class loader.
     *
     * @param delegate the executor to delegate to
     * @param taskClassLoader the context class loader to use
     * @return the new direct executor
     */
    public static Executor contextClassLoaderExecutor(final Executor delegate, final ClassLoader taskClassLoader) {
        return new DelegatingExecutor(delegate) {
            public void execute(final Runnable command) {
                super.execute(new ContextClassLoaderSavingRunnable(taskClassLoader, command));
            }
        };
    }

    // ==================================================
    // REJECTED EXECUTION HANDLERS
    // ==================================================

    private static final RejectedExecutionHandler ABORT_POLICY = new ThreadPoolExecutor.AbortPolicy();
    private static final RejectedExecutionHandler CALLER_RUNS_POLICY = new ThreadPoolExecutor.CallerRunsPolicy();
    private static final RejectedExecutionHandler DISCARD_OLDEST_POLICY = new ThreadPoolExecutor.DiscardOldestPolicy();
    private static final RejectedExecutionHandler DISCARD_POLICY = new ThreadPoolExecutor.DiscardPolicy();

    /**
     * Get the abort policy for a {@link java.util.concurrent.ThreadPoolExecutor}.
     *
     * @return the abort policy
     * @see java.util.concurrent.ThreadPoolExecutor.AbortPolicy
     */
    public static RejectedExecutionHandler abortPolicy() {
        return ABORT_POLICY;
    }

    /**
     * Get the caller-runs policy for a {@link java.util.concurrent.ThreadPoolExecutor}.
     *
     * @return the caller-runs policy
     * @see java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy
     */
    public static RejectedExecutionHandler callerRunsPolicy() {
        return CALLER_RUNS_POLICY;
    }

    /**
     * Get the discard-oldest policy for a {@link java.util.concurrent.ThreadPoolExecutor}.
     *
     * @return the discard-oldest policy
     * @see java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy
     */
    public static RejectedExecutionHandler discardOldestPolicy() {
        return DISCARD_OLDEST_POLICY;
    }

    /**
     * Get the discard policy for a {@link java.util.concurrent.ThreadPoolExecutor}.
     *
     * @return the discard policy
     * @see java.util.concurrent.ThreadPoolExecutor.DiscardPolicy
     */
    public static RejectedExecutionHandler discardPolicy() {
        return DISCARD_POLICY;
    }

    /**
     * Get a handoff policy for a {@link java.util.concurrent.ThreadPoolExecutor}.  The returned instance will
     * delegate to another executor in the event that the task is rejected.
     *
     * @param target the target executor
     * @return the new handoff policy implementation
     */
    public static RejectedExecutionHandler handoffPolicy(final Executor target) {
        return new HandoffRejectedExecutionHandler(target);
    }

    // ==================================================
    // PROTECTED EXECUTOR SERVICE WRAPPERS
    // ==================================================

    /**
     * Wrap an executor with an {@code ExecutorService} instance which supports all the features of {@code ExecutorService}
     * except for shutting down the executor.
     *
     * @param target the target executor
     * @return the executor service
     */
    public static ExecutorService protectedExecutorService(final Executor target) {
        return new DelegatingExecutorService(target);
    }

    /**
     * Wrap a scheduled executor with a {@code ScheduledExecutorService} instance which supports all the features of
     * {@code ScheduledExecutorService} except for shutting down the executor.
     *
     * @param target the target executor
     * @return the executor service
     */
    public static ScheduledExecutorService protectedScheduledExecutorService(final ScheduledExecutorService target) {
        return new DelegatingScheduledExecutorService(target);
    }

    // ==================================================
    // THREAD FACTORIES
    // ==================================================

    /**
     * Create a thread factory which resets all thread-local storage and delegates to the given thread factory.
     * You must have the {@link RuntimePermission}{@code ("modifyThread")} permission to use this method.
     *
     * @param delegate the delegate thread factory
     * @return the resetting thread factory
     * @throws SecurityException if the caller does not have the {@link RuntimePermission}{@code ("modifyThread")}
     * permission
     */
    public static ThreadFactory resettingThreadFactory(final ThreadFactory delegate) throws SecurityException {
        return new ThreadFactory() {
            public Thread newThread(final Runnable r) {
                return delegate.newThread(new ThreadLocalResettingRunnable(r));
            }
        };
    }

    private static final Runnable TCCL_RESETTER = new Runnable() {
        public void run() {
            Thread.currentThread().setContextClassLoader(null);
        }

        public String toString() {
            return "ContextClassLoader-resetting Runnable";
        }
    };

    // ==================================================
    // RUNNABLES
    // ==================================================

    private static final Runnable NULL_RUNNABLE = NullRunnable.getInstance();

    /**
     * Get the null runnable which does nothing.
     *
     * @return the null runnable
     */
    public static Runnable nullRunnable() {
        return NULL_RUNNABLE;
    }

    /**
     * Get a {@code Runnable} which, when executed, clears the thread context class loader (if the caller has sufficient
     * privileges).
     *
     * @return the runnable
     */
    public static Runnable contextClassLoaderResetter() {
        return TCCL_RESETTER;
    }

    /**
     * Create a task that delegates to the given task, preserving the context classloader which was in effect when
     * this method was invoked.
     *
     * @param delegate the delegate runnable
     * @return the wrapping runnable
     * @throws SecurityException if a security manager exists and the caller does not have the {@code "copyClassLoader"}
     * {@link RuntimePermission}.
     */
    public static Runnable classLoaderPreservingTask(final Runnable delegate) throws SecurityException {
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(COPY_CONTEXT_CLASSLOADER_PERMISSION);
        }
        return classLoaderPreservingTaskUnchecked(delegate);
    }

    static final ClassLoader SAFE_CL;

    static {
        ClassLoader safeClassLoader = JBossExecutors.class.getClassLoader();
        if (safeClassLoader == null) {
            safeClassLoader = ClassLoader.getSystemClassLoader();
        }
        if (safeClassLoader == null) {
            safeClassLoader = new ClassLoader() {
            };
        }
        SAFE_CL = safeClassLoader;
    }

    static Runnable classLoaderPreservingTaskUnchecked(final Runnable delegate) {
        Assert.checkNotNullParam("delegate", delegate);
        return new ContextClassLoaderSavingRunnable(getContextClassLoader(Thread.currentThread()), delegate);
    }

    static final Unsafe unsafe;

    static final long contextClassLoaderOffs;

    static {
        unsafe = AccessController.doPrivileged(new PrivilegedAction<Unsafe>() {
            public Unsafe run() {
                try {
                    final Field field = Unsafe.class.getDeclaredField("theUnsafe");
                    field.setAccessible(true);
                    return (Unsafe) field.get(null);
                } catch (IllegalAccessException e) {
                    throw new IllegalAccessError(e.getMessage());
                } catch (NoSuchFieldException e) {
                    throw new NoSuchFieldError(e.getMessage());
                }
            }
        });
        try {
            contextClassLoaderOffs = unsafe.objectFieldOffset(Thread.class.getDeclaredField("contextClassLoader"));
        } catch (NoSuchFieldException e) {
            throw new NoSuchFieldError(e.getMessage());
        }
    }

    /**
     * Privileged method to get the context class loader of the given thread.
     *
     * @param thread the thread to introspect
     * @return the context class loader
     */
    static ClassLoader getContextClassLoader(final Thread thread) {
        return (ClassLoader) unsafe.getObject(thread, contextClassLoaderOffs);
    }

    /**
     * Privileged method to get and set the context class loader of the given thread.
     *
     * @param thread the thread to introspect
     * @param newClassLoader the new context class loader
     * @return the old context class loader
     */
    static ClassLoader getAndSetContextClassLoader(final Thread thread, final ClassLoader newClassLoader) {
        try {
            return getContextClassLoader(thread);
        } finally {
            setContextClassLoader(thread, newClassLoader);
        }
    }

    /**
     * Privileged method to set the context class loader of the given thread.
     *
     * @param thread the thread to introspect
     * @param classLoader the new context class loader
     */
    static void setContextClassLoader(final Thread thread, final ClassLoader classLoader) {
        unsafe.putObject(thread, contextClassLoaderOffs, classLoader);
    }

    /**
     * Privileged method to clear the context class loader of the given thread to a safe non-{@code null} value.
     *
     * @param thread the thread to introspect
     */
    static void clearContextClassLoader(final Thread thread) {
        unsafe.putObject(thread, contextClassLoaderOffs, SAFE_CL);
    }

    // ==================================================
    // UNCAUGHT EXCEPTION HANDLERS
    // ==================================================

    /**
     * Get an uncaught exception handler which logs to the given logger.
     *
     * @param log the logger
     * @return the handler
     */
    public static Thread.UncaughtExceptionHandler loggingExceptionHandler(final Logger log) {
        return new LoggingUncaughtExceptionHandler(log);
    }

    /**
     * Get an uncaught exception handler which logs to the given logger.
     *
     * @param categoryName the name of the logger category to log to
     * @return the handler
     */
    public static Thread.UncaughtExceptionHandler loggingExceptionHandler(final String categoryName) {
        return new LoggingUncaughtExceptionHandler(Logger.getLogger(categoryName));
    }

    private static final Thread.UncaughtExceptionHandler LOGGING_HANDLER = loggingExceptionHandler(THREAD_ERROR_LOGGER);

    /**
     * Get an uncaught exception handler which logs to the default error logger.
     *
     * @return the handler
     */
    public static Thread.UncaughtExceptionHandler loggingExceptionHandler() {
        return LOGGING_HANDLER;
    }
}
