blob: 29b4a5e7cb34c661f0ecac0bed450e0e42be2e43 [file] [log] [blame]
/*
* Copyright 2016 Google Inc.
*
* 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 java.util;
import static javaemul.internal.InternalPreconditions.checkCriticalElement;
import static javaemul.internal.InternalPreconditions.checkCriticalNotNull;
import static javaemul.internal.InternalPreconditions.checkElement;
import static javaemul.internal.InternalPreconditions.checkState;
import javaemul.internal.ArrayHelper;
/**
* A {@link Deque} based on circular buffer that is implemented with an array and head/tail
* pointers. Array deques have no capacity restrictions; they grow as necessary to support usage.
* Null elements are prohibited. This class is likely to be faster than {@link Stack}
* when used as a stack, and faster than {@link LinkedList} when used as a queue.
* <a href="https://docs.oracle.com/javase/8/docs/api/java/util/ArrayDeque.html">ArrayDeque</a>
*
* @param <E> the element type.
*/
public class ArrayDeque<E> extends AbstractCollection<E> implements Deque<E>, Cloneable {
private final class IteratorImpl implements Iterator<E> {
/**
* Index of element to be returned by subsequent call to next.
*/
private int currentIndex = head;
/**
* Tail recorded at construction (also in remove), to stop
* iterator and also to check for comodification.
*/
private int fence = tail;
/**
* Index of element returned by most recent call to next.
* Reset to -1 if element is deleted by a call to remove.
*/
private int lastIndex = -1;
@Override
public boolean hasNext() {
return currentIndex != fence;
}
@Override
public E next() {
checkCriticalElement(hasNext());
E e = array[currentIndex];
// OpenJDK ArrayDeque doesn't catch all possible comodifications,
// but does catch the ones that corrupt traversal
checkConcurrentModification(fence == tail && e != null);
lastIndex = currentIndex;
currentIndex = (currentIndex + 1) & (array.length - 1);
return e;
}
@Override
public void remove() {
checkState(lastIndex >= 0);
if (removeAtIndex(lastIndex) < 0) {
// if left-shifted, undo increment in next()
currentIndex = (currentIndex - 1) & (array.length - 1);
fence = tail;
}
lastIndex = -1;
}
}
private final class DescendingIteratorImpl implements Iterator<E> {
private int currentIndex = tail;
private int fence = head;
private int lastIndex = -1;
@Override
public boolean hasNext() {
return currentIndex != fence;
}
@Override
public E next() {
checkCriticalElement(hasNext());
currentIndex = (currentIndex - 1) & (array.length - 1);
E e = array[currentIndex];
checkConcurrentModification(fence == head && e != null);
lastIndex = currentIndex;
return e;
}
@Override
public void remove() {
checkState(lastIndex >= 0);
if (removeAtIndex(lastIndex) > 0) {
// if right-shifted, undo decrement in next()
currentIndex = (currentIndex + 1) & (array.length - 1);
fence = head;
}
lastIndex = -1;
}
}
/**
* The minimum capacity that we'll use for a newly created deque.
* Must be a power of 2.
*/
private static final int MIN_INITIAL_CAPACITY = 8;
private static void checkConcurrentModification(boolean expression) {
if (!expression) {
throw new ConcurrentModificationException();
}
}
/**
* Returns best power-of-two array length to hold the given number of elements.
*
* @param numElements the number of elements to hold
*/
@SuppressWarnings("unchecked")
private static int nextArrayLength(int numElements) {
return nextPowerOfTwo(Math.max(MIN_INITIAL_CAPACITY, numElements));
}
/**
* Returns a number that is greater than {@code num} and is a power of two.
* If passed {@code num} is not positive integer or next power of two overflows then
* returned value is non-positive.
* E.g., if num == 32, returns 64. if num == 31, returns 32.
*
* @param num positive integer.
*/
private static int nextPowerOfTwo(int num) {
return Integer.highestOneBit(num) << 1;
}
private E[] array;
/**
* The index of the element at the head of the deque (which is the
* element that would be removed by remove() or pop()); or an
* arbitrary number equal to tail if the deque is empty.
*/
private int head;
/**
* The index at which the next element would be added to the tail
* of the deque (via addLast(E), add(E), or push(E)).
*/
private int tail;
public ArrayDeque() {
this.array = (E[]) new Object[MIN_INITIAL_CAPACITY];
}
public ArrayDeque(int numElements) {
this.array = (E[]) new Object[nextArrayLength(numElements)];
}
public ArrayDeque(Collection<? extends E> c) {
this(c.size());
addAll(c);
}
@Override
public boolean add(E e) {
addLast(e);
return true;
}
@Override
public void addFirst(E e) {
checkCriticalNotNull(e);
head = (head - 1) & (array.length - 1);
array[head] = e;
ensureCapacity();
}
@Override
public void addLast(E e) {
checkCriticalNotNull(e);
array[tail] = e;
tail = (tail + 1) & (array.length - 1);
ensureCapacity();
}
@SuppressWarnings("unchecked")
@Override
public void clear() {
if (head == tail) {
return;
}
array = (E[]) new Object[MIN_INITIAL_CAPACITY];
head = 0;
tail = 0;
}
public Object clone() {
return new ArrayDeque<>(this);
}
@Override
public boolean contains(Object o) {
return contains(iterator(), o);
}
@Override
public Iterator<E> descendingIterator() {
return new DescendingIteratorImpl();
}
@Override
public E element() {
return getFirst();
}
@Override
public E getFirst() {
E e = peekFirstElement();
checkElement(e != null);
return e;
}
@Override
public E getLast() {
E e = peekLastElement();
checkElement(e != null);
return e;
}
@Override
public boolean isEmpty() {
return head == tail;
}
@Override
public Iterator<E> iterator() {
return new IteratorImpl();
}
@Override
public boolean offer(E e) {
return offerLast(e);
}
@Override
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
@Override
public boolean offerLast(E e) {
addLast(e);
return true;
}
@Override
public E peek() {
return peekFirst();
}
@Override
public E peekFirst() {
return peekFirstElement();
}
@Override
public E peekLast() {
return peekLastElement();
}
@Override
public E poll() {
return pollFirst();
}
@Override
public E pollFirst() {
E e = peekFirstElement();
if (e == null) {
return null;
}
array[head] = null;
head = (head + 1) & (array.length - 1);
return e;
}
@Override
public E pollLast() {
E e = peekLastElement();
if (e == null) {
return null;
}
tail = (tail - 1) & (array.length - 1);
array[tail] = null;
return e;
}
@Override
public E pop() {
return removeFirst();
}
@Override
public void push(E e) {
addFirst(e);
}
@Override
public E remove() {
return removeFirst();
}
@Override
public boolean remove(Object o) {
return removeFirstOccurrence(o);
}
@Override
public E removeFirst() {
E e = pollFirst();
checkElement(e != null);
return e;
}
@Override
public boolean removeFirstOccurrence(Object o) {
return remove(iterator(), o);
}
@Override
public E removeLast() {
E e = pollLast();
checkElement(e != null);
return e;
}
@Override
public boolean removeLastOccurrence(Object o) {
return remove(descendingIterator(), o);
}
@Override
public int size() {
return (tail - head) & (array.length - 1);
}
@Override
public Spliterator<E> spliterator() {
return Spliterators.spliterator(this, Spliterator.NONNULL | Spliterator.ORDERED);
}
@SuppressWarnings("unchecked")
@Override
public <T> T[] toArray(T[] out) {
int size = size();
if (out.length < size) {
out = ArrayHelper.createFrom(out, size);
}
copyElements(out, size);
if (out.length > size) {
out[size] = null;
}
return out;
}
private boolean contains(Iterator<E> it, Object o) {
if (o == null) {
return false;
}
while (it.hasNext()) {
if (o.equals(it.next())) {
return true;
}
}
return false;
}
private boolean remove(Iterator<E> it, Object o) {
if (contains(it, o)) {
it.remove();
return true;
}
return false;
}
private E peekFirstElement() {
return array[head];
}
private E peekLastElement() {
return array[(tail - 1) & (array.length - 1)];
}
/**
* Copies {@code count} ArrayDeque's elements to {@code dest} array.
* The method is safe to use when ArrayDeque's array has been rolled over,
* i.e. {@code head == tail}.
* It is assumed that {@code count < size()}.
*/
private void copyElements(Object[] dest, int count) {
final int mask = array.length - 1;
for (int i = head, dstIdx = 0; dstIdx < count; i = (i + 1) & mask, ++dstIdx) {
dest[dstIdx] = array[i];
}
}
/**
* Increase the capacity of this deque when full, i.e.,
* when head and tail have wrapped around to become equal.
*/
private void ensureCapacity() {
if (head != tail) {
return;
}
int numElements = array.length;
int newLength = nextArrayLength(numElements);
if (head != 0) {
E[] newArray = ArrayHelper.createFrom(array, newLength);
copyElements(newArray, numElements);
array = newArray;
head = 0;
} else {
ArrayHelper.setLength(array, newLength);
}
tail = numElements;
}
/**
* Removes the element at the specified position in the elements array,
* adjusting head and tail as necessary. This results in motion of
* elements backwards or forwards in the array.
*
* @return -1 if elements moved backwards (left-shifted); 1 if forwards (right-shifted).
*/
private int removeAtIndex(int i) {
final int mask = array.length - 1;
int headDistance = (i - head) & mask;
int tailDistance = (tail - i) & mask;
int size = (tail - head) & mask;
checkConcurrentModification(headDistance < size);
if (headDistance >= tailDistance) {
shiftLeftAtIndex(i);
return -1;
} else {
shiftRightAtIndex(i);
return 1;
}
}
private void shiftLeftAtIndex(int i) {
final int mask = array.length - 1;
tail = (tail - 1) & mask;
while (i != tail) {
int nextOffset = (i + 1) & mask;
array[i] = array[nextOffset];
i = nextOffset;
}
array[tail] = null;
}
private void shiftRightAtIndex(int i) {
final int mask = array.length - 1;
while (i != head) {
int prevOffset = (i - 1) & mask;
array[i] = array[prevOffset];
i = prevOffset;
}
array[head] = null;
head = (head + 1) & mask;
}
}