/*
 * @(#)JavaCGIBridgePool.java 2.00 07/18/98
 *
 * Copyright Info: This class was written by Gunther Birznieks
 * gunther@clark.net having been inspired by countless other
 * software developers.
 *
 * Version 1.00: 08/10/97
 * Version 2.00: 07/19/98
 *
 * The second version of this software was written for
 * independent study under the guidance of Professor 
 * Marty Hall as part of the Johns Hopkins University 
 * Computer Science Master's Degree program.
 *
 * Feel free to copy, cite, reference, sample, borrow, resell
 * or plagiarize the contents.  However, if you don't mind,
 * please let me know where it goes so that I can at least
 * watch and take part in the development of the memes. Information
 * wants to be free; support public domain freeware.  
 * 
 * Donations are appreciated.  If you use this software please
 * consider making a donation to the Electronic Frontier Foundation
 * http://www.eff.org/ or another organization of your choice
 * dedicated to defending liberty on the net.
 *
 */

package com.extropia.net;

import java.util.Hashtable;
import java.util.Enumeration;
import java.util.Vector;

/**
 * This is a class that manages the creation and reuse of
 * previously created JavaCGIBridge Objects.  This is useful
 * in two cases.
 * <P>
 * First, if your applet requires using JavaCGIBridge many
 * times during its life, it can be wasteful to keep creating
 * new JavaCGIBridge objects for use.  The JavaCGIBridgePool 
 * class creates a new JavaCGIBridge if none are available
 * and it hands back an existing idle JavaCGIBridge Object
 * otherwise for reuse.
 * <P>
 * Second, if you methods in the JavaCGIBridge which cause
 * it to return control back to the main applet thread before
 * it has actually finished with the HTTP communication, then
 * JavaCGIBridgePool also takes into account the status of
 * the JavaCGIBridge objects in deciding whether or not 
 * to hand it to the user.  If the JavaCGIBridge object claims
 * it is still processing an HTTP request in another thread,
 * then the JavaCGIBridgePool will pass over that object and
 * look at the next one or create a new instance if no more
 * is available.
 * <P>
 * In addition, if you turn on the feature, a separate
 * thread will be spawned which will periodically check
 * if there are any JavaCGIBridge objects that have timed
 * out. These will be stopped and the observers of the
 * timed out JavaCGIBridge object will be notified of the
 * exception.
 * <P>
 * Most traditional object pool implementations require
 * explicit check in and check out procedures so that the
 * pool knows that the object has been handed back in for
 * reuse.  JavaCGIBridgePool does not operate this way. 
 * Since JavaCGIBridge objects know when they are done
 * processing and have their state set to IDLE, the
 * JavaCGIBridgePool reclaims these objects at that point.
 * Therefore, it is important to always use JavaCGIBridgePool
 * to get the next JavaCGIBridge object after each request
 * instead of reusing it on your own.  If you plan on reusing
 * a JavaCGIBridge object on your own, it is better if you
 * create it yourself.  However, if you create your own, you
 * will lose a lot of the built in logic of the JavaCGIBridgePool
 * class.
 * <P>
 * Design note: This class is meant to be used as a singleton.
 * However, instead of simply containing static methods, you need
 * to instantiate this class.  The reason for this is that 
 * browsers tend to run all applets in a single java virtual
 * machine.  Using a class singleton instead of an instantiated
 * object singleton would therefore introduce subtle bugs if
 * multiple applets provide different defaults to the Pool.
 * If you wish to use a class singleton mechanism, I would
 * suggest creating a wrapper class that has a JavaCGIBridgePool
 * static member.  This could be an advantage if you are
 * explicitly designing your application to share JavaCGIBridge
 * objects between applets.
 *
 * @version     2.00, 18 Jul 1998
 * @author      Gunther Birznieks
 * @author      Marty Hall
 *
 */
public class JavaCGIBridgePool implements Runnable {

