/*
 * @(#)ThreadPool.java	1.4 01/12/04
 *
 * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package com.sun.corba.se.internal.orbutil;

import java.util.LinkedList;
import java.util.NoSuchElementException;

/**
 * Generic way to create a pool of threads.  Users define classes that implement
 * the Work interface for different kinds of tasks.
 *
 * Possible future improvements:
 *
 * Management of the pooled threads (further inactivity work, pool size, etc)
 *
 * Priority of work
 *
 * Debug messages
 */
public final class ThreadPool
{
    private static int threadCounter = 0; // serial counter useful for debugging

    private WorkQueue workToDo = new WorkQueue();
    private int availableWorkers = 0;
    private int inactivityTimeout;

    public ThreadPool() {
        inactivityTimeout = ORBConstants.DEFAULT_INACTIVITY_TIMEOUT;
    }

    public ThreadPool(int inactivityTimeout) {
        this.inactivityTimeout = inactivityTimeout;
    }

    private static synchronized int getUniqueThreadId() {
        return ThreadPool.threadCounter++;
    }

    /*
     * Note that the methods are not synchronized since they are
     * only used in the requestWork and addWork methods which
     * are already synchronized.
     */
    private static final class WorkQueue
    {
        private LinkedList workToDo = new LinkedList();

        final void enqueue(Work work)
        {
            workToDo.add(work);
        }

        final Work dequeue() throws NoSuchElementException
        {
            return (Work)workToDo.removeFirst();
        }

        final boolean isEmpty()
        {
            return workToDo.isEmpty();
        }
    }

    /**
     * Generic thread in the pool.  Simply requests and
     * processes Work instances.  The while(true) loop
     * could be replaced by something that checks for a
     * shutdown value set by the ThreadPool.
     */
    private class PooledThread extends Thread
    {
        private Work currentWork;
        private int threadId = 0; // unique id for the thread

        PooledThread() {
            this.threadId = ThreadPool.getUniqueThreadId();
        }

        public void run()
        {
            while (true) {
                try {

                    // Set the name to Idle for debugging
                    setName("Idle");

                    // Get some work to do
                    currentWork = requestWork();

                    // Set the name to the specific type
                    // of work for debugging.
                    StringBuffer buff = new StringBuffer();
                    buff.append(currentWork.getName());
                    buff.append('[');
                    buff.append(this.threadId);
                    buff.append(']');
                    setName(buff.toString());

                    // Do the work
                    currentWork.process();

                } catch (TimeoutException e) {
                    // This thread timed out waiting for something to do,
                    // so it can exit.
                    return;

                } catch (Exception e) {

                    // Ignore any exceptions that currentWork.process
                    // accidently lets through, but let Errors pass.
                    // Add debugging output?  REVISIT

                    // Note that InterruptedExceptions are
                    // caught here.  Thus, threads can be forced out of
                    // requestWork and so they have to reacquire the lock.
                    // Other options include ignoring or
                    // letting this thread die.
                }
            }
        }
    }

    /**
     * Called by a PooledThread to get the next appropriate
     * Work.
     */
    private synchronized Work requestWork()
        throws TimeoutException, InterruptedException
    {
        availableWorkers++;

        try {

            if (workToDo.isEmpty()) {

                this.wait(inactivityTimeout);

                // Did we time out?
                if (workToDo.isEmpty())
                    throw new TimeoutException();
            }

        } finally {

            availableWorkers--;
        }

        return workToDo.dequeue();
    }

    /**
     * Used by classes which access the ThreadPool to add new
     * tasks.
     */
    public synchronized void addWork(Work work)
    {
        workToDo.enqueue(work);

        if (availableWorkers == 0) {

            PooledThread thread = new PooledThread();

            // The thread must be set to a daemon thread so the
            // VM can exit if the only threads left are PooledThreads
            // or other daemons.  We don't want to rely on the
            // calling thread always being a daemon.

            // Catch exceptions since setDaemon can cause a
            // security exception to be thrown under netscape
            // in the Applet mode
            try {
                thread.setDaemon(true);
            } catch (Exception e) {}

            thread.start();
        } else
            this.notify();
    }

    private static class TimeoutException extends Exception {}
}
