Emulate java.util.ArrayDeque

Tests adapted from Apache Harmony.

Bug: #8511
Bug-Link: https://github.com/gwtproject/gwt/issues/8511
Review-Link: https://gwt-review.googlesource.com/#/c/6037/
Change-Id: Ic1b55332c9444b0451d165145dfb90ea2754b6d8
diff --git a/user/super/com/google/gwt/emul/java/util/ArrayDeque.java b/user/super/com/google/gwt/emul/java/util/ArrayDeque.java
new file mode 100644
index 0000000..619b7e8
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/ArrayDeque.java
@@ -0,0 +1,487 @@
+/*
+ * 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);
+      checkConcurrentModification(head <= lastIndex && lastIndex < tail);
+
+      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);
+      checkConcurrentModification(head <= lastIndex && lastIndex < tail);
+
+      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;
+  }
+
+  /**
+   * This field holds a JavaScript array.
+   */
+  @SuppressWarnings("unchecked")
+  private E[] array = (E[]) new Object[MIN_INITIAL_CAPACITY];
+
+  /**
+   * 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() {
+  }
+
+  public ArrayDeque(Collection<? extends E> c) {
+    this(c.size());
+    addAll(c);
+  }
+
+  public ArrayDeque(int numElements) {
+    ArrayHelper.setLength(array, nextArrayLength(numElements));
+  }
+
+  @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);
+    }
+    Object[] dest = out;
+    final int mask = size - 1;
+    for (int i = head, dstIdx = 0; dstIdx < size; i = (i + 1) & mask, ++dstIdx) {
+      dest[dstIdx] = array[i];
+    }
+    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)];
+  }
+
+  /**
+   * 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);
+      array = toArray(newArray);
+    } else {
+      ArrayHelper.setLength(array, newLength);
+    }
+    head = 0;
+    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;
+    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;
+  }
+}
diff --git a/user/test/com/google/gwt/emultest/CollectionsSuite.java b/user/test/com/google/gwt/emultest/CollectionsSuite.java
index 6ab5bfa..6d69a38 100644
--- a/user/test/com/google/gwt/emultest/CollectionsSuite.java
+++ b/user/test/com/google/gwt/emultest/CollectionsSuite.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.emultest;
 
+import com.google.gwt.emultest.java.util.ArrayDequeTest;
 import com.google.gwt.emultest.java.util.ArrayListTest;
 import com.google.gwt.emultest.java.util.ArraysTest;
 import com.google.gwt.emultest.java.util.BitSetTest;
@@ -48,6 +49,7 @@
     GWTTestSuite suite = new GWTTestSuite("Tests for emulation of Java Collections");
 
     // $JUnit-BEGIN$
+    suite.addTestSuite(ArrayDequeTest.class);
     suite.addTestSuite(ArrayListTest.class);
     suite.addTestSuite(ArraysTest.class);
     suite.addTestSuite(BitSetTest.class);
diff --git a/user/test/com/google/gwt/emultest/java/util/ArrayDequeTest.java b/user/test/com/google/gwt/emultest/java/util/ArrayDequeTest.java
new file mode 100644
index 0000000..9915e2a
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/util/ArrayDequeTest.java
@@ -0,0 +1,856 @@
+/*
+ * 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 com.google.gwt.emultest.java.util;
+
+import static java.util.Arrays.asList;
+
+import com.google.gwt.core.client.JavaScriptException;
+
+import org.apache.commons.collections.TestCollection;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Tests ArrayDeque class.
+ */
+public class ArrayDequeTest extends TestCollection {
+
+  public void testAdd() throws Exception {
+    Object o1 = new Object();
+    Object o2 = new Object();
+    Object o3 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    assertTrue(deque.add(o1));
+    checkDequeSizeAndContent(deque, o1);
+    assertTrue(deque.add(o2));
+    checkDequeSizeAndContent(deque, o1, o2);
+    assertTrue(deque.add(o3));
+    checkDequeSizeAndContent(deque, o1, o2, o3);
+
+    try {
+      deque.add(null);
+      fail();
+    } catch (NullPointerException expected) { }
+  }
+
+  public void testAddAll() throws Exception {
+    Object o1 = new Object();
+    Object o2 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    assertTrue(deque.addAll(asList(o1, o2)));
+    checkDequeSizeAndContent(deque, o1, o2);
+
+    try {
+      deque = new ArrayDeque<>();
+      deque.addAll(asList(o1, null, o2));
+      fail();
+    } catch (NullPointerException expected) { }
+  }
+
+  public void testAddFirst() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+    Object o3 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    deque.addFirst(o1);
+    checkDequeSizeAndContent(deque, o1);
+    deque.addFirst(o2);
+    checkDequeSizeAndContent(deque, o2, o1);
+    deque.addFirst(o3);
+    checkDequeSizeAndContent(deque, o3, o2, o1);
+
+    try {
+      deque.addFirst(null);
+      fail();
+    } catch (NullPointerException expected) { }
+  }
+
+  public void testAddLast() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+    Object o3 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    deque.addLast(o1);
+    checkDequeSizeAndContent(deque, o1);
+    deque.addLast(o2);
+    checkDequeSizeAndContent(deque, o1, o2);
+    deque.addLast(o3);
+    checkDequeSizeAndContent(deque, o1, o2, o3);
+
+    try {
+      deque.addLast(null);
+      fail();
+    } catch (NullPointerException expected) { }
+  }
+
+  public void testConstructorFromCollection() {
+    assertEquals(0, new ArrayDeque<>(new ArrayList<>()).size());
+    try {
+      new ArrayDeque<>(null);
+      fail();
+    } catch (NullPointerException expected) {
+      // expected
+    } catch (JavaScriptException expected) {
+      // expected
+    }
+
+    try {
+      new ArrayDeque<>(asList(new Object(), null, new Object()));
+      fail();
+    } catch (NullPointerException expected) { }
+
+    Collection<Object> collection = new ArrayList<>(asList(getFullNonNullElements()));
+    checkDequeSizeAndContent(new ArrayDeque<>(collection), getFullNonNullElements());
+  }
+
+  public void testContains() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+    Object o3 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    assertFalse(deque.contains(o3));
+    assertFalse(deque.contains(null));
+    assertTrue(deque.add(o1));
+    assertTrue(deque.add(o2));
+    assertTrue(deque.add(o1));
+    assertTrue(deque.add(o3));
+    assertTrue(deque.contains(o1));
+    assertTrue(deque.contains(o2));
+    assertTrue(deque.contains(o3));
+    assertFalse(deque.contains(null));
+
+    deque.clear();
+    assertTrue(deque.isEmpty());
+    assertFalse(deque.contains(o1));
+    assertFalse(deque.contains(o2));
+    assertFalse(deque.contains(o3));
+  }
+
+  public void testDescendingIterator() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+    Object o3 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    Iterator<Object> it = deque.descendingIterator();
+    assertFalse(it.hasNext());
+    try {
+      it.next();
+      fail();
+    } catch (NoSuchElementException expected) { }
+
+    deque.add(o1);
+    deque.add(o2);
+    deque.add(o3);
+    it = deque.descendingIterator();
+    assertTrue(it.hasNext());
+    assertEquals(o3, it.next());
+    assertTrue(it.hasNext());
+    assertEquals(o2, it.next());
+    assertTrue(it.hasNext());
+    assertEquals(o1, it.next());
+    assertFalse(it.hasNext());
+    try {
+      it.next();
+      fail();
+    } catch (NoSuchElementException expected) { }
+    checkDequeSizeAndContent(deque, o1, o2, o3);
+
+    deque = new ArrayDeque<>();
+    deque.add(o1);
+    deque.add(o2);
+    deque.add(o3);
+    it = deque.descendingIterator();
+    assertTrue(it.hasNext());
+    assertEquals(o3, it.next());
+    it.remove();
+    assertEquals(2, deque.size());
+    assertTrue(it.hasNext());
+    assertEquals(o2, it.next());
+    assertTrue(it.hasNext());
+    assertEquals(o1, it.next());
+    it.remove();
+    checkDequeSizeAndContent(deque, o2);
+  }
+
+  public void testElement() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    try {
+      deque.element();
+      fail();
+    } catch (NoSuchElementException expected) { }
+
+    deque.add(o1);
+    assertEquals(o1, deque.element());
+    checkDequeSizeAndContent(deque, o1);
+
+    deque.add(o2);
+    assertEquals(o1, deque.element());
+    checkDequeSizeAndContent(deque, o1, o2);
+
+    deque.clear();
+    assertTrue(deque.isEmpty());
+    try {
+      deque.element();
+      fail();
+    } catch (NoSuchElementException expected) { }
+  }
+
+  public void testFailFastIterator() {
+    ArrayDeque<Object> deque = new ArrayDeque<>(asList(getFullNonNullElements()));
+    Iterator<Object> it = deque.iterator();
+    it.next();
+    deque.removeFirst();
+    try {
+      it.next();
+    } catch (ConcurrentModificationException e) {
+      fail();
+    }
+    deque.removeLast();
+    try {
+      it.next();
+      fail();
+    } catch (ConcurrentModificationException expected) { }
+
+    deque = new ArrayDeque<>(asList(getFullNonNullElements()));
+    it = deque.iterator();
+    it.next();
+    deque.clear();
+    try {
+      it.next();
+      fail();
+    } catch (ConcurrentModificationException expected) { }
+
+    deque = new ArrayDeque<>(asList(getFullNonNullElements()));
+    it = deque.iterator();
+    it.next();
+    deque.addFirst(new Object());
+    try {
+      it.next();
+    } catch (ConcurrentModificationException e) {
+      fail();
+    }
+    deque.addLast(new Object());
+    try {
+      it.next();
+      fail();
+    } catch (ConcurrentModificationException expected) { }
+
+    deque = new ArrayDeque<>(asList(getFullNonNullElements()));
+    it = deque.iterator();
+    it.next();
+    it.next();
+    deque.removeFirst();
+    try {
+      it.remove();
+    } catch (ConcurrentModificationException e) {
+      fail();
+    }
+
+    deque = new ArrayDeque<>(asList(getFullNonNullElements()));
+    it = deque.iterator();
+    it.next();
+    it.next();
+    deque.removeFirst();
+    deque.removeFirst();
+    try {
+      it.remove();
+      fail();
+    } catch (ConcurrentModificationException expected) { }
+  }
+
+  public void testFailFastDescendingIterator() {
+    ArrayDeque<Object> deque = new ArrayDeque<>(asList(getFullNonNullElements()));
+    Iterator<Object> it = deque.descendingIterator();
+    it.next();
+    deque.removeLast();
+    try {
+      it.next();
+    } catch (ConcurrentModificationException e) {
+      fail();
+    }
+    deque.removeFirst();
+    try {
+      it.next();
+      fail();
+    } catch (ConcurrentModificationException expected) { }
+
+    deque = new ArrayDeque<>(asList(getFullNonNullElements()));
+    it = deque.descendingIterator();
+    it.next();
+    deque.clear();
+    try {
+      it.next();
+      fail();
+    } catch (ConcurrentModificationException expected) { }
+
+    deque = new ArrayDeque<>(asList(getFullNonNullElements()));
+    it = deque.descendingIterator();
+    it.next();
+    deque.addLast(new Object());
+    try {
+      it.next();
+    } catch (ConcurrentModificationException e) {
+      fail();
+    }
+    deque.addFirst(new Object());
+    try {
+      it.next();
+      fail();
+    } catch (ConcurrentModificationException expected) { }
+
+    deque = new ArrayDeque<>(asList(getFullNonNullElements()));
+    it = deque.descendingIterator();
+    it.next();
+    it.next();
+    deque.removeLast();
+    try {
+      it.remove();
+    } catch (ConcurrentModificationException e) {
+      fail();
+    }
+
+    deque = new ArrayDeque<>(asList(getFullNonNullElements()));
+    it = deque.descendingIterator();
+    it.next();
+    it.next();
+    deque.removeLast();
+    deque.removeLast();
+    try {
+      it.remove();
+      fail();
+    } catch (ConcurrentModificationException expected) { }
+  }
+
+  public void testGetFirst() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    try {
+      deque.getFirst();
+      fail();
+    } catch (NoSuchElementException expected) { }
+
+    deque.add(o1);
+    assertEquals(o1, deque.getFirst());
+    checkDequeSizeAndContent(deque, o1);
+
+    deque.add(o2);
+    assertEquals(o1, deque.getFirst());
+    checkDequeSizeAndContent(deque, o1, o2);
+
+    deque.clear();
+    assertTrue(deque.isEmpty());
+    try {
+      deque.getFirst();
+      fail();
+    } catch (NoSuchElementException expected) { }
+  }
+
+  public void testGetLast() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    try {
+      deque.getLast();
+      fail();
+    } catch (NoSuchElementException expected) { }
+
+    deque.add(o1);
+    assertEquals(o1, deque.getLast());
+    checkDequeSizeAndContent(deque, o1);
+
+    deque.add(o2);
+    assertEquals(o2, deque.getLast());
+    checkDequeSizeAndContent(deque, o1, o2);
+
+    deque.clear();
+    assertTrue(deque.isEmpty());
+    try {
+      deque.getLast();
+      fail();
+    } catch (NoSuchElementException expected) { }
+  }
+
+  public void testOffer() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+    Object o3 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    assertTrue(deque.offer(o1));
+    checkDequeSizeAndContent(deque, o1);
+    assertTrue(deque.offer(o2));
+    checkDequeSizeAndContent(deque, o1, o2);
+    assertTrue(deque.offer(o3));
+    checkDequeSizeAndContent(deque, o1, o2, o3);
+
+    try {
+      deque.offer(null);
+      fail();
+    } catch (NullPointerException expected) { }
+  }
+
+  public void testOfferFirst() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+    Object o3 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    assertTrue(deque.offerFirst(o1));
+    checkDequeSizeAndContent(deque, o1);
+    assertTrue(deque.offerFirst(o2));
+    checkDequeSizeAndContent(deque, o2, o1);
+    assertTrue(deque.offerFirst(o3));
+    checkDequeSizeAndContent(deque, o3, o2, o1);
+
+    try {
+      deque.offerFirst(null);
+      fail();
+    } catch (NullPointerException expected) { }
+  }
+
+  public void testOfferLast() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+    Object o3 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    assertTrue(deque.offerLast(o1));
+    checkDequeSizeAndContent(deque, o1);
+    assertTrue(deque.offerLast(o2));
+    checkDequeSizeAndContent(deque, o1, o2);
+    assertTrue(deque.offerLast(o3));
+    checkDequeSizeAndContent(deque, o1, o2, o3);
+
+    try {
+      deque.offerLast(null);
+      fail();
+    } catch (NullPointerException expected) { }
+  }
+
+  public void testPeek() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+    Object o3 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    assertNull(deque.peek());
+
+    deque.add(o1);
+    assertEquals(o1, deque.peek());
+    checkDequeSizeAndContent(deque, o1);
+
+    deque.add(o2);
+    assertEquals(o1, deque.peek());
+    checkDequeSizeAndContent(deque, o1, o2);
+
+    deque.addFirst(o3);
+    assertEquals(o3, deque.peek());
+    checkDequeSizeAndContent(deque, o3, o1, o2);
+
+    deque.clear();
+    assertTrue(deque.isEmpty());
+    assertNull(deque.peek());
+  }
+
+  public void testPeekFirst() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+    Object o3 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    assertNull(deque.peekFirst());
+
+    deque.add(o1);
+    assertEquals(o1, deque.peekFirst());
+    checkDequeSizeAndContent(deque, o1);
+
+    deque.add(o2);
+    assertEquals(o1, deque.peekFirst());
+    checkDequeSizeAndContent(deque, o1, o2);
+
+    deque.addFirst(o3);
+    assertEquals(o3, deque.peekFirst());
+    checkDequeSizeAndContent(deque, o3, o1, o2);
+
+    deque.clear();
+    assertTrue(deque.isEmpty());
+    assertNull(deque.peekFirst());
+  }
+
+  public void testPeekLast() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+    Object o3 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    assertNull(deque.peekLast());
+
+    deque.add(o1);
+    assertEquals(o1, deque.peekLast());
+    checkDequeSizeAndContent(deque, o1);
+
+    deque.add(o2);
+    assertEquals(o2, deque.peekLast());
+    checkDequeSizeAndContent(deque, o1, o2);
+
+    deque.addFirst(o3);
+    assertEquals(o2, deque.peekLast());
+    checkDequeSizeAndContent(deque, o3, o1, o2);
+
+    deque.clear();
+    assertTrue(deque.isEmpty());
+    assertNull(deque.peekLast());
+  }
+
+  public void testPoll() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    assertNull(deque.poll());
+
+    deque.add(o1);
+    assertEquals(o1, deque.poll());
+    assertTrue(deque.isEmpty());
+
+    deque.add(o1);
+    deque.add(o2);
+    assertEquals(o1, deque.poll());
+    checkDequeSizeAndContent(deque, o2);
+    assertEquals(o2, deque.poll());
+    assertTrue(deque.isEmpty());
+    assertNull(deque.poll());
+  }
+
+  public void testPollFirst() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    assertNull(deque.pollFirst());
+    assertTrue(deque.isEmpty());
+
+    deque.add(o1);
+    assertEquals(o1, deque.pollFirst());
+    assertTrue(deque.isEmpty());
+    assertNull(deque.pollFirst());
+
+    deque.add(o1);
+    deque.add(o2);
+    assertEquals(o1, deque.pollFirst());
+    checkDequeSizeAndContent(deque, o2);
+    assertEquals(o2, deque.pollFirst());
+    assertTrue(deque.isEmpty());
+    assertNull(deque.pollFirst());
+  }
+
+  public void testPollLast() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    assertNull(deque.pollLast());
+    assertTrue(deque.isEmpty());
+
+    deque.add(o1);
+    assertEquals(o1, deque.pollLast());
+    assertTrue(deque.isEmpty());
+    assertNull(deque.pollFirst());
+
+    deque.add(o1);
+    deque.add(o2);
+    assertEquals(o2, deque.pollLast());
+    checkDequeSizeAndContent(deque, o1);
+    assertEquals(o1, deque.pollLast());
+    assertTrue(deque.isEmpty());
+    assertNull(deque.pollLast());
+  }
+
+  public void testPop() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    try {
+      deque.pop();
+      fail();
+    } catch (NoSuchElementException expected) { }
+
+    deque.add(o1);
+    assertEquals(o1, deque.pop());
+    assertTrue(deque.isEmpty());
+
+    deque.add(o1);
+    deque.add(o2);
+    assertEquals(o1, deque.pop());
+    checkDequeSizeAndContent(deque, o2);
+    assertEquals(o2, deque.pop());
+    assertTrue(deque.isEmpty());
+    try {
+      deque.pop();
+      fail();
+    } catch (NoSuchElementException expected) { }
+  }
+
+  public void testPush() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+    Object o3 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    deque.push(o1);
+    checkDequeSizeAndContent(deque, o1);
+    deque.push(o2);
+    checkDequeSizeAndContent(deque, o2, o1);
+    deque.push(o3);
+    checkDequeSizeAndContent(deque, o3, o2, o1);
+
+    try {
+      deque.push(null);
+      fail();
+    } catch (NullPointerException expected) { }
+  }
+
+  public void testRemove() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    try {
+      deque.remove();
+      fail();
+    } catch (NoSuchElementException expected) { }
+
+    deque.add(o1);
+    assertEquals(o1, deque.remove());
+    assertTrue(deque.isEmpty());
+
+    deque.add(o1);
+    deque.add(o2);
+    assertEquals(o1, deque.remove());
+    checkDequeSizeAndContent(deque, o2);
+    assertEquals(o2, deque.removeFirst());
+    assertTrue(deque.isEmpty());
+
+    try {
+      deque.remove();
+      fail();
+    } catch (NoSuchElementException expected) { }
+  }
+
+  public void testRemoveElement() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    assertFalse(deque.remove(o1));
+
+    deque.add(o1);
+    assertTrue(deque.remove(o1));
+    assertTrue(deque.isEmpty());
+
+    deque.add(o1);
+    deque.add(o2);
+    assertTrue(deque.remove(o1));
+    checkDequeSizeAndContent(deque, o2);
+    assertTrue(deque.remove(o2));
+    assertTrue(deque.isEmpty());
+
+    assertFalse(deque.remove(null));
+  }
+
+  public void testRemoveFirst() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    try {
+      deque.removeFirst();
+      fail();
+    } catch (NoSuchElementException expected) { }
+
+    deque.add(o1);
+    assertEquals(o1, deque.removeFirst());
+    assertTrue(deque.isEmpty());
+
+    deque.add(o1);
+    deque.add(o2);
+    assertEquals(o1, deque.removeFirst());
+    checkDequeSizeAndContent(deque, o2);
+    assertEquals(o2, deque.removeFirst());
+    assertTrue(deque.isEmpty());
+
+    try {
+      deque.removeFirst();
+      fail();
+    } catch (NoSuchElementException expected) { }
+  }
+
+  public void testRemoveFirstOccurrence() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+    Object o3 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    assertFalse(deque.removeFirstOccurrence(o1));
+
+    deque.add(o1);
+    assertTrue(deque.removeFirstOccurrence(o1));
+    assertTrue(deque.isEmpty());
+
+    deque = new ArrayDeque<>();
+    deque.add(o1);
+    deque.add(o2);
+    deque.add(o3);
+    assertTrue(deque.removeFirstOccurrence(o2));
+    checkDequeSizeAndContent(deque, o1, o3);
+
+    deque = new ArrayDeque<>();
+    deque.add(o1);
+    deque.add(o2);
+    deque.add(o3);
+    deque.add(o1);
+    deque.add(o2);
+    deque.add(o3);
+    assertTrue(deque.removeFirstOccurrence(o2));
+    checkDequeSizeAndContent(deque, o1, o3, o1, o2, o3);
+    assertTrue(deque.removeFirstOccurrence(o2));
+    checkDequeSizeAndContent(deque, o1, o3, o1, o3);
+    assertTrue(deque.removeFirstOccurrence(o1));
+    checkDequeSizeAndContent(deque, o3, o1, o3);
+    assertTrue(deque.removeFirstOccurrence(o1));
+    checkDequeSizeAndContent(deque, o3, o3);
+    assertFalse(deque.removeFirstOccurrence(o1));
+
+    assertFalse(deque.removeFirstOccurrence(null));
+  }
+
+  public void testRemoveLast() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    try {
+      deque.removeLast();
+      fail();
+    } catch (NoSuchElementException expected) { }
+
+    deque.add(o1);
+    assertEquals(o1, deque.removeLast());
+    assertTrue(deque.isEmpty());
+
+    deque.add(o1);
+    deque.add(o2);
+    assertEquals(o2, deque.removeLast());
+    checkDequeSizeAndContent(deque, o1);
+    assertEquals(o1, deque.removeLast());
+    assertEquals(0, deque.size());
+
+    try {
+      deque.removeLast();
+      fail();
+    } catch (NoSuchElementException expected) { }
+  }
+
+  public void testRemoveLastOccurrence() {
+    Object o1 = new Object();
+    Object o2 = new Object();
+    Object o3 = new Object();
+
+    ArrayDeque<Object> deque = new ArrayDeque<>();
+    assertFalse(deque.removeLastOccurrence(o1));
+
+    deque.add(o1);
+    assertTrue(deque.removeLastOccurrence(o1));
+    assertTrue(deque.isEmpty());
+
+    deque = new ArrayDeque<>();
+    deque.add(o1);
+    deque.add(o2);
+    deque.add(o3);
+    assertTrue(deque.removeLastOccurrence(o2));
+    checkDequeSizeAndContent(deque, o1, o3);
+
+    deque = new ArrayDeque<>();
+    deque.add(o1);
+    deque.add(o2);
+    deque.add(o3);
+    deque.add(o1);
+    deque.add(o2);
+    deque.add(o3);
+    assertTrue(deque.removeLastOccurrence(o2));
+    checkDequeSizeAndContent(deque, o1, o2, o3, o1, o3);
+    assertTrue(deque.removeLastOccurrence(o2));
+    checkDequeSizeAndContent(deque, o1, o3, o1, o3);
+    assertTrue(deque.removeLastOccurrence(o3));
+    checkDequeSizeAndContent(deque, o1, o3, o1);
+    assertTrue(deque.removeLastOccurrence(o3));
+    checkDequeSizeAndContent(deque, o1, o1);
+    assertFalse(deque.removeLastOccurrence(o3));
+
+    assertFalse(deque.removeLastOccurrence(null));
+  }
+
+  /**
+   * Null elements are prohibited in ArrayDeque.
+   */
+  @Override
+  protected Object[] getFullElements() {
+    return getFullNonNullElements();
+  }
+
+  @Override
+  protected Collection makeConfirmedCollection() {
+    return new ArrayList<>();
+  }
+
+  @Override
+  protected Collection makeConfirmedFullCollection() {
+    return new ArrayList<>(asList(getFullElements()));
+  }
+
+  @Override
+  protected Collection makeCollection() {
+    return new ArrayDeque<>();
+  }
+
+  private void checkDequeSizeAndContent(Deque<?> deque, Object... expected) {
+    assertEquals(expected.length, deque.size());
+    int i = 0;
+    for (Object e : deque) {
+      assertEquals(expected[i++], e);
+    }
+    assertEquals(expected.length, i);
+  }
+}