Refactors LinkedList to not use circular list. The new implementation is using double pivot instead of single. This is to make LinkedList safer when preconditions are not enabled and iterator is not used properly. For example, with a circular list, if you keep calling next/remove you might never hit an NPE because it will eventually come back to beginning and start iterating again. This might be a little bit on the paranoid side but given the new implementation is no more complex, it is a good trade-off. Change-Id: I4915d5c7b3a6a0e48b96c14be4ce0d64bf3b0de2 Review-Link: https://gwt-review.googlesource.com/#/c/9363/
diff --git a/user/super/com/google/gwt/emul/java/util/LinkedList.java b/user/super/com/google/gwt/emul/java/util/LinkedList.java index ae82156..a276656 100644 --- a/user/super/com/google/gwt/emul/java/util/LinkedList.java +++ b/user/super/com/google/gwt/emul/java/util/LinkedList.java
@@ -15,8 +15,9 @@ */ package java.util; -import static com.google.gwt.core.shared.impl.GwtPreconditions.checkCriticalElement; +import static com.google.gwt.core.shared.impl.GwtPreconditions.checkElement; import static com.google.gwt.core.shared.impl.GwtPreconditions.checkPositionIndex; +import static com.google.gwt.core.shared.impl.GwtPreconditions.checkState; import java.io.Serializable; @@ -30,13 +31,13 @@ public class LinkedList<E> extends AbstractSequentialList<E> implements Cloneable, List<E>, Deque<E>, Serializable { /* - * This implementation uses a doubly-linked circular list with a header node. + * This implementation uses a doubly-linked list with a header/tail node. * * TODO(jat): add more efficient subList implementation. */ private final class DescendingIteratorImpl implements Iterator<E> { - private final ListIterator<E> itr = new ListIteratorImpl(size, header); + private final ListIterator<E> itr = new ListIteratorImpl(size, tail); @Override public boolean hasNext() { @@ -84,70 +85,73 @@ currentIndex = index; } + @Override public void add(E o) { - addBefore(o, currentNode); + addNode(o, currentNode.prev, currentNode); ++currentIndex; lastNode = null; } + @Override public boolean hasNext() { - return currentNode != header; + return currentNode != tail; } + @Override public boolean hasPrevious() { return currentNode.prev != header; } + @Override public E next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } + checkElement(hasNext()); + lastNode = currentNode; currentNode = currentNode.next; ++currentIndex; return lastNode.value; } + @Override public int nextIndex() { return currentIndex; } + @Override public E previous() { - if (!hasPrevious()) { - throw new NoSuchElementException(); - } + checkElement(hasPrevious()); + lastNode = currentNode = currentNode.prev; --currentIndex; return lastNode.value; } + @Override public int previousIndex() { return currentIndex - 1; } + @Override public void remove() { - verifyCurrentElement(); + checkState(lastNode != null); + + Node<E> nextNode = lastNode.next; + removeNode(lastNode); if (currentNode == lastNode) { // We just did a previous(). - currentNode = lastNode.next; + currentNode = nextNode; } else { // We just did a next(). --currentIndex; } - lastNode.remove(); lastNode = null; - --size; } + @Override public void set(E o) { - verifyCurrentElement(); - lastNode.value = o; - } + checkState(lastNode != null); - protected void verifyCurrentElement() { - if (lastNode == null) { - throw new IllegalStateException(); - } + lastNode.value = o; } } @@ -160,38 +164,6 @@ public Node<E> next; public Node<E> prev; public E value; - - public Node() { - next = prev = this; - } - - public Node(E value) { - this.value = value; - } - - /** - * Construct a node containing a value and add it before the specified node. - * - * @param value - * @param nextNode - */ - public Node(E value, Node<E> nextNode) { - this(value); - this.next = nextNode; - this.prev = nextNode.prev; - nextNode.prev.next = this; - nextNode.prev = this; - } - - /** - * Remove this node from any list it is in, leaving it with circular - * references to itself. - */ - public void remove() { - this.next.prev = this.prev; - this.prev.next = this.next; - this.next = this.prev = this; - } } /** @@ -202,11 +174,14 @@ private E exposeElement; /** - * Header node - header.next is the first element of the list, and header.prev - * is the last element of the list. If the list is empty, the header node will - * point to itself. + * Header node - header.next is the first element of the list. */ - private Node<E> header; + private final Node<E> header = new Node<E>(); + + /** + * Tail node - tail.prev is the last element of the list. + */ + private final Node<E> tail = new Node<E>(); /** * Number of nodes currently present in the list. @@ -214,11 +189,11 @@ private int size; public LinkedList() { - clear(); + reset(); } public LinkedList(Collection<? extends E> c) { - this(); + reset(); addAll(c); } @@ -228,19 +203,25 @@ return true; } + @Override public void addFirst(E o) { - new Node<E>(o, header.next); - ++size; + addNode(o, header, header.next); } + @Override public void addLast(E o) { - new Node<E>(o, header); - ++size; + addNode(o, tail.prev, tail); } @Override public void clear() { - header = new Node<E>(); + reset(); + } + + private void reset() { + header.next = tail; + tail.prev = header; + header.prev = tail.next = null; size = 0; } @@ -253,20 +234,23 @@ return new DescendingIteratorImpl(); } + @Override public E element() { return getFirst(); } + @Override public E getFirst() { - checkCriticalElement(size != 0); + checkElement(size != 0); return header.next.value; } + @Override public E getLast() { - checkCriticalElement(size != 0); + checkElement(size != 0); - return header.prev.value; + return tail.prev.value; } @Override @@ -276,7 +260,7 @@ Node<E> node; // start from the nearest end of the list if (index >= size >> 1) { - node = header; + node = tail; for (int i = size; i > index; --i) { node = node.prev; } @@ -290,6 +274,7 @@ return new ListIteratorImpl(index, node); } + @Override public boolean offer(E o) { return offerLast(o); } @@ -306,48 +291,34 @@ return true; } + @Override public E peek() { return peekFirst(); } @Override public E peekFirst() { - if (size == 0) { - return null; - } else { - return getFirst(); - } + return size == 0 ? null : getFirst(); } @Override public E peekLast() { - if (size == 0) { - return null; - } else { - return getLast(); - } + return size == 0 ? null : getLast(); } + @Override public E poll() { return pollFirst(); } @Override public E pollFirst() { - if (size == 0) { - return null; - } else { - return removeFirst(); - } + return size == 0 ? null : removeFirst(); } @Override public E pollLast() { - if (size == 0) { - return null; - } else { - return removeLast(); - } + return size == 0 ? null : removeLast(); } @Override @@ -360,13 +331,16 @@ addFirst(e); } + @Override public E remove() { return removeFirst(); } + @Override public E removeFirst() { - Node<E> node = removeNode(header.next); - return node.value; + checkElement(size != 0); + + return removeNode(header.next); } @Override @@ -374,14 +348,16 @@ return remove(o); } + @Override public E removeLast() { - Node<E> node = removeNode(header.prev); - return node.value; + checkElement(size != 0); + + return removeNode(tail.prev); } @Override public boolean removeLastOccurrence(Object o) { - for (Node<E> e = header.prev; e != header; e = e.prev) { + for (Node<E> e = tail.prev; e != header; e = e.prev) { if (Objects.equals(e.value, o)) { removeNode(e); return true; @@ -395,16 +371,22 @@ return size; } - private void addBefore(E o, Node<E> target) { - new Node<E>(o, target); + private void addNode(E o, Node<E> prev, Node<E> next) { + Node<E> node = new Node<E>(); + node.value = o; + node.prev = prev; + node.next = next; + next.prev = prev.next = node; ++size; } - private Node<E> removeNode(Node<E> node) { - checkCriticalElement(size != 0); - + private E removeNode(Node<E> node) { + E oldValue = node.value; + node.next.prev = node.prev; + node.prev.next = node.next; + node.next = node.prev = null; + node.value = null; --size; - node.remove(); - return node; + return oldValue; } }