// ========================================================================
// $Id: Pool.java,v 1.13 2005/08/13 00:01:28 gregwilkins Exp $
// Copyright 1999-2004 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed 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 org.mortbay.util;

import java.io.Serializable;
import java.util.HashMap;

import org.apache.commons.logging.Log;
import org.mortbay.log.LogFactory;

/* ------------------------------------------------------------ */
/** A pool of Objects.
 * <p>
 * @version $Id: Pool.java,v 1.13 2005/08/13 00:01:28 gregwilkins Exp $
 * @author Juancarlo Aņez <juancarlo@modelistica.com>
 * @author Greg Wilkins <gregw@mortbay.com>
 */
public class Pool
    implements LifeCycle, Serializable
{
    private static Log log = LogFactory.getLog(Pool.class);

    /* ------------------------------------------------------------ */
    static int __max = 
        Integer.getInteger("POOL_MAX",256).intValue();
    static int __min =
        Integer.getInteger("POOL_MIN",2).intValue();

    
    /* ------------------------------------------------------------ */
    public static interface PondLife
    {
        int getID();
        void enterPool(Pool pool,int id);
        void poolClosing();
        void leavePool();
    }
    
    /* ------------------------------------------------------------------- */
    static HashMap __nameMap=new HashMap();
    
    /* ------------------------------------------------------------------- */
    private int _max = __max;
    private int _min = __min;
    private String _name;
    private String _className;
    private int _maxIdleTimeMs=10000;
    private HashMap _attributes = new HashMap();
    
    private transient Class _class;
    private transient PondLife[] _pondLife; // Array of pondlife indexed by ID.
    private transient int[] _index; // Mapping of pondlife IDs.  Entries with indexes <_available are idle IDs.  Entries with indexes>_size are unused IDs.
    private transient int _size;
    private transient int _available;
    private transient int _running=0;
    private transient long _lastShrink=0;  // control shrinking to once per maxIdleTime
    
    /* ------------------------------------------------------------------- */
    public static Pool getPool(String name)
    {
        return (Pool)__nameMap.get(name);
    }

    /* ------------------------------------------------------------------- */
    /* Construct
     */
    public Pool() 
    {}

    /* ------------------------------------------------------------ */
    /** 
     * @return The name of the Pool.
     */
    public String getPoolName()
    {
        return _name;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @param name The pool name
     * @exception IllegalStateException If the name is already defined.
     */
    public void setPoolName(String name)
        throws IllegalStateException
    {
        synchronized(this)
        {
            synchronized(Pool.class)
            {
                if (_name!=null && !_name.equals(name))
                    __nameMap.remove(_name);
                if (__nameMap.containsKey(name))
                    throw new IllegalStateException("Name already exists");
                _name=name;
                
                __nameMap.put(_name,this);
            }
        }
    }
    
    /* ------------------------------------------------------------ */
    /** Set the class.
     * @param poolClass The class
     * @exception IllegalStateException If the pool has already
     *            been started.
     */
    public void setPoolClass(Class poolClass)
        throws IllegalStateException
    {
        synchronized(this)
        {
            if (_class!=poolClass)
            {
                if (_running>0)
                    throw new IllegalStateException("Thread Pool Running");
                if (!PondLife.class.isAssignableFrom(poolClass))
                    throw new IllegalArgumentException("Not PondLife: "+poolClass);
                _class=poolClass;
                _className=_class.getName();
            }
        }
    }
    

    /* ------------------------------------------------------------ */
    public Class getPoolClass()
    {
        return _class;
    }

    /* ------------------------------------------------------------ */
    public int getMinSize()
    {
        return _min;
    }
    
    /* ------------------------------------------------------------ */
    public void setMinSize(int min)
    {
        _min=min;
    }
    
    /* ------------------------------------------------------------ */
    public int getMaxSize()
    {
        return _max;
    }
    
    /* ------------------------------------------------------------ */
    public void setMaxSize(int max)
    {
        _max=max;
    }
    
    /* ------------------------------------------------------------ */
    public int getMaxIdleTimeMs()
    {
        return _maxIdleTimeMs;
    }
    
    /* ------------------------------------------------------------ */
    public void setMaxIdleTimeMs(int maxIdleTimeMs)
    {
        _maxIdleTimeMs=maxIdleTimeMs;
    }
    
    /* ------------------------------------------------------------ */
    public void setAttribute(String name,Object value)
    {
        _attributes.put(name,value);
    }
    
    /* ------------------------------------------------------------ */
    public Object getAttribute(String name)
    {
        return _attributes.get(name);
    }
    
    /* ------------------------------------------------------------ */
    public boolean isStarted()
    {
        return _running>0 && _pondLife!=null;
    }
    
    /* ------------------------------------------------------------ */
    public int size()
    {
        return _size;
    }
    
    /* ------------------------------------------------------------ */
    public int available()
    {
        return _available;
    }
    
    
    /* ------------------------------------------------------------ */
    public void start()
        throws Exception
    {
        synchronized(this)
        {
            _running++;
            if (_running>1)
                return;

	    	if (_min >= _max || _max<1)
				throw new IllegalStateException("!(0<=min<max)");

            // Start the threads
            _pondLife=new PondLife[_max];
            _index=new int[_max];
            _size=0;
            _available=0;
            
            for (int i=0;i<_max;i++)
                _index[i]=i;
            for (int i=0;i<_min;i++)
                newPondLife();
        }
    }

    /* ------------------------------------------------------------ */
    public void stop()
        throws InterruptedException
    {
        synchronized(this)
        {
            _running--;
            if (_running>0)
                return;
            notifyAll();
        }
        
        if (_pondLife!=null && _size>0)
        {
            for (int i=0;i<_pondLife.length;i++)
                closePondLife(i);
            Thread.yield();
            for (int i=0;i<_pondLife.length;i++)
                stopPondLife(i);
        }

        synchronized(this)
        {
            _pondLife=null;
            _index=null;
            _size=0;
            _available=0;
        }
    }
    
    /* ------------------------------------------------------------ */
    public PondLife get(int timeoutMs)
        throws Exception
    {
        PondLife pl=null;
        
        // Defer to other threads before locking 
        if (_available<_min)
            Thread.yield();
        
        int new_id=-1;
        
        // Try to get pondlife without creating new one.
        synchronized(this)
        {
            // Wait if none available.
            if (_running>0 && _available==0 && _size==_pondLife.length && timeoutMs>0)
                wait(timeoutMs);

            // If still running
            if (_running>0)
            {
                // if pondlife available
                if (_available>0)
                {
                    int id=_index[--_available];
                    pl=_pondLife[id];
                }
                else if (_size<_pondLife.length)
                {
                    // Reserve spot for a new one
                    new_id=reservePondLife(false);
                }
            }

            // create reserved pondlife
            if (pl==null && new_id>=0)
                pl=newPondLife(new_id);
        }

        return pl;
    }
    
    
    /* ------------------------------------------------------------ */
    public void put(PondLife pl)
        throws InterruptedException
    {
        int id=pl.getID();
        
        synchronized(this)
        {
            if (_running==0)
                stopPondLife(id);
            else if (_pondLife[id]!=null)
            {
                _index[_available++]=id;
                notify();
            }
        }
        
    }
    
    /* ------------------------------------------------------------ */
    public void shrink()
        throws InterruptedException
    {
        if (_running==0)
            return;

        synchronized(this)
        {
            // If we have a maxIdleTime, then only shrink once per period.
            if (_maxIdleTimeMs>0)
            {
                long now=System.currentTimeMillis();
                if ((now-_lastShrink)<_maxIdleTimeMs)
                    return; // don't shrink
                _lastShrink=now;
            }
            
            // shrink if we are running and have available threads and we are above minimal size
            if (_running>0 && _available>0 && _size>_min)
                stopPondLife(_index[--_available]);
        }
    }

    /* ------------------------------------------------------------ */
    private int reservePondLife(boolean available)
        throws Exception
    {
        int id=-1;
        synchronized(this)
        {
            id=_index[_size++];
            if (available)
                _index[_available++]=id;
        }
        return id;
    }
    
    /* ------------------------------------------------------------ */
    private PondLife newPondLife(int id)
        throws Exception
    {
        PondLife pl= (PondLife)_class.newInstance();
        _pondLife[id]=pl;
        pl.enterPool(this,id);
        return pl;
    }

    /* ------------------------------------------------------------ */
    private PondLife newPondLife()
        throws Exception
    {
        return newPondLife(reservePondLife(true));
    }
    
    /* ------------------------------------------------------------ */
    private void closePondLife(int id)
    {
        if (_pondLife[id]!=null)
            _pondLife[id].poolClosing();
    }
    
    /* ------------------------------------------------------------ */
    private void stopPondLife(int id)
    {
        PondLife pl = null;
        synchronized(this)
        {
            pl=_pondLife[id];
            if (pl!=null)
            {
                _pondLife[id]=null;
                _index[--_size]=id;
                if (_available>_size)
                    _available=_size;
            }
        }
        if (pl!=null)
            pl.leavePool();
    }

    /* ------------------------------------------------------------ */
    public void dump(String msg)
    {
        StringBuffer pond=new StringBuffer();
        StringBuffer avail=new StringBuffer();
        StringBuffer index=new StringBuffer();
        
         pond.append("pond: ");
         avail.append("avail:");
         index.append("index:");
        
        for (int i=0;i<_pondLife.length;i++)
        {
            if (_pondLife[i]==null)
                pond.append("   ");
            else
            {
                pond.append(' ');
                StringUtil.append(pond,(byte)i,16);
            }
            
            if (i==_size)
                avail.append(i==_available?" AS":"  S");
            else
                avail.append(i==_available?" A ":"   ");
            
            index.append(' ');
            StringUtil.append(index,(byte)_index[i],16);
        }

        System.err.println();
        System.err.println(msg);
        System.err.println(pond);
        System.err.println(avail);
        System.err.println(index);
    }
    
    
    /* ------------------------------------------------------------ */
    private void readObject(java.io.ObjectInputStream in)
        throws java.io.IOException, ClassNotFoundException
    {
        in.defaultReadObject();
        if (_class==null || !_class.getName().equals(_className))
        {
            try
            {
                setPoolClass(Loader.loadClass(Pool.class,_className));
            }
            catch (Exception e)
            {
                log.warn(LogSupport.EXCEPTION,e);
                throw new java.io.InvalidObjectException(e.toString());
            }
        }
    }
}