    /**
     * Vector that contains JavaCGIBridge objects.  This data
     * structure is the virtual "pool" of JavaCGIBridges.
     */
    private Vector _javaCGIBridgeVector = new Vector();

    /**
     * int containing how many milliseconds to sleep
     * before checking to see if any JavaCGIBridge objects
     * need cleaning up.
     *
     * The default, 0, means no automatic clean up occurs.
     */
    private int _threadCleanTime = 0;

    /**
     * Thread containing a reference to the thread
     * which checks for cleanup
     */
    private Thread _threadCleaner = null;

    /**
     * Maximum number of IDLE JavaCGIBridge objects to
     * keep around. 0 means keep all of them around.
     */
    private int _maxSpareObjects = 0;

    /**
     * Maximum number of JavaCGIBridge objects
     * to have in the pool. Default is unlimited (0).
     * Be careful with this setting! You may have
     * a severe performance loss if you set up an
     * upper limit.  Do this only if you are really
     * concerned about RAM and object creation time
     * overhead for your platform.
     */
    private int _maxObjects = 0;

    /**
     * This is the default CGI Timeout in milliseconds.  For example,
     * a value of 10000 will tell the class to throw a
     * JavaCGIBridgeTimeOutException if the data is not retrieved within
     * 10 seconds of having initiated a data transfer.
     */
    private int _defaultTimeOut = 10000;

    /**
     * This is the default value for the ParseAsRaw
     * flag that will be assigned to JavaCGIBridge objects
     * when they are handed out from the pool.
     */
    private boolean _defaultParseAsRaw = false;

    /**
     * This is the default notification interval that
     * will be assigned to the JavaCGIBridge object when
     * it is handed out from the pool.
     */
    private int _defaultParsedNotifyInterval = 0;

    /**
     * This is the default notification interval that
     * will be assigned to the JavaCGIBridge object when
     * it is handed out from the pool.
     */
    private int _defaultRawNotifyInterval = 0;

    /**
     * This is the default start of data separator
     * that will be assigned to the JavaCGIBridge
     * object when it is handed out from the pool.
     */
    private String _defaultStartDataSeparator = "<!--start of data-->\n";

    /**
     * This is the default end of data separator
     * that will be assigned to the JavaCGIBridge
     * object when it is handed out from the pool.
     */
    private String _defaultEndDataSeparator = "<!--end of data-->\n";

    /** 
     * This is the default field separator that
     * will be assigned to the JavaCGIBridge object
     * when it is handed out from the pool.
     */
    private String _defaultFieldSeparator = "|";

    /**
     * This is the default record separator that
     * will be assigned to the JavaCGIBridge object
     * when it is handed out from the pool.
     */
    private String _defaultRecordSeparator = "\n";

    /**
     * Obtains a free JavaCGIBridge object or creates a
     * new one if none are available.
     *
     * @return JavaCGIBridge
     * @see #getJavaCGIBridgeExtension
     */
    public JavaCGIBridge getJavaCGIBridge() {
        return _getJavaCGIBridge(false);
    } // end of getJavaCGIBridge
   
    /**
     * Obtains a free JavaCGIBridgeExtension object or creates a
     * new one if none are available.
     *
     * @return JavaCGIBridgeExtension
     * @see #getJavaCGIBridge
     */
    public JavaCGIBridgeExtension getJavaCGIBridgeExtension() {
        return (JavaCGIBridgeExtension)_getJavaCGIBridge(true);
    } // end of getJavaCGIBridgeExtension


