/*
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 Bull S.A.
 * Contact: jonas-team@objectweb.org
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * Initial developer(s): Philippe Coq, Philippe Durieux
 * Contributor(s): ______________________________________.
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.objectweb.jonas_ejb.container;

import java.util.ArrayList;

/**
 * This is a generic Thread Pool. It manages an ArrayList of Runnable
 * Each thread picks one to run it and loop on the next.
 */
public class ThreadPool {

    protected static final int DEFAULT_POOL_MIN_SIZE = 1;
    protected static final int DEFAULT_POOL_MAX_SIZE = 100;
    protected static int poolnumber = 0;

    protected int poolsz;    // current size of thread pool
    protected int maxpoolsz; // max size of thread pool
    protected ArrayList runnablelist = null;	// List of runnable ready to run
    protected boolean valid = true; // set to false when pool is removed.

    /**
     * Create a pool with default values
     */
    public ThreadPool() {
	this(DEFAULT_POOL_MIN_SIZE, DEFAULT_POOL_MAX_SIZE);
    }

    /**
     * Create a pool with a number of threads pre allocated
     */
    public ThreadPool(int size) {
	this(size, size > DEFAULT_POOL_MAX_SIZE ? size : DEFAULT_POOL_MAX_SIZE);
    }

    /**
     * Create a pool with a minimum and a maximum values for thread number.
     */
    public ThreadPool(int minsz, int maxsz) {
	runnablelist = new ArrayList(minsz);
	poolnumber++;
	for (poolsz = 0; poolsz < minsz; poolsz++) {
	    ServerThread st = new ServerThread(this, poolsz, poolnumber);
	    st.start();
	}
	maxpoolsz = maxsz;
    }

    /**
     * Add a Runnable in the pool, ready to be run.
     */
    public synchronized void addRunnable(Runnable runner) {
	TraceEjb.debugThread("ThreadPool : addRunnable");
	runnablelist.add(runner);
	// The policy here is to create 1 more thread when there are more
	// waiting runnable than running threads. why not ?
	// Do not create more than maxpoolsz threads.
	if (poolsz < maxpoolsz && poolsz < runnablelist.size()) {
	    ServerThread st = new ServerThread(this, poolsz++, poolnumber);
	    st.start();
	}
	TraceEjb.debugThread("ThreadPool : runnablelist size = "+runnablelist.size());
	// Wake up 1 thread if it's already waiting for a Runnable
	notify();
    }

    /**
     * Get the next Runnable to be run
     */
    public synchronized Runnable nextRunnable() {
	TraceEjb.debugThread("ThreadPool: nextRunnable");
	Runnable run = null;

	while (runnablelist.isEmpty()) {
	    try {
		wait();
		if (!valid) {
		    // thread will be stopped
		    return run;
		}
	    } catch(InterruptedException ex) {
		TraceEjb.error("ThreadPool: Interrupted");
		return null;
	    }
	}
	run = (Runnable)runnablelist.remove(0);
	TraceEjb.debugThread("ThreadPool: runnablelist size = "+runnablelist.size());
	return run;
    }

    /**
     * Pool is removed: Stop all threads
     */
    public synchronized void stopThreads() {
	valid = false;
	notifyAll();
	poolnumber--;
    }
}

/**
 * This Thread loops executing runnables added in the pool
 */
class ServerThread extends Thread {
    ThreadPool pool;
    int number;

    ServerThread(ThreadPool p, int num, int pinb) {
	pool = p;
	setName("ServerThread-"+pinb+"/"+num);
	number = num;
    }

    public void run() {
	while (true) {
	    try {
		// get next runnable, or wait if empty list.
		Runnable runner = pool.nextRunnable();
		if (runner == null) {
		    TraceEjb.debugThread(getName() + " ending");
		    return;
		}		
		TraceEjb.debugThread(getName() + " running");
		runner.run();
	    } catch(Exception ex) {
	    }
	}
    }
}



