package org.andengine.util.adt.list; import java.util.Arrays; import org.andengine.util.adt.queue.IQueue; /** * 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 ShiftList} is allocation free (unlike the {@link java.util.LinkedList} family). * * Supports <code>null</code> items. * * (c) Zynga 2012 * * @author Nicolas Gramlich <ngramlich@zynga.com> * @author Greg Haynes * @since 15:02:40 - 24.02.2012 */ public class ShiftList<T> implements IQueue<T> { // =========================================================== // Constants // =========================================================== private static final int CAPACITY_INITIAL_DEFAULT = 1; private static final int INDEX_INVALID = -1; // =========================================================== // Fields // =========================================================== protected Object[] mItems; protected int mHead; protected int mTail; // =========================================================== // Constructors // =========================================================== public ShiftList() { this(ShiftList.CAPACITY_INITIAL_DEFAULT); } public ShiftList(final int pInitialCapacity) { this.mItems = new Object[pInitialCapacity]; } // =========================================================== // Getter & Setter // =========================================================== // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Override public boolean isEmpty() { return this.mHead == this.mTail; } @Override @SuppressWarnings("unchecked") public T get(final int pIndex) throws ArrayIndexOutOfBoundsException { return (T) this.mItems[this.mHead + pIndex]; } @Override public void set(final int pIndex, final T pItem) throws IndexOutOfBoundsException { this.mItems[this.mHead + pIndex] = pItem; } @Override public int indexOf(final T pItem) { if(pItem == null) { for(int i = this.mHead; i < this.mTail; i++) { if(this.mItems[i] == null) { return i - this.mHead; } } } else { for(int i = this.mHead; i < this.mTail; i++) { if(pItem.equals(this.mItems[i])) { return i - this.mHead; } } } return ShiftList.INDEX_INVALID; } @SuppressWarnings("unchecked") @Override public T peek() { if(this.mHead == this.mTail) { return null; } else { return (T) this.mItems[this.mHead]; } } @SuppressWarnings("unchecked") @Override public T poll() { if(this.mHead == this.mTail) { return null; } else { final T item = (T) this.mItems[this.mHead]; this.mItems[this.mHead] = null; this.mHead++; if(this.mHead == this.mTail) { this.mHead = 0; this.mTail = 0; } return item; } } @Override public void enter(final T pItem) { this.ensureShiftableRight(); this.mItems[this.mTail] = pItem; this.mTail++; } @Override public void enter(final int pIndex, final T pItem) throws ArrayIndexOutOfBoundsException { final int size = this.mTail - this.mHead; /* Check which side to shift to is more efficient. */ if(pIndex < (size >> 1)) { /* Shift left. */ this.enterShiftingLeft(pIndex, pItem); } else { /* Shift right. */ this.enterShiftingRight(pIndex, pItem, size); } } private void enterShiftingRight(final int pIndex, final T pItem, final int size) { this.ensureShiftableRight(); /* Check if items need to be copied. */ final int shiftAmount = size - pIndex; if(shiftAmount == 0) { /* Nothing to shift, we can insert at the tail. */ this.mItems[this.mTail] = pItem; } else { /* Shift all items to the right of pIndex one to the right, so there is a free spot at pIndex. */ final int internalIndex = this.mHead + pIndex; System.arraycopy(this.mItems, internalIndex, this.mItems, internalIndex + 1, shiftAmount); this.mItems[internalIndex] = pItem; } this.mTail++; } private void enterShiftingLeft(final int pIndex, final T pItem) { this.ensureShiftableLeft(); this.mHead--; /* Check if items need to be copied. */ if(pIndex == 0) { /* Nothing to shift, we can insert at the head. */ this.mItems[this.mHead] = pItem; } else { /* Shift all items to the left if pIndex one to the left, so there is a free spot at pIndex. */ System.arraycopy(this.mItems, this.mHead + 1, this.mItems, this.mHead, pIndex); final int internalIndex = this.mHead + pIndex; this.mItems[internalIndex] = pItem; } } @Override public void add(final T pItem) { this.enter(pItem); } @Override public void add(final int pIndex, final T pItem) throws ArrayIndexOutOfBoundsException { this.enter(pIndex, pItem); } @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; } } @SuppressWarnings("unchecked") @Override public T remove(final int pIndex) throws ArrayIndexOutOfBoundsException { final int internalIndex = this.mHead + pIndex; final T removed = (T) this.mItems[internalIndex]; final int size = this.mTail - this.mHead; /* Determine which side to shift to makes more sense. */ final int center = size >> 1; if(pIndex < center) { /* Shift right. */ if(internalIndex > this.mHead) { System.arraycopy(this.mItems, this.mHead, this.mItems, this.mHead + 1, pIndex); } this.mItems[this.mHead] = null; this.mHead++; } else { /* Shift left. */ final int shiftAmount = size - pIndex - 1; if(shiftAmount > 0) { System.arraycopy(this.mItems, internalIndex + 1, this.mItems, internalIndex, shiftAmount); } this.mTail--; this.mItems[this.mTail] = null; } return removed; } @Override public int size() { return this.mTail - this.mHead; } @Override public void clear() { Arrays.fill(this.mItems, this.mHead, this.mTail, null); this.mHead = 0; this.mTail = 0; } // =========================================================== // Methods // =========================================================== public void shift() { final int size = this.mTail - this.mHead; if(size == 0) { this.mHead = 0; this.mTail = 0; } else { /* Copy items to the start of the array. */ System.arraycopy(this.mItems, this.mHead, this.mItems, 0, size); /* Null out old item references, ensuring not to overwrite just copied ones. */ final int start = Math.max(size, this.mHead); final int end = Math.max(start, this.mTail); if(start < end) { Arrays.fill(this.mItems, start, end, null); } this.mHead = 0; this.mTail = size; } } private void ensureShiftableRight() { final int currentCapacity = this.mItems.length; /* Check if tail reached the end. */ if(this.mTail == currentCapacity) { final int size = this.mTail - this.mHead; /* Check if space problem can be solved by shifting. */ if(size != currentCapacity) { this.shift(); } else { /* Increase array capacity. */ final int newCapacity = ((currentCapacity * 3) >> 1) + 1; final Object newItems[] = new Object[newCapacity]; System.arraycopy(this.mItems, 0, newItems, 0, currentCapacity); this.mItems = newItems; } } } private void ensureShiftableLeft() { /* Check if there is room at the head. */ if(this.mHead == 0) { final int size = this.mTail - this.mHead; final int currentCapacity = this.mItems.length; /* Check if space problem can be solved by shifting. */ if(size < currentCapacity) { if(size == 0) { this.mHead = 1; this.mTail = 1; } else { /* Shift array items one position to the right. */ System.arraycopy(this.mItems, this.mHead, this.mItems, this.mHead + 1, size); /* Null out old item reference. */ this.mItems[this.mHead] = null; this.mHead++; this.mTail++; } } else { /* Increase array capacity. */ final int newCapacity = ((currentCapacity * 3) >> 1) + 1; final Object newItems[] = new Object[newCapacity]; System.arraycopy(this.mItems, 0, newItems, 1, currentCapacity); this.mItems = newItems; this.mHead++; this.mTail++; } } } // =========================================================== // Inner and Anonymous Classes // =========================================================== }