public class

CircularList

extends Object
implements IList<T>
package org.andengine.util.adt.list;

import java.util.Arrays;

import org.andengine.util.adt.list.IList;

/**
 * TODO This class could take some kind of AllocationStrategy object.
 *
 * This implementation is particular useful/efficient for enter/poll operations.
 * Its {@link java.util.Queue} like behavior performs better than a plain {@link java.util.ArrayList}, since it automatically shift the contents of its internal Array only when really necessary.
 * Besides sparse allocations to increase the size of the internal Array, {@link CircularList} is allocation free (unlike the {@link java.util.LinkedList} family).
 *
 * (c) Zynga 2012
 *
 * @author Greg Haynes
 * @author Nicolas Gramlich <ngramlich@zynga.com>
 * @since 15:02:40 - 24.02.2012
 */
public class CircularList<T> implements IList<T> {
	// ===========================================================
	// Constants
	// ===========================================================

	private static final int CAPACITY_INITIAL_DEFAULT = 1;
	private static final int INDEX_INVALID = -1;

	// ===========================================================
	// Fields
	// ===========================================================

	private Object[] mItems;
	private int mHead;
	private int mSize;

	// ===========================================================
	// Constructors
	// ===========================================================

	public CircularList() {
		this(CircularList.CAPACITY_INITIAL_DEFAULT);
	}

	public CircularList(final int pInitialCapacity) {
		this.mItems = new Object[pInitialCapacity];
	}

	// ===========================================================
	// Getter & Setter
	// ===========================================================

	// ===========================================================
	// Methods for/from SuperClass/Interfaces
	// ===========================================================

	@Override
	public boolean isEmpty() {
		return this.mSize == 0;
	}

	@Override
	public void add(final T pItem) {
		this.ensureCapacity();
		this.mItems[this.encodeToInternalIndex(this.mSize)] = pItem;
		this.mSize++;
	}

	@Override
	@SuppressWarnings("unchecked")
	public T get(final int pIndex) throws ArrayIndexOutOfBoundsException {
		return (T) this.mItems[this.encodeToInternalIndex(pIndex)];
	}

	@Override
	public void set(final int pIndex, final T pItem) throws IndexOutOfBoundsException {
		this.mItems[this.encodeToInternalIndex(pIndex)] = pItem;
	}

	@Override
	public int indexOf(final T pItem) {
		final int size = this.size();
		if(pItem == null) {
			for(int i = 0; i < size; i++) {
				if(this.get(i) == null) {
					return i;
				}
			}
		} else {
			for(int i = 0; i < size; i++) {
				if(pItem.equals(this.get(i))) {
					return i;
				}
			}
		}
		return CircularList.INDEX_INVALID;
	}

	@Override
	public void add(final int pIndex, final T pItem) {
		int internalIndex = this.encodeToInternalIndex(pIndex);

		this.ensureCapacity();

		final int internalTail = this.encodeToInternalIndex(this.mSize);
		if(internalIndex == internalTail) {
			// nothing to shift, tail is free
		} else if(internalIndex == this.mHead) {
			this.mHead--;
			if(this.mHead == -1) {
				this.mHead = this.mItems.length - 1;
			}
			internalIndex--;
			if(internalIndex == -1) {
				internalIndex = this.mItems.length - 1;
			}
		} else if((internalIndex < this.mHead) || (this.mHead == 0)) {
			System.arraycopy(this.mItems, internalIndex, this.mItems, internalIndex + 1, internalTail - internalIndex);
		} else if(internalIndex > internalTail) {
			System.arraycopy(this.mItems, this.mHead, this.mItems, this.mHead - 1, pIndex);
			this.mHead--;
			if(this.mHead == -1) {
				this.mHead = this.mItems.length - 1;
			}
			internalIndex--;
			if(internalIndex == -1) {
				internalIndex = this.mItems.length - 1;
			}
		} else if(pIndex < (this.mSize >> 1)) {
			System.arraycopy(this.mItems, this.mHead, this.mItems, this.mHead - 1, pIndex);
			this.mHead--;
			if(this.mHead == -1) {
				this.mHead = this.mItems.length - 1;
			}
			internalIndex--;
			if(internalIndex == -1) {
				internalIndex = this.mItems.length - 1;
			}
		} else {
			System.arraycopy(this.mItems, internalIndex, this.mItems, internalIndex + 1, internalTail - internalIndex);
		}
		this.mItems[internalIndex] = pItem;
		this.mSize++;
	}