    /**
     * Obtains a JavaCGIBridge or JavaCGIBridgeExtension 
     * object from the pool depending on the type of 
     * object passed as a parameter.
     * <P>
     * Note, if you set an upper limit 
     * on the maximum number of objects the
     * JavaCGIBridgePool can hold, then this method will
     * block until an object is freed if no more slots 
     * are available.  It is recommended that you not
     * set an upper limit unless you have a very good 
     * architectural reason for doing so in your application.
     *
     * @param useExtension flag to indicate whether to return 
     *                     a JavaCGIBridge or JavaCGIBridgeExtension
     * @return JavaCGIBridge
     * @see #getJavaCGIBridge
     * @see #getJavaCGIBridgeExtension
     * @see #getMaxObjects
     * @see #setMaxObjects
     */
    private synchronized JavaCGIBridge _getJavaCGIBridge(boolean useExtension) {
        JavaCGIBridge jcb;

        int length = _javaCGIBridgeVector.size();
        for (int i = 0; i < length; i++) { 
            jcb = (JavaCGIBridge)_javaCGIBridgeVector.elementAt(i);
            if (jcb.getStatus() == jcb.IDLE && 
                ((jcb instanceof JavaCGIBridge && !useExtension)||
                 (jcb instanceof JavaCGIBridgeExtension && useExtension))) {
                jcb.setStatus(jcb.SETUP);
                // set up defaults
                jcb.setTimeOut(_defaultTimeOut);
                jcb.setParseAsRaw(_defaultParseAsRaw);
                jcb.setParsedNotifyInterval(_defaultParsedNotifyInterval);
                jcb.setRawNotifyInterval(_defaultRawNotifyInterval);
                jcb.setStartDataSeparator(_defaultStartDataSeparator);
                jcb.setEndDataSeparator(_defaultEndDataSeparator);
                jcb.setFieldSeparator(_defaultFieldSeparator);
                jcb.setRecordSeparator(_defaultRecordSeparator);
                jcb.deleteObservers(); // clear observer list
                return jcb; 
            }
            // Wait until an object gets freed up if we set up
            // an upper limit of the number of JavaCGIBridges that
            // can exist in the pool
            if (_maxObjects != 0 && i == length - 1 && _maxObjects <= length) {
                i = -1;
                Thread.yield();
            }
        }
 
        if (useExtension) {
            jcb = new JavaCGIBridgeExtension();
        } else {
            jcb = new JavaCGIBridge();
        }
        _javaCGIBridgeVector.addElement(jcb);

        return jcb;

    } // end of _getJavaCGIBridge

    /**
     * Removes a particular JavaCGIBridge object from
     * the pool.  If the status is not idle, the object
     * will not be removed.
     *
     * @param jcb JavaCGIBridge object to remove
     * @return boolean returns true if object was removed
     * @see #cleanPool
     */ 
    public synchronized boolean removeJavaCGIBridge(JavaCGIBridge jcb) {
        if (jcb.getStatus() != jcb.IDLE)
            return false;

        _javaCGIBridgeVector.removeElement(jcb);
        return true;
    } // end of removeJavaCGIBridge

    /**
     * Clears out the pool of JavaCGIBridge objects. Objects
     * not currently in use are removed from the pool.
     * Objects current in use are kept in the pool.
     *
     * @see #removeJavaCGIBridge
     */
    public synchronized void clearPool() {
        JavaCGIBridge jcb;

        int length = _javaCGIBridgeVector.size();

        for (int i = length - 1; i >= 0; i--) {
            jcb = (JavaCGIBridge)_javaCGIBridgeVector.elementAt(i);
            //if (jcb.getStatus() == jcb.IDLE) {
                _javaCGIBridgeVector.removeElement(jcb);
            //}
        }
    } // end of clearPool

    /**
     * Returns a value indicating the maximum number
     * of objects the JavaCGIBridgePool is allowed
     * to hold.  Zero (the default) indicates 
     * unlimited number of JavaCGIBridge objects can
     * be created.
     * 
     * @return int maximum number of JavaCGIBridge objects
     *             the JavaCGIBridgePool can hold
     * @see #setMaxObjects
     * @see #getJavaCGIBridge
     * @see #getJavaCGIBridgeExtension
     */
    public int getMaxObjects() { return _maxObjects; }

