/*
 * @(#)ObjectCache.java      0.9.0 04/11/2001 - 13:21:11
 *
 * Copyright (C) 2001-2003 Matt Albrecht
 * groboclown@users.sourceforge.net
 * http://groboutils.sourceforge.net
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a
 *  copy of this software and associated documentation files (the "Software"),
 *  to deal in the Software without restriction, including without limitation
 *  the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *  and/or sell copies of the Software, and to permit persons to whom the
 *  Software is furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 *  DEALINGS IN THE SOFTWARE.
 */

package net.sourceforge.groboutils.util.datastruct.v1;


/**
 * An object cache which allows for objects to be added and removed.
 * If the cache is empty when an object is requested, the object type
 * is created and returned.  There can be a maximum size specified for
 * the pending object list - if an object is retrieved by the cache,
 * and the list is beyond its size, then the object is thrown away.
 * By default, the maximum size is unlimited.
 * <P>
 * If the cache should not create objects, then a ObjectCreator should
 * not be given to the cache.
 * <P>
 * Alternatively, you can specify that the cache not create objects and
 * instead wait for the objects to be retrieved.
 * <P>
 * Care has been taken to keep this synchronized across threads.
 *
 * @author     Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
 * @since      April 11, 2001 (0.9.0 Alpha)
 * @version    $Date: 2003/05/23 20:55:43 $
 */
public class ObjectCache
{
    /**
     * The size to use when you want to specify an unlimited cache size.
     */
    public static final int UNLIMITED_SIZE = -1;
    
    
    /**
     * An interface which needs to be implemented and given to the
     * cache in order to create new instances.
     */
    public static interface ObjectCreator
    {
        /**
         * Called when a new object needs to be created.
         *
         * @return the newly created object
         */
        public Object createObject();
    }
    
    
    /**
     * A default object creator - given a Class object, it attempts
     * to create a new Object using the default constructor.
     */
    public static class DefaultObjectCreator implements ObjectCreator
    {
        private Class clazz;
        
        /**
         * Defines the class to create an instance in the creation method;
         * the class <i>must</i> have a no-argument constructor for this
         * class to work.
         */
        public DefaultObjectCreator( Class clazz )
        {
            if (clazz == null)
            {
                throw new IllegalArgumentException( "No null args" );
            }
            this.clazz = clazz;
        }
        
        public Object createObject()
        {
            try
            {
                return clazz.newInstance();
            }
            catch (Exception e)
            {
                return null;
            }
        }
    }
    
    
    
    /**
     * We use a synch queue to optimize the store of objects
     */
    private SynchQueue cache = new SynchQueue();
    
    /**
     * We also need to keep the object created.
     */
    private ObjectCreator creator;
    
    /**
     * Maximum size of the cache.
     */
    private int maxSize = UNLIMITED_SIZE;

    
    /**
     * Records the number of overflows - for performance tuning.
     */
    private int overflowCount = 0;

    
    /**
     * Records the number of underflows - for performance tuning.
     */
    private int underflowCount = 0;
    
    
    //-----------------------------------------------------
    // Constructors
    
    
    /**
     * Create a new ObjectCache without an object creator.
     */
    public ObjectCache()
    {
        // do nothing
    }
    
    
    /**
     * Create a new ObjectCache without an object creator, and
     * sets the maximum number of objects to keep waiting in the cache.
     */
    public ObjectCache( int maxSize )
    {
        setMaxSize( maxSize );
    }
    
    
    /**
     * Create a new ObjectCache. This uses the given creator to
     * create new objects for the cache.
     */
    public ObjectCache( ObjectCreator creator )
    {
        setObjectCreator( creator );
    }
    
    
    /**
     * Create a new ObjectCache. This uses the given Class to
     * create new objects for the cache, using its default constructor.
     */
    public ObjectCache( Class creator )
    {
        setClassCreator( creator );
    }
    
    
    /**
     * Create a new ObjectCache. This uses the given creator to
     * create new objects for the cache, and sets the internal maximum
     * number of elements to keep waiting.
     */
    public ObjectCache( ObjectCreator creator, int maxSize )
    {
        setMaxSize( maxSize );
        setObjectCreator( creator );
    }
    
    
    /**
     * Create a new ObjectCache. This uses the given Class to
     * create new objects for the cache, using its default constructor,
     * and sets the internal maximum
     * number of elements to keep waiting.
     */
    public ObjectCache( Class creator, int maxSize )
    {
        setMaxSize( maxSize );
        setClassCreator( creator );
    }
    
    
    /**
     * Create a new ObjectCache. This uses the given creator to
     * create new objects for the cache, and sets the internal maximum
     * number of elements to keep waiting.
     *
     * @param fill <tt>true</tt> if the cache should be filled at
     *      construction time, or <tt>false</tt> if it should be empty
     *      initially.
     */
    public ObjectCache( ObjectCreator creator, int maxSize, boolean fill )
    {
        setMaxSize( maxSize );
        setObjectCreator( creator );
        if (fill)
        {
            fillCache();
        }
    }
    
    
    /**
     * Create a new ObjectCache. This uses the given Class to
     * create new objects for the cache, using its default constructor,
     * and sets the internal maximum
     * number of elements to keep waiting.
     *
     * @param fill <tt>true</tt> if the cache should be filled at
     *      construction time, or <tt>false</tt> if it should be empty
     *      initially.
     */
    public ObjectCache( Class creator, int maxSize, boolean fill )
    {
        setMaxSize( maxSize );
        setClassCreator( creator );
        if (fill)
        {
            fillCache();
        }
    }
    
    

    
    //-----------------------------------------------------
    // Public methods
    
    
    /**
     * Adds an element to the end of the queue.  If the list is empty,
     * then the next waiting thread is woken up.  If the list has one or
     * fewer elements, this this method will block any access to the queue,
     * otherwise this only blocks access to adding to the list.
     *
     * @param o the object to place at the end of the list.
     */
    public void putBack( Object o )
    {
        if (o == null)
        {
            // throw an exception - can't insert a null
            throw new IllegalArgumentException(
                "Null objects cannot be added into the cache" );
        }
        if (this.maxSize > 0)
        {
            if (this.cache.size() >= this.maxSize)
            {
// System.out.println("ObjectCache.putBack: caused overflow");
                // ignore the object - we're full
                this.overflowCount++;
                return;
            }
        }
        this.cache.enqueue( o );
    }

