/* ======================================================================
The Bodington System Software License, Version 1.0
  
Copyright (c) 2001 The University of Leeds.  All rights reserved.
  
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1.  Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2.  Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

3.  The end-user documentation included with the redistribution, if any,
must include the following acknowledgement:  "This product includes
software developed by the University of Leeds
(http://www.bodington.org/)."  Alternately, this acknowledgement may
appear in the software itself, if and wherever such third-party
acknowledgements normally appear.

4.  The names "Bodington", "Nathan Bodington", "Bodington System",
"Bodington Open Source Project", and "The University of Leeds" must not be
used to endorse or promote products derived from this software without
prior written permission. For written permission, please contact
d.gardner@leeds.ac.uk.

5.  The name "Bodington" may not appear in the name of products derived
from this software without prior written permission of the University of
Leeds.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO,  TITLE,  THE IMPLIED WARRANTIES 
OF QUALITY  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO 
EVENT SHALL THE UNIVERSITY OF LEEDS OR ITS CONTRIBUTORS BE LIABLE FOR 
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
=========================================================

This software was originally created by the University of Leeds and may contain voluntary 
contributions from others.  For more information on the Bodington Open Source Project, please 
see http://bodington.org/

====================================================================== */
package org.bodington.pool;

/**
 * A very simple object pool that stored the pooled objects in an array.
 *
 * @author Jon Maber
 * @author buckett
 */
public class ObjectPool {

	/**
	 * The status of slots that are available to be claimed.
	 */
	public static final int STATUS_IDLE		=0;
	
	/**
	 * The status of slots that have been claimed and are in use.
	 */
	public static final int STATUS_BUSY		=1;
	
	/**
	 * The status of slots that are moving from busy to idle.
	 */
	public static final int STATUS_FREEING	=2;
	
	/**
	 * The status of slots that are moving from idle to busy.
	 */
	public static final int STATUS_GETTING	=3;
	
	private Object[] pool;
	private int[] status;
	
	/** 
	 * Keeps track of number of slots free.
	 * Updated in synchronized code.
	 */
	protected int free_spaces;
	
	/**
	 * The default constructor leaves the pool uninitialised.  A subclass
	 * constructor calling this constructor will have to call init() after.
	 */
	protected ObjectPool()
	{
		pool = null;
	}
	
	public ObjectPool(int size) throws ObjectPoolException
	{
		super();
		init(size);
	}
	
	protected void init (int size) throws ObjectPoolException
	{
		status = new int[size];
		pool = new Object[size];
		
		try
		{
			for ( int i=0; i<size; i++ )
			{
				status[i] = STATUS_IDLE;
				pool[i] = createObject();
			}
		}
		catch ( Exception ex )
		{
			throw new ObjectPoolException( 
					ObjectPoolException.REASON_CREATE_OBJECT_FAILED, 
					ex, 
			"Failed to construct ObjectPool." );
		}
		
		free_spaces = size;
	}

	/**
	 * This method instantiates an object of class Object.  A
	 * subclass should override this method to instantiate an
	 * object of the appropriate class.
	 * 
	 * @return An object to place in the pool.
	 * @exception java.lang.Exception Any type of exception could be thrown.
	 */
	protected Object createObject() throws Exception {
		return new Object();
	}

	/**
	 * In LimitedObjectPool this method does nothing but return the same
	 * object that was passed in but subclasses would overide this
	 * method and use it to prepare the object for use.  It is
	 * called from getObject().  If the object cannot be prepared
	 * for use this method can instantiate and prepare for use a
	 * new object - the return object need not be the same one that
	 * was passed as a parameter.
	 * 
	 * @param o The object to be prepared for use passed in by the getObject method.
	 * @return The object from the pool, ready for use.
	 * @exception java.lang.Exception Any type of exception could be thrown.
	 */
	protected Object initialiseObject(Object o) throws Exception {
		return o;
	}

	/**
	 * In LimitedObjectPool this method does nothing.  Subclasses
	 * of LimitedObjectPool override this method to "clean up" the
	 * object just before it is returned to the pool.
	 * 
	 * @param o The object that is being returned to the pool of available
	 * objects.
	 * @exception java.lang.Exception Any type of exception could be thrown.
	 */
	protected void poolObject(Object o) throws Exception {
	}
	