    /**
     * Sets the maximum number of objects the JavaCGIBridgePool
     * can hold. Zero (the default) indicates unlimited 
     * number of JavaCGIBridge objects can be created.
     *
     * @param max maximum number of JavaCGIBridge objects
     *            the JavaCGIBridgePool can hold
     * @see #getMaxObjects
     * @see #getJavaCGIBridge
     * @see #getJavaCGIBridgeExtension
     */
    public void setMaxObjects(int max) {
        _maxObjects = max;
    } // end of setMaxObjects

    /**
     * Returns a value indicating the maximum number
     * of objects the JavaCGIBridgePool keeps lying around
     * in the IDLE state.  It is useful to lower this
     * if you want to conserve RAM and you think there
     * might be a point in your software where many objects
     * would be created at once.  This might occur if many
     * asynchronous requests need to be serviced. Zero (the default)
     * indicates unlimited number of JavaCGIBridge IDLE objects
     * can be left hanging around.  This value is used when
     * the cleanPool() method tries to clean out unnecessary or 
     * timed out objects.
     *
     * @see #setMaxSpareObjects
     * @see #cleanPool
     */
    public int getMaxSpareObjects() { return _maxSpareObjects; }

    /**
     * Sets the maximum number of spare IDLE objects the
     * JavaCGIBridgePool keeps lying around.  It is useful
     * to lower this value if you want to conserve RAM and
     * you think there might be a point in your software where
     * many objects would be created at once which you don't
     * want to keep around for the life of the program
     *
     * @see #getMaxSpareObjects
     * @see #cleanPool
     */
    public void setMaxSpareObjects(int max) {
        _maxSpareObjects = max;
    } // end of setMaxSpareObjects

    /** 
     * Returns the time in milliseconds that a thread will
     * wait before waking up and seeing whether any
     * JavaCGIBridge objects in the pool need cleaning out.
     *
     * @return int time in miiliseconds for thread to wake up
     *             for cleaning up the JavaCGIBridgePool
     * @see #setCleanTime
     * @see #cleanPool
     */ 
    public int getCleanTime() { return _threadCleanTime; }

    /**
     * Sets the time between when the JavaCGIBridgePool's
     * thread will check if objects in the pool have
     * timed out and need to be cleaned.
     *
     * If the time is 0 (default), then no thread cleans
     * up automatically. If the time is positive, then this
     * method actually creates and starts the thread that
     * watches the objects for cleanup.
     * 
     * @param cleanUpTime the time in milliseconds before
     *                    checking objects for clean up.
     * @see #getCleanTime
     * @see #cleanPool
     */
    public void setCleanTime(int cleanUpTime) {
        if (cleanUpTime == 0) {
            if (_threadCleaner != null) {
                _threadCleaner.stop();
                _threadCleaner = null;
            }
            _threadCleanTime = 0; 
        } else {
            _threadCleanTime = cleanUpTime;
            if (_threadCleaner == null) {
                _threadCleaner = new Thread(this);
                _threadCleaner.start();
            } 
        }

    } // end of setCleanTime

    /** 
     * Thread that cleans up timed out 
     * JavaCGIBridge objects.  It wakes
     * up at a user defined interval and
     * checks the objects for time out then.
     * Note that this thread will not start unless
     * setCleanTime() has been called with a
     * time other than 0.
     *
     * @see #getCleanTime
     * @see #setCleanTime
     */
    public void run() {
        while (true) {
            try {
                Thread.sleep(_threadCleanTime);
            } catch (InterruptedException e) {
                // Ignore it
            }
            cleanPool();
        }
    } // end of run

