/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with this * work for additional information regarding copyright ownership. The ASF * licenses this file to you 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.lang.reflect.Array; 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 java.util.concurrent.locks.ReentrantLock; /** * Implements a {@link java.util.ArrayList} variant that is thread-safe. All * write operation result in a new copy of the underlying data being created. * Iterators reflect the state of the CopyOnWriteArrayList at the time they were * created. They are not updated to reflect subsequent changes to the list. In * addition, these iterators cannot be used for modifying the underlying * CopyOnWriteArrayList. * * @param the element type */ public class CopyOnWriteArrayList implements List, RandomAccess, Cloneable, Serializable { private static final long serialVersionUID = 8673264195747942595L; private transient volatile E[] arr; /** * Lock for the queue write methods */ private final transient ReentrantLock lock = new ReentrantLock(); /** * Creates a new, empty instance of CopyOnWriteArrayList. */ public CopyOnWriteArrayList() { } /** * Creates a new instance of CopyOnWriteArrayList and fills it with the * contents of a given Collection. * * @param c the collection the elements of which are to be copied into * the new instance. */ public CopyOnWriteArrayList(Collection c) { this((E[]) c.toArray()); } /** * Creates a new instance of CopyOnWriteArrayList and fills it with the * contents of a given array. * * @param array the array the elements of which are to be copied into the * new instance. */ public CopyOnWriteArrayList(E[] array) { int size = array.length; E[] data = newElementArray(size); for (int i = 0; i < size; i++) { data[i] = array[i]; } arr = data; } public boolean add(E e) { lock.lock(); try { E[] data; E[] old = getData(); int size = old.length; data = newElementArray(size + 1); System.arraycopy(old, 0, data, 0, size); data[size] = e; setData(data); return true; } finally { lock.unlock(); } } public void add(int index, E e) { lock.lock(); try { E[] data; E[] old = getData(); int size = old.length; checkIndexInclusive(index, size); data = newElementArray(size+1); System.arraycopy(old, 0, data, 0, index); data[index] = e; if (size > index) { System.arraycopy(old, index, data, index + 1, size - index); } setData(data); } finally { lock.unlock(); } } public boolean addAll(Collection c) { Iterator it = c.iterator(); int ssize = c.size(); lock.lock(); try { int size = size(); E[] data; E[] old = getData(); int nSize = size + ssize; data = newElementArray(nSize); System.arraycopy(old, 0, data, 0, size); while (it.hasNext()) { data[size++] = (E) it.next(); } setData(data); } finally { lock.unlock(); } return true; } public boolean addAll(int index, Collection c) { Iterator it = c.iterator(); int ssize = c.size(); lock.lock(); try { int size = size(); checkIndexInclusive(index, size); E[] data; E[] old = getData(); int nSize = size + ssize; data = newElementArray(nSize); System.arraycopy(old, 0, data, 0, index); int i = index; while (it.hasNext()) { data[i++] = (E) it.next(); } if (size > index) { System.arraycopy(old, index, data, index + ssize, size - index); } setData(data); } finally { lock.unlock(); } return true; } /** * Adds to this CopyOnWriteArrayList all those elements from a given * collection that are not yet part of the list. * * @param c the collection from which the potential new elements are * taken. * * @return the number of elements actually added to this list. */ public int addAllAbsent(Collection c) { if (c.size() == 0) { return 0; } lock.lock(); try { E[] old = getData(); int size = old.length; E[] toAdd = newElementArray(c.size()); int i = 0; for (Iterator it = c.iterator(); it.hasNext();) { E o = (E) it.next(); if (indexOf(o) < 0) { toAdd[i++] = o; } } E[] data = newElementArray(size + i); System.arraycopy(old, 0, data, 0, size); System.arraycopy(toAdd, 0, data, size, i); setData(data); return i; } finally { lock.unlock(); } } /** * Adds to this CopyOnWriteArrayList another element, given that this * element is not yet part of the list. * * @param e the potential new element. * * @return true if the element was added, or false otherwise. */ public boolean addIfAbsent(E e) { lock.lock(); try { E[] data; E[] old = getData(); int size = old.length; if (size != 0) { if (indexOf(e) >= 0) { return false; } } data = newElementArray(size + 1); System.arraycopy(old, 0, data, 0, size); data[size] = e; setData(data); return true; } finally { lock.unlock(); } } public void clear() { lock.lock(); try { setData(newElementArray(0)); } finally { lock.unlock(); } } @Override public Object clone() { try { CopyOnWriteArrayList thisClone = (CopyOnWriteArrayList) super.clone(); thisClone.setData(this.getData()); return thisClone; } catch (CloneNotSupportedException e) { throw new RuntimeException("CloneNotSupportedException is not expected here"); } } public boolean contains(Object o) { return indexOf(o) >= 0; } public boolean containsAll(Collection c) { E[] data = getData(); return containsAll(c, data, 0, data.length); } public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof List)) { return false; } List l = (List) o; Iterator it = l.listIterator(); Iterator ourIt = listIterator(); while (it.hasNext()) { if (!ourIt.hasNext()) { return false; } Object thisListElem = it.next(); Object anotherListElem = ourIt.next(); if (!(thisListElem == null ? anotherListElem == null : thisListElem .equals(anotherListElem))) { return false; } } if (ourIt.hasNext()) { return false; } return true; } public E get(int index) { E[] data = getData(); return data[index]; } public int hashCode() { int hashCode = 1; Iterator it = listIterator(); while (it.hasNext()) { Object obj = it.next(); hashCode = 31 * hashCode + (obj == null ? 0 : obj.hashCode()); } return hashCode; } /** * Returns the index of a given element, starting the search from a given * position in the list. * * @param e the element to search. * @param index the index at which to start the search. * * @return the index of the element or null, if the element has not been * found at or beyond the given start index. */ public int indexOf(E e, int index) { E[] data = getData(); return indexOf(e, data, index, data.length - index); } public int indexOf(Object o) { E[] data = getData(); return indexOf(o, data, 0, data.length); } public boolean isEmpty() { return size() == 0; } public Iterator iterator() { return new ListIteratorImpl(getData(), 0); } /** * Returns the last index of a given element, starting the search from * a given position in the list and going backwards. * * @param e the element to search. * @param index the index at which to start the search. * * @return the index of the element or null, if the element has not been * found at or before the given start index. */ public int lastIndexOf(E e, int index) { E[] data = getData(); return lastIndexOf(e, data, 0, index); } public int lastIndexOf(Object o) { E[] data = getData(); return lastIndexOf(o, data, 0, data.length); } public ListIterator listIterator() { return new ListIteratorImpl(getData(), 0); } public ListIterator listIterator(int index) { E[] data = getData(); checkIndexInclusive(index, data.length); return new ListIteratorImpl(data, index); } public E remove(int index) { return removeRange(index, 1); } public boolean remove(Object o) { lock.lock(); try { int index = indexOf(o); if (index == -1) { return false; } remove(index); return true; } finally { lock.unlock(); } } public boolean removeAll(Collection c) { lock.lock(); try { return removeAll(c, 0, getData().length) != 0; } finally { lock.unlock(); } } public boolean retainAll(Collection c) { if (c == null) { throw new NullPointerException(); } lock.lock(); try { return retainAll(c, 0, getData().length) != 0; } finally { lock.unlock(); } } public E set(int index, E e) { lock.lock(); try { int size = size(); checkIndexExlusive(index, size); E[] data; data = newElementArray(size); E[] oldArr = getData(); System.arraycopy(oldArr, 0, data, 0, size); E old = data[index]; data[index] = e; setData(data); return old; } finally { lock.unlock(); } } public int size() { return getData().length; } public List subList(int fromIndex, int toIndex) { return new SubList(this, fromIndex, toIndex); } public Object[] toArray() { E[] data = getData(); return toArray(data, 0, data.length); } public T[] toArray(T[] a) { E[] data = getData(); return (T[]) toArray(a, data, 0, data.length); } @Override public String toString() { StringBuilder sb = new StringBuilder("["); Iterator it = listIterator(); while (it.hasNext()) { sb.append(String.valueOf(it.next())); sb.append(", "); } if (sb.length() > 1) { sb.setLength(sb.length() - 2); } sb.append("]"); return sb.toString(); } // private and package private methods @SuppressWarnings("unchecked") private final E[] newElementArray(int size) { return (E[])new Object[size]; } /** * sets the internal data array * * @param data array to set */ private final void setData(E[] data) { arr = data; } /** * gets the internal data array (or a new array if it is null) * * @return the data array */ final E[] getData() { if (arr == null) { return newElementArray(0); } return arr; } /** * Removes from the specified range of this list * all the elements that are contained in the specified collection *

