package org.andengine.util.adt.list;
import java.util.Arrays;
/**
* 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
// ===========================================================
}