    /**
     * Checks the pool to see if any JavaCGIBridge
     * objects have timed out. If they have, it shuts
     * down the object, notifies the observers of the
     * timeout exception, and then gets removed from
     * the pool.
     * <P>
     * This method is generally called from within
     * the pool's clean up thread.  However, this
     * method has been made public in case you want
     * to deterministically call this routine at
     * various stages in your program.
     * <P>
     * The cleanPool will also make sure that no
     * more than the maxSpareObjects will exist
     * in the pool. 
     *
     * @see #removeJavaCGIBridge
     * @see #getCleanTime
     * @see #setCleanTime
     * @see #clearPool
     * @see #getMaxSpareObjects
     * @see #setMaxSpareObjects
     */
    public synchronized void cleanPool() {
        JavaCGIBridge jcb;
        long currentTime = System.currentTimeMillis();

        int length = _javaCGIBridgeVector.size();
        for (int i = length - 1; i >= 0; i--) {
            jcb = (JavaCGIBridge)_javaCGIBridgeVector.elementAt(i);
            if (jcb.getStatus() == jcb.PROCESSING &&
                jcb._threadCompleted == false &&
                jcb._threadTimeOut < (currentTime - jcb._threadBaseTime)) {
                jcb._thread.stop();
                jcb.notifyObservers(new JavaCGIBridgeNotify(new JavaCGIBridgeTimeOutException(), false));
                _javaCGIBridgeVector.removeElement(jcb);
            }
        }

        length = _javaCGIBridgeVector.size();
        int removedCount = 0;
        for (int i = length - 1; i >= 0; i--) {
           jcb = (JavaCGIBridge)_javaCGIBridgeVector.elementAt(i);
            if (_maxSpareObjects > 0 &&
                jcb.getStatus() == jcb.IDLE &&
                       _maxSpareObjects < (length - removedCount)) {
                _javaCGIBridgeVector.removeElement(jcb);
                removedCount++;
            }
        }

    } // end of cleanPool

    /**
     * Returns the default communication time out
     * in milliseconds for the class. The default is
     * initially set to 10 seconds.
     *
     * When the object retrieves data from a URL, it must get
     * the data within timeout milliseconds or a
     * JavaCGIBridgeTimeOutException is thrown.
     *
     * @return default communication time out in milliseconds
     * @see #setDefaultTimeOut
     */
    public int getDefaultTimeOut() {
        return _defaultTimeOut;
    }

    /**
     * Sets the default communication time out in
     * milliseconds for the class.  The default is initially
     * set to 10 seconds.
     *
     * When the object retrieves data from a URL, it must get
     * the data within timeout milliseconds or a
     * JavaCGIBridgeTimeOutException is thrown.
     *
     * @param t default communication time out in milliseconds
     * @see #getDefaultTimeOut
     */
    public void setDefaultTimeOut(int t) {
        _defaultTimeOut = t;
    }

    /**
     * Returns the default ParseAsRaw value to be assigned
     * to the JavaCGIBridge object when it is handed out from
     * the pool. The default value for this parameter is false.
     *
     * @return default ParseAsRaw value to be assigned
     * @see #setDefaultParseAsRaw
     */
    public boolean getDefaultParseAsRaw() { return _defaultParseAsRaw; }

    /**
     * Sets the default ParseAsRaw value to be assigned
     * to the JavaCGIBridge object when it is handed out
     * from the pool.  The default value for this parameter
     * is initially set to false.
     *
     * @param b default ParseAsRaw value to be assigned
     * @see #getDefaultParseAsRaw
     */
    public void setDefaultParseAsRaw(boolean b) { _defaultParseAsRaw = b; }

    /**
     * Returns the default ParsedNotifyInterval value to be assigned
     * to the JavaCGIBridge object when it is handed out from
     * the pool. The default value for this parameter is 0.
     *
     * @return default ParsedNotifyInterval value to be assigned
     * @see #setDefaultParsedNotifyInterval
     */
    public int getDefaultParsedNotifyInterval() { return _defaultParsedNotifyInterval; }
   
    /**
     * Sets the default ParsedNotifyInterval value to be assigned
     * to the JavaCGIBridge object when it is handed out
     * from the pool.  The default value for this parameter
     * is initially set to 0.
     *
     * @param i default ParsedNotifyInterval value to be assigned
     * @see #getDefaultParsedNotifyInterval
     */
    public void setDefaultParsedNotifyInterval(int i) { _defaultParsedNotifyInterval = i; }