	@Override
	public T removeFirst() {
		return this.remove(0);
	}
	
	@Override
	public T removeLast() {
		return this.remove(this.size() - 1);
	}

	@Override
	public boolean remove(final T pItem) {
		final int index = this.indexOf(pItem);
		if(index >= 0) {
			this.remove(index);
			return true;
		} else {
			return false;
		}
	}

	@Override
	@SuppressWarnings("unchecked")
	public T remove(final int pIndex) {
		final int internalIndex = this.encodeToInternalIndex(pIndex);
		final T removed = (T) this.mItems[internalIndex];

		final int internalTail = this.encodeToInternalIndex(this.mSize - 1);

		if(internalIndex == internalTail) {
			this.mItems[internalTail] = null;
		} else if(internalIndex == this.mHead) {
			this.mItems[this.mHead] = null;
			this.mHead++;
			if(this.mHead == this.mItems.length) {
				this.mHead = 0;
			}
		} else if(internalIndex < this.mHead) {
			System.arraycopy(this.mItems, internalIndex + 1, this.mItems, internalIndex, internalTail - internalIndex);
			this.mItems[internalTail] = null;
		} else if(internalIndex > internalTail) {
			System.arraycopy(this.mItems, this.mHead, this.mItems, this.mHead + 1, pIndex);
			this.mItems[this.mHead] = null;
			this.mHead++;
			if(this.mHead == this.mItems.length) {
				this.mHead = 0;
			}
		} else if(pIndex < (this.mSize >> 1)) {
			System.arraycopy(this.mItems, this.mHead, this.mItems, this.mHead + 1, pIndex);
			this.mItems[this.mHead] = null;
			this.mHead++;
			if(this.mHead == this.mItems.length) {
				this.mHead = 0;
			}
		} else {
			System.arraycopy(this.mItems, internalIndex + 1, this.mItems, internalIndex, internalTail - internalIndex);
			this.mItems[internalTail] = null;
		}
		this.mSize--;

		return removed;
	}

	@Override
	public int size() {
		return this.mSize;
	}

	@Override
	public void clear() {
		final int tail = this.mHead + this.mSize;
		final int capacity = this.mItems.length;
		/* Check if items can be blacked out in one or two calls. */
		if(tail <= capacity) {
			Arrays.fill(this.mItems, this.mHead, tail, null);
		} else {
			final int headToCapacity = capacity - this.mHead;
			/* Black out items from head to the end of the array. */
			Arrays.fill(this.mItems, this.mHead, capacity, null);
			/* Black out items from the beginning of the array to the tail. */
			Arrays.fill(this.mItems, 0, this.mSize - headToCapacity, null);
		}
		this.mHead = 0;
		this.mSize = 0;
	}

	// ===========================================================
	// Methods
	// ===========================================================

	private void ensureCapacity() {
		final int currentCapacity = this.mItems.length;
		if(this.mSize == currentCapacity) {
			final int newCapacity = ((currentCapacity * 3) >> 1) + 1;
			final Object newItems[] = new Object[newCapacity];

			System.arraycopy(this.mItems, this.mHead, newItems, 0, this.mSize - this.mHead);
			System.arraycopy(this.mItems, 0, newItems, this.mSize - this.mHead, this.mHead);

			this.mItems = newItems;
			this.mHead = 0;
		}
	}

	private int encodeToInternalIndex(final int pIndex) {
		int internalIndex = this.mHead + pIndex;
		if(internalIndex >= this.mItems.length) {
			internalIndex -= this.mItems.length;
		}
		return internalIndex;
	}

	// ===========================================================
	// Inner and Anonymous Classes
	// ===========================================================
}