/* ======================================================================
   Parts Copyright 2006 University of Leeds, Oxford University, University of the Highlands and Islands.

   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.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." );
	}
}
