import java.util.*;
import org.apache.log4j.Logger;

/**
* Copyright 2004 BayBridging.
*
* This software is furnished under a license and may be used
* and copied only in accordance with the terms of such license.
* This software or any other copies thereof in any form, may not be
* provided or otherwise made available, to any other person or company
* without written consent from BayBridging.
*
* BayBridging assumes no responsibility for the use or reliability of
* software which has been modified without approval.
*/


public class ThreadPool
{
private static final Log log = Logger.getLogger(ThreadPool.class);

/**
* Number of threads the pool contains
*/
private int threadCount;

/**
* the request queue
*/
private RequestQueue requestQueue;

/**
* list of worker threads
*/
private List threadList;

/**
* flags whether the pool is alive
*/
private boolean alive;

/**
* SuicideException used by the suicide pill to break out of the accept loop
*/
private static class SuicideException
extends RuntimeException
{
}

/**
* SuicidePill is a do nothing request that forces the an worker thread to quit.
* It performs this by throwing a SuicideException.
*/
private static class SuicidePill
implements ThreadRequest
{
boolean executed = false;
public synchronized void execute()
{
executed = true;
throw new SuicideException(); // Terminate this thread cleanly
}

public String toString()
{
return "Suicide Pill";
}

/**
* isExecuted
*
* @return boolean
*/
public synchronized boolean isExecuted()
{
return executed;
}
}

/**
* @param threadCount int Number of threads to initialize the pool to.
*/
public ThreadPool(int threadCount)
{
if (threadCount <= 0)
{
throw new IllegalArgumentException("Thread count must be greater than zero");
}

alive = false;
this.threadCount = threadCount;
}

/**
* Set request queue for the pool
* @param queue RequestQueue
* @throws ThreadPoolException thrown if the pool is running
*/
public void setRequestQueue(RequestQueue queue) throws ThreadPoolException
{
if (queue == null)
{
throw new IllegalArgumentException("null request queue is not allowed");
}

if (!alive)
{
requestQueue = queue;
}
else
{
throw new ThreadPoolException("Thread pool is running! Request queue cant be changed.");
}
}

/**
* Make the pool start to work.
*/
public void startup()
{
if (!alive)
{
alive = true;

if (requestQueue == null)
{
// create default request queue if the queue is not set
requestQueue = new SimpleRequestQueue();
}

// create thread list
threadList = new ArrayList();

// create worker threads
for (int i = 0; i < threadCount; i++)
{
Thread t = new WorkerThread(this);
synchronized (threadList)
{
threadList.add(t);
}

log.debug("Start thread: " + t);
t.start();
}
}
}

/**
* Shut down the pool.
* @param discardUndoneRequests flags whether to discard all undone requests
*/
public void shutdown(boolean discardUndoneRequests)
{
if (!alive)
{
log.warn("Pool is not started");
return;
}

if (discardUndoneRequests)
{
log.debug("Discard all undone requests");

// remove all requests
requestQueue.clear();
}

for (int i = 0; i < threadCount; i++)
{
try
{
// fill with Suicide pill to make worker threads quit
submitRequest(new SuicidePill());
}
catch (ThreadPoolException e)
{
// we should not get here
}
}

// wait for all threads to quit
synchronized (threadList)
{
while (threadList.size() > 0)
{
try
{
threadList.wait();
}
catch (InterruptedException ex)
{
log.error(ex);
}
}
}
log.debug("All worker threads have quit.");

requestQueue.clear();

alive = false;
}

/**
* Submit a reqeust to the pool.
* @param request ThreadRequest
* @throws ThreadPoolException Thrown if the pool is not alive.
*/
public void submitRequest(ThreadRequest request) throws ThreadPoolException
{
if (!alive)
{
throw new ThreadPoolException("The thread pool is not alive.");
}

requestQueue.addRequest(request);
}

/**
* Get requests from request queue and execute them.
*/
private void executeRequests()
{
ThreadRequest request;
while (true)
{
request = requestQueue.getRequest(keyForRequestQueue());
// if no request or the request has been executed, continue to next request
if (request == null || request.isExecuted())
{
continue;
}

try
{
if (log.isDebugEnabled())
{
log.debug("Execute request: " + request + ", " + Thread.currentThread());
}
// execute the request
request.execute();
}
catch (SuicideException e)
{
// got a suicide pill, the worker thread should quit
log.debug("Terminate worker thread: " + Thread.currentThread());
return;
}
catch (Throwable ex)
{
// we catch all other exceptions to prevent the worker thread quitting
log.error(ex);
}
}
}

/**
* get the object used to retrieve request from the request queue.

* Default returns current thread object.
* @return Object
*/
protected Object keyForRequestQueue()
{
return Thread.currentThread();
}

/**
* called by a worker thread when it has finished execution.
* @param t Thread
*/
private void threadFinished(Thread t)
{
synchronized (threadList)
{
threadList.remove(t);
threadList.notify();
}
}

/**
*

Title:


*

Description: Worker thread to execute requests.


*/
private static class WorkerThread
extends Thread
{
private ThreadPool pool;

WorkerThread(ThreadPool pool)
{
this.pool = pool;
}

public void run()
{
try
{
pool.executeRequests();
}
catch (Throwable e)
{
ThreadPool.log.error(e);
}

pool.threadFinished(this);
pool = null;
}
}

}