/**
* Copyright 2008 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 org.waveprotocol.wave.model.util;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* A list implementation that permits modification while iterating. Iteration
* order is LIFO, compared to the order of {@link #add(Object) add} calls.
*
* Elements added while iterating will not be exposed by the iterator, and
* elements removed while iterating will also not be exposed by future
* {@link Iterator#next()} calls of existing iterators (except for the rare
* case of the removal an element x, which was preceded by a
* {@link Iterator#hasNext()} call that succeeded because of the existence of
* that element x; in that case, {@link Iterator#next()} will return the
* removed element).
*
* This implementation has no extra runtime cost over a linked list, and is
* suitable for collections that fire events frequently, and/or change
* frequently during iteration. However, it does incur the space cost of a
* list node per list item, and the reasoning cost of a non-trivial removal
* policy.
*
*/
public final class ConcurrentList<T> implements Iterable<T> {
/**
* A simple doubly-linked node that can be deleted.
*/
private final static class Node<T> {
private boolean isDeleted;
private Node<T> prev;
private Node<T> next;
private final T data;
Node(Node<T> prev, Node<T> next, T data) {
this.isDeleted = false;
this.prev = prev;
this.next = next;
this.data = data;
}
Node<T> prev() {
return prev;
}
Node<T> next() {
return next;
}
T data() {
return data;
}
boolean isDeleted() {
return isDeleted;
}
/**
* Routes surrounding nodes around this one, but does _not_ change this
* node's links (so it can still be iterated over).
*/
void remove() {
if (prev != null) {
prev.next = next;
}
if (next != null) {
next.prev = prev;
}
isDeleted = true;
}
}
/**
* Iterator that skips deleted nodes. The iterator simply maintains a
* reference to a node in the list. This node is never in a deleted state
* at the time when it is set. It is only updated as follows:
* <ul>
* <li>hasNext() updates the node by skipping newly deleted nodes;</li>
* <li>next() moves the node to the next non-deleted node;</li>
* <li>remove() does not move the node.</li>
* </ul>
*/
private final class NodeIterator implements Iterator<T> {
/** Node which defines our current iteration point. */
private Node<T> node;
/** Node at most recent return of next(). We keep this to make remove() trivial. */
private Node<T> lastReturnedNode;
/**
* Creates an iterator pointing to the start of the list.
*/
private NodeIterator() {
node = start;
}
/**
* Finds the next non-deleted node after a reference one.
*
* @param node reference node
* @return next non-deleted node, or {@code null} if there are none.
*/
private Node<T> nextNonDeletedNode(Node<T> node) {
while (node != null && node.isDeleted()) {
node = node.next();
}
return node;
}
/**
* {@inheritDoc}
*/
public boolean hasNext() {
// skip nodes deleted since node was last set
node = nextNonDeletedNode(node);
return node != null;
}
/**
* {@inheritDoc}
*/
public T next() {
if (node == null) {
throw new NoSuchElementException();
}
// Do not skip deleted nodes here. We want the nullity of this method to match the
// truth of a prior call to hasNext(), regardless of mutations inbetween.
lastReturnedNode = node;
node = nextNonDeletedNode(node.next());
return lastReturnedNode.data();
}
/**
* {@inheritDoc}
*/
public void remove() {
if (lastReturnedNode != null && !lastReturnedNode.isDeleted()) {
ConcurrentList.this.remove(node);
}
}
}
/**
* First node of the list, or null for an empty list.
* A class invariant is that ((start == null) || (!start.isDeleted)).
*/
private Node<T> start;
/**
* Creates a concurrent list.
*
* @return a new concurrent list.
*/
public static <T> ConcurrentList<T> create() {
return new ConcurrentList<T>();
}
/**
* Adds an item to the front of this list.
*
* @param item item to add
*/
public void add(T item) {
if (start == null) {
start = new Node<T>(null, null, item);
} else {
start = new Node<T>(null, start, item);
start.next.prev = start;
}
}
/**
* Removes the first occurrence of an item from this list.
*
* @param item item to remove
*/
public void remove(T item) {
Node<T> node = start;
while (node != null) {
if (node.data().equals(item)) {
break;
} else {
node = node.next();
}
}
if (node != null) {
remove(node);
}
}
/**
* Removes a node, updating terminal references.
*
* @param node node to remove
*/
private void remove(Node<T> node) {
if (node == start) {
start = start.next();
}
node.remove();
}
/**
* Returns true if this collection contains no elements.
*
* @return true if this collection contains no elements
*/
public boolean isEmpty() {
// This test is valid, despite the concept of deleted nodes, due to the
// invariant condition on start.
return start == null;
}
/**
* {@inheritDoc}
*/
public Iterator<T> iterator() {
return new NodeIterator();
}
}