    /**
     * Returns the default RawNotifyInterval value to be assigned
     * to the JavaCGIBridge object when it is handed out from
     * the pool. The default value for this parameter is 0.
     *
     * @return default RawNotifyInterval value to be assigned
     * @see #setDefaultRawNotifyInterval
     */
    public int getDefaultRawNotifyInterval() { return _defaultRawNotifyInterval; }

    /**
     * Sets the default RawNotifyInterval value to be assigned
     * to the JavaCGIBridge object when it is handed out
     * from the pool.  The default value for this parameter
     * is initially set to 0.
     *
     * @param i default RawNotifyInterval value to be assigned
     * @see #getDefaultRawNotifyInterval
     */
    public void setDefaultRawNotifyInterval(int i) { _defaultRawNotifyInterval = i; }

    /**
     * Returns the default Start of Data separator value to be assigned
     * to the JavaCGIBridge object when it is handed out from
     * the pool. The default value for this parameter is "&LT;!--start of data-->\n".
     *
     * @return default start of data separator value to be assigned
     * @see #setDefaultStartDataSeparator
     */
    public String getDefaultStartDataSeparator() { return _defaultStartDataSeparator; }

    /**
     * Sets the default Start of Data value to be assigned
     * to the JavaCGIBridge object when it is handed out
     * from the pool.  The default value for this parameter
     * is initially set to "&LT;!--start of data-->\n".
     *
     * @param s default start of data value to be assigned
     * @see #getDefaultStartDataSeparator
     */
    public void setDefaultStartDataSeparator(String s) { _defaultStartDataSeparator = s; }
 
    /**
     * Returns the default End of Data separator value to be assigned
     * to the JavaCGIBridge object when it is handed out from
     * the pool. The default value for this parameter is "&LT;!--end of data-->".
     *
     * @return default end of data value to be assigned
     * @see #setDefaultEndData
     */
    public String getDefaultEndDataSeparator() { return _defaultEndDataSeparator; }

    /**
     * Sets the default End of Data value to be assigned
     * to the JavaCGIBridge object when it is handed out
     * from the pool.  The default value for this parameter
     * is initially set to "&LT;!--end of data-->".
     *
     * @param s default end of data value to be assigned
     * @see #getDefaultEndData
     */
    public void setDefaultEndDataSeparator(String s) { _defaultEndDataSeparator = s; }

    /**
     * Returns the default field separator value to be assigned
     * to the JavaCGIBridge object when it is handed out from
     * the pool. The default value for this parameter is "|".
     *
     * @return default field separator value to be assigned
     * @see #setDefaultFieldSeparator
     */
    public String getDefaultFieldSeparator() { return _defaultFieldSeparator; }

    /**
     * Sets the default field separator value to be assigned
     * to the JavaCGIBridge object when it is handed out
     * from the pool.  The default value for this parameter
     * is initially set to "|".
     *
     * @param s default field separator value to be assigned
     * @see #getDefaultFieldSeparator
     */
    public void setDefaultFieldSeparator(String s) { _defaultFieldSeparator = s; }

    /**
     * Returns the default record separator value to be assigned
     * to the JavaCGIBridge object when it is handed out from
     * the pool. The default value for this parameter is "\n".
     *
     * @return default record separator value to be assigned
     * @see #setDefaultRecordSeparator
     */
    public String getDefaultRecordSeparator() { return _defaultRecordSeparator; }

    /**
     * Sets the default record separator value to be assigned
     * to the JavaCGIBridge object when it is handed out
     * from the pool.  The default value for this parameter
     * is initially set to "\n".
     *
     * @param s default record separator value to be assigned
     * @see #getDefaultRecordSeparator
     */
    public void setDefaultRecordSeparator(String s) { _defaultRecordSeparator = s; }
    
} // End of JavaCGIBridgePool class
