/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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 com.sun.jini.thread;

import com.sun.jini.action.GetLongAction;
import java.security.AccessController;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * ThreadPool is a simple thread pool implementation of the Executor
 * interface.
 *
 * A new task is always given to an idle thread, if one is available;
 * otherwise, a new thread is always created.  There is no minimum
 * warm thread count, nor is there a maximum thread count (tasks are
 * never queued unless there are sufficient idle threads to execute
 * them).
 *
 * New threads are created as daemon threads in the thread group that
 * was passed to the ThreadPool instance's constructor.  Each thread's
 * name is the prefix NewThreadAction.NAME_PREFIX followed by the name
 * of the task it is currently executing, or "Idle" if it is currently
 * idle.
 *
 * <p>This implementation uses the {@link Logger} named
 * <code>com.sun.jini.thread.ThreadPool</code> to
 * log information at the following levels:
 *
 * <p><table summary="Describes what is logged by ThreadPool at
 * various logging levels" border=1 cellpadding=5>
 *
 * <tr> <th> Level <th> Description
 *
 * <tr> <td> {@link Level#WARNING WARNING} <td> uncaught exception in
 * worker thread
 *
 * </table>
 *
 * @author	Sun Microsystems, Inc.
 **/
final class ThreadPool implements Executor, java.util.concurrent.Executor {

    /** how long a thread waits in the idle state before passing away */
    private static final long idleTimeout =		// default 5 minutes
	((Long) AccessController.doPrivileged(new GetLongAction(
	    "com.sun.jini.thread.idleThreadTimeout", 300000)))
	    .longValue();

    private static final Logger logger =
	Logger.getLogger("com.sun.jini.thread.ThreadPool");

    /** thread group that this pool's threads execute in */
    private final ThreadGroup threadGroup;

    /** lock guarding all mutable instance state (below) */
    private final Object lock = new Object();

    /** threads definitely available to take new tasks */
    private int idleThreads = 0;

    /** queues of tasks to execute */
    private final LinkedList queue = new LinkedList();

    /**
     * Creates a new thread group that executes tasks in threads of
     * the given thread group.
     */
    ThreadPool(ThreadGroup threadGroup) {
	this.threadGroup = threadGroup;
    }

    public void execute(Runnable runnable, String name) {
	Task task = new Task(runnable, name);
	synchronized (lock) {
	    if (queue.size() < idleThreads) {
		queue.addLast(task);
		lock.notify();
		return;
	    }
	}
	Thread t = (Thread) AccessController.doPrivileged(
	    new NewThreadAction(threadGroup, new Worker(task), name, true));
	t.start();
    }

    public void execute(Runnable command) {
        execute(command, "com.sun.jini.thread.ThreadPool");
    }

    /**
     * Task simply encapsulates a task's Runnable object with its name.
     */
    private static class Task {

	final Runnable runnable;
	final String name;

	Task(Runnable runnable, String name) {
	    this.runnable = runnable;
	    this.name = name;
	}
    }

    /**
     * Worker executes an initial task, and then it executes tasks from the
     * queue, passing away if ever idle for more than the idle timeout value.
     */
    private class Worker implements Runnable {

	private Task first;

	Worker(Task first) {
	    this.first = first;
	}

	public void run() {
	    Task task = first;
	    first = null;

	    while (true) {
		try {
		    task.runnable.run();
		} catch (Throwable t) {
		    logger.log(Level.WARNING, "uncaught exception", t);
		}
		/*
		 * REMIND: What if the task changed this thread's
		 * priority? or context class loader?
		 */

		synchronized (lock) {
		    if (queue.isEmpty()) {
			Thread.currentThread().setName(
			    NewThreadAction.NAME_PREFIX + "Idle");
			idleThreads++;
			try {
			    lock.wait(idleTimeout);
			} catch (InterruptedException e) {
			    // ignore interrupts at this level
			} finally {
			    idleThreads--;
			}
			if (queue.isEmpty()) {
			    break;		// timed out
			}
		    }
		    task = (Task) queue.removeFirst();
		    Thread.currentThread().setName(
			NewThreadAction.NAME_PREFIX + task.name);
		}
	    };
	}
    }
}