    /**
     * Retrieves a cached element. If the cache is empty, and no
     * creator is known, then <tt>null</tt> is returned. If the cache is
     * empty, and a creator is known, then a new object is created and
     * returned.
     * <P>
     * Synchronized so that the time between the isEmpty check and the
     * pull does not have another thread pulling out an instance. Only
     * the get needs to be synchronized, so as to not mess with the
     * checks.
     */
    public Object get()
    {
        // only synchronize the necesary code.
        synchronized( this )
        {
            if (!this.cache.isEmpty())
            {
                try
                {
// System.out.println("ObjectCache.get(): not empty, dequeueing");
                    return this.cache.dequeue();
                }
                catch (InterruptedException ie)
                {
                    // should never happen - but it might!
                    throw new IllegalStateException("encountered an interrupt: "+ie);
                }
            }
        }
        // an underflow
        // doesn't need to be synchronized
//System.err.println("ObjectCache.get(): underflow, calling createObject");
//(new Throwable()).printStackTrace();
        this.underflowCount++;
        return createObject();
    }
    
    
    /**
     * Retrieves a cached element. If the cache is empty, then one of several
     * things can happen, based on the time passed in:
     *  <UL>
     *      <LI><B>&lt; 0:</B> <tt>null</tt> is immediately returned. This is
     *          the identical behavior to calling {@link #get()} with
     *          a <tt>null</tt> creator.
     *      <LI><B>0:</B> the routine will wait indefinitely until
     *          an object is {@link #putBack( Object )} into the cache.
     *      <LI><B>&gt; 0:</B> the routine will wait up to the given number
     *          of milliseconds for another object to be
     *          {@link #putBack( Object )}. If by that time the cache is still
     *          empty, then <tt>null</tt> is returned.
     *  </UL>
     * <P>
     * Important parts of the code are synchronized.
     */
    public Object get( long millisWaitTime )
            throws InterruptedException
    {
        // only synchronize the necesary code.
        synchronized( this )
        {
            // normal behavior
            if (!this.cache.isEmpty())
            {
                try
                {
// System.out.println("ObjectCache.get( "+millisWaitTime+" ): not empty, dequeueing");
                    return this.cache.dequeue();
                }
                catch (InterruptedException ie)
                {
                    // should never happen - but it might!
                    throw new IllegalStateException("encountered an interrupt: "+ie);
                }
            }
        }
        // an underflow
        // doesn't need to be synchronized
//System.out.println("ObjectCache.get( "+millisWaitTime+" ): underflow...");
//(new Throwable()).printStackTrace();
        this.underflowCount++;
        
        return this.cache.dequeue( millisWaitTime );
    }
    
    
    /**
     * Retrieves the number of "overflows" encountered. An overflow occurs
     * when the cache is full and a {@link #putBack( Object )} is called.
     */
    public int getOverflows()
    {
        return this.overflowCount;
    }
    
    
    /**
     * Retrieves the number of "underflows" encountered. An underflow occurs
     * when the cache is empty and a {@link #get()} is called.
     */
    public int getUnderflows()
    {
        return this.underflowCount;
    }
    
    
    /**
     * Resets the internal maximum number of objects that the cache can
     * hold. Note that it does not immediately clear out the extra objects -
     * that is naturally cleared by the {@link #putBack( Object )} ignoring
     * overflows.
     */
    public void setMaxSize( int size )
    {
        this.maxSize = size;
    }
    
    
    /**
     * 
     */
    public int getMaxSize()
    {
        return this.maxSize;
    }
    
    
    /**
     * Sets the internal cache-underflow Object creator.
     */
    public void setObjectCreator( ObjectCreator creator )
    {
        this.creator = creator;
    }
    
    
    /**
     * Creates a new DefaultObjectCreator based on the given class.
     */
    public void setClassCreator( Class creator )
    {
        ObjectCreator oc = null;
        if (creator != null)
        {
            oc = new DefaultObjectCreator( creator );
        }
        setObjectCreator( oc );
    }
    
    
    /**
     * Create a new object and put it into the cache.  This follows the
     * rules of {@link #putBack( Object )}.
     */
    public void addObject()
    {
        Object o = createObject();
        if (o != null)
        {
            putBack( o );
        }
    }
    
    
    /**
     * Fills the cache to its maximum.  If there is no maximum or there
     * is no creator, then nothing is done.
     */
    public void fillCache()
    {
        if (this.creator != null)
        {
            // even if creation doesn't work, go ahead and increment the count
            // - this prevents an infinite loop
            for (int i = this.cache.size(); i < this.maxSize; i++)
            {
                addObject();
            }
        }
    }
    
    //-----------------------------------------------------
    // Protected methods
    
    
    /**
     * Generates an Object for the cache.  May return null.
     */
    protected Object createObject()
    {
        Object o = null;
        if (this.creator != null)
        {
// System.out.println("ObjectCache.createObject(): calling creator's createObject");
            o = this.creator.createObject();
        }
        return o;
    }
}