* !should be called under lock * * @return Returns the number of removed elements */ final int removeAll(Collection c, int start, int size) { int ssize = c.size(); if (ssize == 0) { return 0; } Object[] old = getData(); int arrsize = old.length; if (arrsize == 0) { return 0; } Object[] data = new Object[size]; int j = 0; for (int i = start; i < (start + size); i++) { if (!c.contains(old[i])) { data[j++] = old[i]; } } if (j != size) { E[] result = newElementArray(arrsize - (size - j)); System.arraycopy(old, 0, result, 0, start); System.arraycopy(data, 0, result, start, j); System.arraycopy(old, start + size, result, start + j, arrsize - (start + size)); setData(result); return (size - j); } return 0; } /** * Retains only the elements in the specified range of this list * that are contained in the specified collection * * @return Returns the number of removed elements */ // should be called under lock int retainAll(Collection c, int start, int size) { Object[] old = getData(); if (size == 0) { return 0; } if (c.size() == 0) { E[] data; if (size == old.length) { data = newElementArray(0); } else { data = newElementArray(old.length - size); System.arraycopy(old, 0, data, 0, start); System.arraycopy(old, start + size, data, start, old.length - start - size); } setData(data); return size; } Object[] temp = new Object[size]; int pos = 0; for (int i = start; i < (start + size); i++) { if (c.contains(old[i])) { temp[pos++] = old[i]; } } if (pos == size) { return 0; } E[] data = newElementArray(pos + old.length - size); System.arraycopy(old, 0, data, 0, start); System.arraycopy(temp, 0, data, start, pos); System.arraycopy(old, start + size, data, start + pos, old.length - start - size); setData(data); return (size - pos); } /** * Removes specified range from this list */ E removeRange(int start, int size) { lock.lock(); try { int sizeArr = size(); checkIndexExlusive(start, sizeArr); checkIndexInclusive(start + size, sizeArr); E[] data; data = newElementArray(sizeArr - size); E[] oldArr = getData(); System.arraycopy(oldArr, 0, data, 0, start); E old = oldArr[start]; if (sizeArr > (start + size)) { System.arraycopy(oldArr, start + size, data, start, sizeArr - (start + size)); } setData(data); return old; } finally { lock.unlock(); } } // some util static functions to use by iterators and methods /** * Returns an array containing all of the elements * in the specified range of the array in proper sequence */ static Object[] toArray(Object[] data, int start, int size) { Object[] result = new Object[size]; System.arraycopy(data, start, result, 0, size); return result; } /** * Returns an array containing all of the elements * in the specified range of the array in proper sequence, * stores the result in the array, specified by first parameter * (as for public instance method toArray(Object[] to) */ static Object[] toArray(Object[] to, Object[] data, int start, int size) { int l = data.length; if (to.length < l) { to = (Object[]) Array.newInstance(to.getClass().getComponentType(), l); } else { if (to.length > l) { to[l] = null; } } System.arraycopy(data, start, to, 0, size); return to; } /** * Checks if the specified range of the * array contains all of the elements in the collection * * @param c collection with elements * @param data array where to search the elements * @param start start index * @param size size of the range */ static final boolean containsAll(Collection c, Object[] data, int start, int size) { if (size == 0) { return false; } Iterator it = c.iterator(); while (it.hasNext()) { Object next = it.next(); if (indexOf(next, data, start, size) < 0) { return false; } } return true; } /** * Returns the index in the specified range of the data array * of the last occurrence of the specified element * * @param o element to search * @param data array where to search * @param start start index * @param size size of the range * @return */ static final int lastIndexOf(Object o, Object[] data, int start, int size) { if (size == 0) { return -1; } if (o != null) { for (int i = start + size - 1; i > start - 1; i--) { if (o.equals(data[i])) { return i; } } } else { for (int i = start + size - 1; i > start - 1; i--) { if (data[i] == null) { return i; } } } return -1; } /** * Returns the index in the specified range of the data array * of the first occurrence of the specified element * * @param o element to search * @param data array where to search * @param start start index * @param size end index * @return */ static final int indexOf(Object o, Object[] data, int start, int size) { if (size == 0) { return -1; } if (o == null) { for (int i = start; i < start + size; i++) { if (data[i] == null) { return i; } } } else { for (int i = start; i < start + size; i++) { if (o.equals(data[i])) { return i; } } } return -1; } /** * Throws IndexOutOfBoundsException if index * is out of the list bounds. * * @param index element index to check. */ static final void checkIndexInclusive(int index, int size) { if (index < 0 || index > size) { throw new IndexOutOfBoundsException("Index is " + index + ", size is " + size); } } /** * Throws IndexOutOfBoundsException if index * is out of the list bounds. Excluding the last element. * * @param index element index to check. */ static final void checkIndexExlusive(int index, int size) { if (index < 0 || index >= size) { throw new IndexOutOfBoundsException("Index is " + index + ", size is " + size); } } /** * gets the internal data array * * @return the data array */ final E[] getArray() { return arr; } /** * list iterator implementation, * when created gets snapshot of the list, * so never throws ConcurrentModificationException */ private static class ListIteratorImpl implements ListIterator { private final Object[] arr; private int current; private final int size; final int size() { return size; } public ListIteratorImpl(Object[] data, int current) { this.current = current; arr = data; size = data.length; } public void add(Object o) { throw new UnsupportedOperationException("Unsupported operation add"); } public boolean hasNext() { if (current < size) { return true; } return false; } public boolean hasPrevious() { return current > 0; } public Object next() { if (hasNext()) { return arr[current++]; } throw new NoSuchElementException("pos is " + current + ", size is " + size); } public int nextIndex() { return current; } public Object previous() { if (hasPrevious()) { return arr[--current]; } throw new NoSuchElementException("pos is " + (current-1) + ", size is " + size); } public int previousIndex() { return current - 1; } public void remove() { throw new UnsupportedOperationException("Unsupported operation remove"); } public void set(Object o) { throw new UnsupportedOperationException("Unsupported operation set"); } } /** * Keeps a state of sublist implementation, * size and array declared as final, * so we'll never get the unconsistent state */ static final class SubListReadData { final int size; final Object[] data; SubListReadData(int size, Object[] data) { this.size = size; this.data = data; } } /** * Represents a list returned by sublist(). */ static class SubList implements List { private final CopyOnWriteArrayList list; private volatile SubListReadData read; private final int start; /** * Sublist constructor. * * @param list backing list. * @param fromIdx startingIndex, inclusive * @param toIdx endIndex, exclusive */ public SubList(CopyOnWriteArrayList list, int fromIdx, int toIdx) { this.list = list; Object[] data = list.getData(); int size = toIdx - fromIdx; checkIndexExlusive(fromIdx, data.length); checkIndexInclusive(toIdx, data.length); read = new SubListReadData(size, list.getData()); start = fromIdx; } /** * throws ConcurrentModificationException when the list * is structurally modified in the other way other than via this subList *