	public Object getObject() throws ObjectPoolException
	{
		Object fresh_object;
		boolean inited = false;
		
		if ( pool == null )
			throw new ObjectPoolException( ObjectPoolException.REASON_NOT_INITIALISED, null,
			"The Object Pool is not initialised." );
		
		for ( int i=0; i<pool.length; i++ )
		{
			// synchronized block means we wait in case another thread is
			// changing/examining the status information.
			// This synchronized block doesn't call anything that might
			// cause exceptions or errors.  It consists of simple
			// equality checks, assignments and arithmatic on primative
			// data types.
			synchronized ( this )
			{
				// only idle slots are of interest - in particular
				// if another thread has already decided to prepare
				// this slot for use its status will have changed to
				// STATUS_GETTING.
				if ( status[i] != STATUS_IDLE )
					continue;
				
				
				// while still blocking other threads claim this slot
				status[i] = STATUS_GETTING;
				// now determined to give this slot to caller
				
				
				// keep track of how many slots are now free
				free_spaces--;
			}
			
			
			try
			{
				// this is here in case the initialise() method is 
				// rewritten not to create an object for each slot.
				// This is currently redundant.
				if ( pool[i] == null )
					pool[i] = createObject();
				
				// Prepare object for use by caller - most likely
				// that this method will be overridden in useful
				// subclasses of LimitedObjectPool.
				fresh_object = initialiseObject( pool[i] );
				// It is possible that the pooled object was rejected
				// and a new instance created during this preparation
				// so the new instance must be referenced in the pool.
				if ( fresh_object != pool[i] )
					pool[i] = fresh_object;
				// indicate that the object is properly initialised
				// to help decide what to do in the finally clause
				// below.
				inited=true;

			}
			catch ( Exception ex )
			{
				// Subclasses of LimitedObjectPool could throw all sorts of
				// exception in the initialiseObject method.  Here
				// an ObjectPoolException is constructed and thrown
				// based on the original exception.
				throw new ObjectPoolException(
						ObjectPoolException.REASON_INIT_OBJECT_FAILED,
						ex,
				"Unable to initialise Object in Pool." );
			}
			finally  // this clause MUST execute regardless of exceptions
			{
				// synchronize block means we wait in case another thread is
				// changing the status
				synchronized ( this )
				{
					// Either mark the slot as busy or return it to the
					// pool of idle slots depending on whether initialisation
					// of the object was completed.
					if ( inited )
						status[i] = STATUS_BUSY;	
					else
						status[i] = STATUS_IDLE;
				}
			}
			
			// if no exception was thrown return the object
			return pool[i];
		}
		return null;
	}
	
	/**
	 * Attempts to return a borrowed object to the pool.
	 * @param o The object to return to the pool. It must be equal to the
	 * object that was borrowed from the pool for this to work.
	 * @throws ObjectPoolException If the pool hasn't been setup. If there is a 
	 * problem running {@link #poolObject(Object)}. If the object can't be found
	 * in the pool.
	 */
	public void freeObject( Object o )
	throws ObjectPoolException
	{
		
		if ( pool == null )
			throw new ObjectPoolException( ObjectPoolException.REASON_NOT_INITIALISED, null,
			"The Object Pool is not initialised." );
		
		// This loop could be avoided using a hashtable for example
		// but hashtables aren't thread safe so better to stick to
		// simpler code that allows other threads to work in the 
		// pool at the same time.
		for ( int i=0; i<pool.length; i++ )
		{
			
			// This block is synchronized so it better avoid anything
			// time consuming or capable of going wrong.
			synchronized ( this )
			{
				//  looking for the busy slot
				if ( status[i] != STATUS_BUSY )
					continue;
				
				//  looking for a slot with something in it
				if ( pool[i] == null )
					continue;
				
				// looking for the slot that has this specific
				// object in it.
				if ( !pool[i].equals( o ) )
					continue;
				
				// indicate that it is in the process of being
				// returned to the pool.
				status[i] = STATUS_FREEING;	

			}
			
			// The code that cleans up the object is not synchronized.
			try
			{
				// In LimitedObjectPool this does nothing but subclasses could do
				// useful stuff here.
				poolObject( pool[i] );
			}
			catch ( Exception ex )
			{
				// All sorts of exception could be thrown in subclasses
				throw new ObjectPoolException(
						ObjectPoolException.REASON_INIT_OBJECT_FAILED,
						ex,
				"Unable to prepare Object for return to Pool." );
			}
			finally  // must guarantee that this is called despite exceptions
			{
				synchronized ( this )
				{
					// return the slot to the pool
					status[i] = STATUS_IDLE;
					// update free spaces count
					free_spaces++;
				}
			}
			
			return;
		}
		
		throw new ObjectPoolException(
				ObjectPoolException.REASON_INVALID_OBJECT,
				null,
		"Cannot free object from pool - it isn't in there." );
	}
}
