blob: acf2597b9f2fe0f70a122e46664472afaed629b5 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* 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.concurrent;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.RandomAccess;
import libcore.util.EmptyArray;
import libcore.util.Objects;
/**
* A thread-safe random-access list.
*
* <p>Read operations (including {@link #get}) do not block and may overlap with
* update operations. Reads reflect the results of the most recently completed
* operations. Aggregate operations like {@link #addAll} and {@link #clear} are
* atomic; they never expose an intermediate state.
*
* <p>Iterators of this list never throw {@link
* ConcurrentModificationException}. When an iterator is created, it keeps a
* copy of the list's contents. It is always safe to iterate this list, but
* iterations may not reflect the latest state of the list.
*
* <p>Iterators returned by this list and its sub lists cannot modify the
* underlying list. In particular, {@link Iterator#remove}, {@link
* ListIterator#add} and {@link ListIterator#set} all throw {@link
* UnsupportedOperationException}.
*
* <p>This class offers extended API beyond the {@link List} interface. It
* includes additional overloads for indexed search ({@link #indexOf} and {@link
* #lastIndexOf}) and methods for conditional adds ({@link #addIfAbsent} and
* {@link #addAllAbsent}).
*/
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
private static final long serialVersionUID = 8673264195747942595L;
/**
* Holds the latest snapshot of the list's data. This field is volatile so
* that data can be read without synchronization. As a consequence, all
* writes to this field must be atomic; it is an error to modify the
* contents of an array after it has been assigned to this field.
*
* Synchronization is required by all update operations. This defends
* against one update clobbering the result of another operation. For
* example, 100 threads simultaneously calling add() will grow the list's
* size by 100 when they have completed. No update operations are lost!
*
* Maintainers should be careful to read this field only once in
* non-blocking read methods. Write methods must be synchronized to avoid
* clobbering concurrent writes.
*/
private transient volatile Object[] elements;
/**
* Creates a new empty instance.
*/
public CopyOnWriteArrayList() {
elements = EmptyArray.OBJECT;
}
/**
* Creates a new instance containing the elements of {@code collection}.
*/
@SuppressWarnings("unchecked")
public CopyOnWriteArrayList(Collection<? extends E> collection) {
this((E[]) collection.toArray());
}
/**
* Creates a new instance containing the elements of {@code array}.
*/
public CopyOnWriteArrayList(E[] array) {
this.elements = Arrays.copyOf(array, array.length, Object[].class);
}
@Override public Object clone() {
try {
CopyOnWriteArrayList result = (CopyOnWriteArrayList) super.clone();
result.elements = result.elements.clone();
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
public int size() {
return elements.length;
}
@SuppressWarnings("unchecked")
public E get(int index) {
return (E) elements[index];
}
public boolean contains(Object o) {
return indexOf(o) != -1;
}
public boolean containsAll(Collection<?> collection) {
Object[] snapshot = elements;
return containsAll(collection, snapshot, 0, snapshot.length);
}
static boolean containsAll(Collection<?> collection, Object[] snapshot, int from, int to) {
for (Object o : collection) {
if (indexOf(o, snapshot, from, to) == -1) {
return false;
}
}
return true;
}
/**
* Searches this list for {@code object} and returns the index of the first
* occurrence that is at or after {@code from}.
*
* @return the index or -1 if the object was not found.
*/
public int indexOf(E object, int from) {
Object[] snapshot = elements;
return indexOf(object, snapshot, from, snapshot.length);
}
public int indexOf(Object object) {
Object[] snapshot = elements;
return indexOf(object, snapshot, 0, snapshot.length);
}
/**
* Searches this list for {@code object} and returns the index of the last
* occurrence that is before {@code to}.
*
* @return the index or -1 if the object was not found.
*/
public int lastIndexOf(E object, int to) {
Object[] snapshot = elements;
return lastIndexOf(object, snapshot, 0, to);
}
public int lastIndexOf(Object object) {
Object[] snapshot = elements;
return lastIndexOf(object, snapshot, 0, snapshot.length);
}
public boolean isEmpty() {
return elements.length == 0;
}
/**
* Returns an {@link Iterator} that iterates over the elements of this list
* as they were at the time of this method call. Changes to the list made
* after this method call will not be reflected by the iterator, nor will
* they trigger a {@link ConcurrentModificationException}.
*
* <p>The returned iterator does not support {@link Iterator#remove()}.
*/
public Iterator<E> iterator() {
Object[] snapshot = elements;
return new CowIterator<E>(snapshot, 0, snapshot.length);
}
/**
* Returns a {@link ListIterator} that iterates over the elements of this
* list as they were at the time of this method call. Changes to the list
* made after this method call will not be reflected by the iterator, nor
* will they trigger a {@link ConcurrentModificationException}.
*
* <p>The returned iterator does not support {@link ListIterator#add},
* {@link ListIterator#set} or {@link Iterator#remove()},
*/
public ListIterator<E> listIterator(int index) {
Object[] snapshot = elements;
if (index < 0 || index > snapshot.length) {
throw new IndexOutOfBoundsException("index=" + index + ", length=" + snapshot.length);
}
CowIterator<E> result = new CowIterator<E>(snapshot, 0, snapshot.length);
result.index = index;
return result;
}
/**
* Equivalent to {@code listIterator(0)}.
*/
public ListIterator<E> listIterator() {
Object[] snapshot = elements;
return new CowIterator<E>(snapshot, 0, snapshot.length);
}
public List<E> subList(int from, int to) {
Object[] snapshot = elements;
if (from < 0 || from > to || to > snapshot.length) {
throw new IndexOutOfBoundsException("from=" + from + ", to=" + to +
", list size=" + snapshot.length);
}
return new CowSubList(snapshot, from, to);
}
public Object[] toArray() {
return elements.clone();
}
@SuppressWarnings({"unchecked","SuspiciousSystemArraycopy"})
public <T> T[] toArray(T[] contents) {
Object[] snapshot = elements;
if (snapshot.length > contents.length) {
return (T[]) Arrays.copyOf(snapshot, snapshot.length, contents.getClass());
}
System.arraycopy(snapshot, 0, contents, 0, snapshot.length);
if (snapshot.length < contents.length) {
contents[snapshot.length] = null;
}
return contents;
}
@Override public boolean equals(Object other) {
if (other instanceof CopyOnWriteArrayList) {
return this == other
|| Arrays.equals(elements, ((CopyOnWriteArrayList<?>) other).elements);
} else if (other instanceof List) {
Object[] snapshot = elements;
Iterator<?> i = ((List<?>) other).iterator();
for (Object o : snapshot) {
if (!i.hasNext() || !Objects.equal(o, i.next())) {
return false;
}
}
return !i.hasNext();
} else {
return false;
}
}
@Override public int hashCode() {
return Arrays.hashCode(elements);
}
@Override public String toString() {
return Arrays.toString(elements);
}
public synchronized boolean add(E e) {
Object[] newElements = new Object[elements.length + 1];
System.arraycopy(elements, 0, newElements, 0, elements.length);
newElements[elements.length] = e;
elements = newElements;
return true;
}
public synchronized void add(int index, E e) {
Object[] newElements = new Object[elements.length + 1];
System.arraycopy(elements, 0, newElements, 0, index);
newElements[index] = e;
System.arraycopy(elements, index, newElements, index + 1, elements.length - index);
elements = newElements;
}
public synchronized boolean addAll(Collection<? extends E> collection) {
return addAll(elements.length, collection);
}
public synchronized boolean addAll(int index, Collection<? extends E> collection) {
Object[] toAdd = collection.toArray();
Object[] newElements = new Object[elements.length + toAdd.length];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(toAdd, 0, newElements, index, toAdd.length);
System.arraycopy(elements, index,
newElements, index + toAdd.length, elements.length - index);
elements = newElements;
return toAdd.length > 0;
}
/**
* Adds the elements of {@code collection} that are not already present in
* this list. If {@code collection} includes a repeated value, at most one
* occurrence of that value will be added to this list. Elements are added
* at the end of this list.
*
* <p>Callers of this method may prefer {@link CopyOnWriteArraySet}, whose
* API is more appropriate for set operations.
*/
public synchronized int addAllAbsent(Collection<? extends E> collection) {
Object[] toAdd = collection.toArray();
Object[] newElements = new Object[elements.length + toAdd.length];
System.arraycopy(elements, 0, newElements, 0, elements.length);
int addedCount = 0;
for (Object o : toAdd) {
if (indexOf(o, newElements, 0, elements.length + addedCount) == -1) {
newElements[elements.length + addedCount++] = o;
}
}
if (addedCount < toAdd.length) {
newElements = Arrays.copyOfRange(
newElements, 0, elements.length + addedCount); // trim to size
}
elements = newElements;
return addedCount;
}
/**
* Adds {@code object} to the end of this list if it is not already present.
*
* <p>Callers of this method may prefer {@link CopyOnWriteArraySet}, whose
* API is more appropriate for set operations.
*/
public synchronized boolean addIfAbsent(E object) {
if (contains(object)) {
return false;
}
add(object);
return true;
}
@Override public synchronized void clear() {
elements = EmptyArray.OBJECT;
}
public synchronized E remove(int index) {
@SuppressWarnings("unchecked")
E removed = (E) elements[index];
removeRange(index, index + 1);
return removed;
}
public synchronized boolean remove(Object o) {
int index = indexOf(o);
if (index == -1) {
return false;
}
remove(index);
return true;
}
public synchronized boolean removeAll(Collection<?> collection) {
return removeOrRetain(collection, false, 0, elements.length) != 0;
}
public synchronized boolean retainAll(Collection<?> collection) {
return removeOrRetain(collection, true, 0, elements.length) != 0;
}
/**
* Removes or retains the elements in {@code collection}. Returns the number
* of elements removed.
*/
private int removeOrRetain(Collection<?> collection, boolean retain, int from, int to) {
for (int i = from; i < to; i++) {
if (collection.contains(elements[i]) == retain) {
continue;
}
/*
* We've encountered an element that must be removed! Create a new
* array and copy in the surviving elements one by one.
*/
Object[] newElements = new Object[elements.length - 1];
System.arraycopy(elements, 0, newElements, 0, i);
int newSize = i;
for (int j = i + 1; j < to; j++) {
if (collection.contains(elements[j]) == retain) {
newElements[newSize++] = elements[j];
}
}
/*
* Copy the elements after 'to'. This is only useful for sub lists,
* where 'to' will be less than elements.length.
*/
System.arraycopy(elements, to, newElements, newSize, elements.length - to);
newSize += (elements.length - to);
if (newSize < newElements.length) {
newElements = Arrays.copyOfRange(newElements, 0, newSize); // trim to size
}
int removed = elements.length - newElements.length;
elements = newElements;
return removed;
}
// we made it all the way through the loop without making any changes
return 0;
}
public synchronized E set(int index, E e) {
Object[] newElements = elements.clone();
@SuppressWarnings("unchecked")
E result = (E) newElements[index];
newElements[index] = e;
elements = newElements;
return result;
}
private void removeRange(int from, int to) {
Object[] newElements = new Object[elements.length - (to - from)];
System.arraycopy(elements, 0, newElements, 0, from);
System.arraycopy(elements, to, newElements, from, elements.length - to);
elements = newElements;
}
static int lastIndexOf(Object o, Object[] data, int from, int to) {
if (o == null) {
for (int i = to - 1; i >= from; i--) {
if (data[i] == null) {
return i;
}
}
} else {
for (int i = to - 1; i >= from; i--) {
if (o.equals(data[i])) {
return i;
}
}
}
return -1;
}
static int indexOf(Object o, Object[] data, int from, int to) {
if (o == null) {
for (int i = from; i < to; i++) {
if (data[i] == null) {
return i;
}
}
} else {
for (int i = from; i < to; i++) {
if (o.equals(data[i])) {
return i;
}
}
}
return -1;
}
final Object[] getArray() {
// CopyOnWriteArraySet needs this.
return elements;
}
/**
* The sub list is thread safe and supports non-blocking reads. Doing so is
* more difficult than in the full list, because each read needs to examine
* four fields worth of state:
* - the elements array of the full list
* - two integers for the bounds of this sub list
* - the expected elements array (to detect concurrent modification)
*
* This is accomplished by aggregating the sub list's three fields into a
* single snapshot object representing the current slice. This permits reads
* to be internally consistent without synchronization. This takes advantage
* of Java's concurrency semantics for final fields.
*/
class CowSubList extends AbstractList<E> {
/*
* An immutable snapshot of a sub list's state. By gathering all three
* of the sub list's fields in an immutable object,
*/
private volatile Slice slice;
public CowSubList(Object[] expectedElements, int from, int to) {
this.slice = new Slice(expectedElements, from, to);
}
@Override public int size() {
Slice slice = this.slice;
return slice.to - slice.from;
}
@Override public boolean isEmpty() {
Slice slice = this.slice;
return slice.from == slice.to;
}
@SuppressWarnings("unchecked")
@Override public E get(int index) {
Slice slice = this.slice;
Object[] snapshot = elements;
slice.checkElementIndex(index);
slice.checkConcurrentModification(snapshot);
return (E) snapshot[index + slice.from];
}
@Override public Iterator<E> iterator() {
return listIterator(0);
}
@Override public ListIterator<E> listIterator() {
return listIterator(0);
}
@Override public ListIterator<E> listIterator(int index) {
Slice slice = this.slice;
Object[] snapshot = elements;
slice.checkPositionIndex(index);
slice.checkConcurrentModification(snapshot);
CowIterator<E> result = new CowIterator<E>(snapshot, slice.from, slice.to);
result.index = slice.from + index;
return result;
}
@Override public int indexOf(Object object) {
Slice slice = this.slice;
Object[] snapshot = elements;
slice.checkConcurrentModification(snapshot);
int result = CopyOnWriteArrayList.indexOf(object, snapshot, slice.from, slice.to);
return (result != -1) ? (result - slice.from) : -1;
}
@Override public int lastIndexOf(Object object) {
Slice slice = this.slice;
Object[] snapshot = elements;
slice.checkConcurrentModification(snapshot);
int result = CopyOnWriteArrayList.lastIndexOf(object, snapshot, slice.from, slice.to);
return (result != -1) ? (result - slice.from) : -1;
}
@Override public boolean contains(Object object) {
return indexOf(object) != -1;
}
@Override public boolean containsAll(Collection<?> collection) {
Slice slice = this.slice;
Object[] snapshot = elements;
slice.checkConcurrentModification(snapshot);
return CopyOnWriteArrayList.containsAll(collection, snapshot, slice.from, slice.to);
}
@Override public List<E> subList(int from, int to) {
Slice slice = this.slice;
if (from < 0 || from > to || to > size()) {
throw new IndexOutOfBoundsException("from=" + from + ", to=" + to +
", list size=" + size());
}
return new CowSubList(slice.expectedElements, slice.from + from, slice.from + to);
}
@Override public E remove(int index) {
synchronized (CopyOnWriteArrayList.this) {
slice.checkElementIndex(index);
slice.checkConcurrentModification(elements);
E removed = CopyOnWriteArrayList.this.remove(slice.from + index);
slice = new Slice(elements, slice.from, slice.to - 1);
return removed;
}
}
@Override public void clear() {
synchronized (CopyOnWriteArrayList.this) {
slice.checkConcurrentModification(elements);
CopyOnWriteArrayList.this.removeRange(slice.from, slice.to);
slice = new Slice(elements, slice.from, slice.from);
}
}
@Override public void add(int index, E object) {
synchronized (CopyOnWriteArrayList.this) {
slice.checkPositionIndex(index);
slice.checkConcurrentModification(elements);
CopyOnWriteArrayList.this.add(index + slice.from, object);
slice = new Slice(elements, slice.from, slice.to + 1);
}
}
@Override public boolean add(E object) {
synchronized (CopyOnWriteArrayList.this) {
add(slice.to - slice.from, object);
return true;
}
}
@Override public boolean addAll(int index, Collection<? extends E> collection) {
synchronized (CopyOnWriteArrayList.this) {
slice.checkPositionIndex(index);
slice.checkConcurrentModification(elements);
int oldSize = elements.length;
boolean result = CopyOnWriteArrayList.this.addAll(index + slice.from, collection);
slice = new Slice(elements, slice.from, slice.to + (elements.length - oldSize));
return result;
}
}
@Override public boolean addAll(Collection<? extends E> collection) {
synchronized (CopyOnWriteArrayList.this) {
return addAll(size(), collection);
}
}
@Override public E set(int index, E object) {
synchronized (CopyOnWriteArrayList.this) {
slice.checkElementIndex(index);
slice.checkConcurrentModification(elements);
E result = CopyOnWriteArrayList.this.set(index + slice.from, object);
slice = new Slice(elements, slice.from, slice.to);
return result;
}
}
@Override public boolean remove(Object object) {
synchronized (CopyOnWriteArrayList.this) {
int index = indexOf(object);
if (index == -1) {
return false;
}
remove(index);
return true;
}
}
@Override public boolean removeAll(Collection<?> collection) {
synchronized (CopyOnWriteArrayList.this) {
slice.checkConcurrentModification(elements);
int removed = removeOrRetain(collection, false, slice.from, slice.to);
slice = new Slice(elements, slice.from, slice.to - removed);
return removed != 0;
}
}
@Override public boolean retainAll(Collection<?> collection) {
synchronized (CopyOnWriteArrayList.this) {
slice.checkConcurrentModification(elements);
int removed = removeOrRetain(collection, true, slice.from, slice.to);
slice = new Slice(elements, slice.from, slice.to - removed);
return removed != 0;
}
}
}
static class Slice {
private final Object[] expectedElements;
private final int from;
private final int to;
Slice(Object[] expectedElements, int from, int to) {
this.expectedElements = expectedElements;
this.from = from;
this.to = to;
}
/**
* Throws if {@code index} doesn't identify an element in the array.
*/
void checkElementIndex(int index) {
if (index < 0 || index >= to - from) {
throw new IndexOutOfBoundsException("index=" + index + ", size=" + (to - from));
}
}
/**
* Throws if {@code index} doesn't identify an insertion point in the
* array. Unlike element index, it's okay to add or iterate at size().
*/
void checkPositionIndex(int index) {
if (index < 0 || index > to - from) {
throw new IndexOutOfBoundsException("index=" + index + ", size=" + (to - from));
}
}
void checkConcurrentModification(Object[] snapshot) {
if (expectedElements != snapshot) {
throw new ConcurrentModificationException();
}
}
}
/**
* Iterates an immutable snapshot of the list.
*/
static class CowIterator<E> implements ListIterator<E> {
private final Object[] snapshot;
private final int from;
private final int to;
private int index = 0;
CowIterator(Object[] snapshot, int from, int to) {
this.snapshot = snapshot;
this.from = from;
this.to = to;
this.index = from;
}
public void add(E object) {
throw new UnsupportedOperationException();
}
public boolean hasNext() {
return index < to;
}
public boolean hasPrevious() {
return index > from;
}
@SuppressWarnings("unchecked")
public E next() {
if (index < to) {
return (E) snapshot[index++];
} else {
throw new NoSuchElementException();
}
}
public int nextIndex() {
return index;
}
@SuppressWarnings("unchecked")
public E previous() {
if (index > from) {
return (E) snapshot[--index];
} else {
throw new NoSuchElementException();
}
}
public int previousIndex() {
return index - 1;
}
public void remove() {
throw new UnsupportedOperationException();
}
public void set(E object) {
throw new UnsupportedOperationException();
}
}
private void writeObject(ObjectOutputStream out) throws IOException {
Object[] snapshot = elements;
out.defaultWriteObject();
out.writeInt(snapshot.length);
for (Object o : snapshot) {
out.writeObject(o);
}
}
private synchronized void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();
Object[] snapshot = new Object[in.readInt()];
for (int i = 0; i < snapshot.length; i++) {
snapshot[i] = in.readObject();
}
elements = snapshot;
}
}