* Should be called under lock! */ private void checkModifications() { if (read.data != list.getData()) { throw new ConcurrentModificationException(); } } /** * @see java.util.List#listIterator(int) */ public ListIterator listIterator(int startIdx) { return new SubListIterator(startIdx, read); } /** * @see java.util.List#set(int, java.lang.Object) */ public Object set(int index, Object obj) { list.lock.lock(); try { checkIndexExlusive(index, read.size); checkModifications(); Object result = list.set(index + start, obj); read = new SubListReadData(read.size, list.getData()); return result; } finally { list.lock.unlock(); } } /** * @see java.util.List#get(int) */ public Object get(int index) { SubListReadData data = read; if (data.data != list.getData()) { list.lock.lock(); try { data = read; if (data.data != list.getData()) { throw new ConcurrentModificationException(); } } finally { list.lock.unlock(); } } checkIndexExlusive(index, data.size); return data.data[index + start]; } /** * @see java.util.Collection#size() */ public int size() { return read.size; } /** * @see java.util.List#remove(int) */ public Object remove(int index) { list.lock.lock(); try { checkIndexExlusive(index, read.size); checkModifications(); Object obj = list.remove(index + start); read = new SubListReadData(read.size - 1, list.getData()); return obj; } finally { list.lock.unlock(); } } /** * @see java.util.List#add(int, java.lang.Object) */ public void add(int index, Object object) { list.lock.lock(); try { checkIndexInclusive(index, read.size); checkModifications(); list.add(index + start, object); read = new SubListReadData(read.size + 1, list.getData()); } finally { list.lock.unlock(); } } public boolean add(Object o) { list.lock.lock(); try { checkModifications(); list.add(start + read.size, o); read = new SubListReadData(read.size + 1, list.getData()); return true; } finally { list.lock.unlock(); } } public boolean addAll(Collection c) { list.lock.lock(); try { checkModifications(); int d = list.size(); list.addAll(start + read.size, c); read = new SubListReadData(read.size + (list.size() - d), list .getData()); return true; } finally { list.lock.unlock(); } } public void clear() { list.lock.lock(); try { checkModifications(); list.removeRange(start, read.size); read = new SubListReadData(0, list.getData()); } finally { list.lock.unlock(); } } public boolean contains(Object o) { return indexOf(o) != -1; } public boolean containsAll(Collection c) { SubListReadData b = read; return CopyOnWriteArrayList.containsAll(c, b.data, start, b.size); } public int indexOf(Object o) { SubListReadData b = read; int ind = CopyOnWriteArrayList.indexOf(o, b.data, start, b.size) - start; return ind < 0 ? -1 : ind; } public boolean isEmpty() { return read.size == 0; } public Iterator iterator() { return new SubListIterator(0, read); } public int lastIndexOf(Object o) { SubListReadData b = read; int ind = CopyOnWriteArrayList .lastIndexOf(o, b.data, start, b.size) - start; return ind < 0 ? -1 : ind; } public ListIterator listIterator() { return new SubListIterator(0, read); } public boolean remove(Object o) { list.lock.lock(); try { checkModifications(); int i = indexOf(o); if (i == -1) { return false; } boolean result = list.remove(i + start) != null; if (result) { read = new SubListReadData(read.size - 1, list.getData()); } return result; } finally { list.lock.unlock(); } } public boolean removeAll(Collection c) { list.lock.lock(); try { checkModifications(); int removed = list.removeAll(c, start, read.size); if (removed > 0) { read = new SubListReadData(read.size - removed, list .getData()); return true; } } finally { list.lock.unlock(); } return false; } public boolean retainAll(Collection c) { list.lock.lock(); try { checkModifications(); int removed = list.retainAll(c, start, read.size); if (removed > 0) { read = new SubListReadData(read.size - removed, list .getData()); return true; } return false; } finally { list.lock.unlock(); } } public List subList(int fromIndex, int toIndex) { return new SubList(list, start + fromIndex, start + toIndex); } public Object[] toArray() { SubListReadData r = read; return CopyOnWriteArrayList.toArray(r.data, start, r.size); } public Object[] toArray(Object[] a) { SubListReadData r = read; return CopyOnWriteArrayList.toArray(a, r.data, start, r.size); } /** * @see java.util.List#addAll(int, java.util.Collection) */ public boolean addAll(int index, Collection collection) { list.lock.lock(); try { checkIndexInclusive(index, read.size); checkModifications(); int d = list.size(); boolean rt = list.addAll(index + start, collection); read = new SubListReadData(read.size + list.size() - d, list .getData()); return rt; } finally { list.lock.unlock(); } } /** * Implementation of ListIterator for the * SubList * gets a snapshot of the sublist, * never throws ConcurrentModificationException */ private class SubListIterator extends ListIteratorImpl { private final SubListReadData dataR; /** * Constructs an iterator starting with the given index * * @param index index of the first element to iterate. */ private SubListIterator(int index, SubListReadData d) { super(d.data, index + start); this.dataR = d; } /** * @see java.util.ListIterator#nextIndex() */ public int nextIndex() { return super.nextIndex() - start; } /** * @see java.util.ListIterator#previousIndex() */ public int previousIndex() { return super.previousIndex() - start; } /** * @see java.util.Iterator#hasNext() */ public boolean hasNext() { return nextIndex() < dataR.size; } /** * @see java.util.ListIterator#hasPrevious() */ public boolean hasPrevious() { return previousIndex() > -1; } } } //serialization support /** * Writes the object state to the ObjectOutputStream. * * @param oos ObjectOutputStream to write object to. * @throws IOException if an I/O error occur. */ private void writeObject(ObjectOutputStream oos) throws IOException { E[] back = getData(); int size = back.length; oos.defaultWriteObject(); oos.writeInt(size); for (int i = 0; i < size; i++) { oos.writeObject(back[i]); } } /** * Reads the object state from the ObjectOutputStream. * * @param ois ObjectInputStream to read object from. * @throws IOException if an I/O error occur. */ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); int length = ois.readInt(); if (length == 0) { setData(newElementArray(0)); } else { E[] back = newElementArray(length); for (int i = 0; i < back.length; i++) { back[i] = (E) ois.readObject(); } setData(back); } } }