/** * Copyright (c) 2009 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM - Initial API and implementation */ package org.eclipse.emf.common.util; 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.RandomAccess; /** * A highly extensible abstract list implementation {@link #data() logically backed by an array} that is <b>never<b> modified. * */ public abstract class ArrayDelegatingEList<E> extends AbstractEList<E> implements RandomAccess, Cloneable, Serializable { private static final long serialVersionUID = 1L; /** * Creates an empty instance with no initial capacity. * The data storage will be null. */ public ArrayDelegatingEList() { super(); } /** * Creates an instance that is a copy of the collection. * @param collection the initial contents of the list. */ public ArrayDelegatingEList(Collection<? extends E> collection) { // Conditionally create the data. // int size = collection.size(); if (size > 0) { Object [] data = newData(size); collection.toArray(data); setData(data); } } /** * Creates an initialized instance that directly uses the given arguments. * @param data the underlying storage of the list. */ protected ArrayDelegatingEList(Object [] data) { setData(data); } /** * Returns new allocated data storage. * Clients may override this to create typed storage. * The cost of type checking via a typed array is negligible. * @return new data storage. */ protected Object [] newData(int capacity) { return new Object [capacity]; } /** * Assigns the object into the data storage at the given index and returns the object that's been stored. * Clients can monitor access to the storage via this method. * @param index the position of the new content. * @param object the new content. * @return the object that's been stored. * */ protected E assign(Object [] data, int index, E object) { data[index] = object; return object; } /** * Returns the number of objects in the list. * @return the number of objects in the list. */ @Override public final int size() { Object[] data = data(); return data == null ? 0 : data.length; } /** * Returns whether the list has zero size. * @return whether the list has zero size. */ @Override public boolean isEmpty() { return data() == null; } /** * Returns whether the list contains the object. * This implementation uses either <code>equals</code> or <code>"=="</code> depending on {@link #useEquals useEquals}. * @param object the object in question. * @return whether the list contains the object. * @see #useEquals */ @Override public boolean contains(Object object) { Object[] data = data(); if (data != null) { if (useEquals() && object != null) { for (Object datum : data) { if (object.equals(datum)) { return true; } } } else { for (Object datum : data) { if (datum == object) { return true; } } } } return false; } /** * Returns the position of the first occurrence of the object in the list. * This implementation uses either <code>equals</code> or <code>"=="</code> depending on {@link #useEquals useEquals}. * @param object the object in question. * @return the position of the first occurrence of the object in the list. */ @Override public int indexOf(Object object) { Object[] data = data(); if (data != null) { if (useEquals() && object != null) { for (int i = 0, size = data.length; i < size; ++i) { if (object.equals(data[i])) { return i; } } } else { for (int i = 0, size = data.length; i < size; ++i) { if (data[i] == object) { return i; } } } } return -1; } /** * Returns the position of the last occurrence of the object in the list. * This implementation uses either <code>equals</code> or <code>"=="</code> depending on {@link #useEquals useEquals}. * @param object the object in question. * @return the position of the last occurrence of the object in the list. */ @Override public int lastIndexOf(Object object) { Object[] data = data(); if (data != null) { if (useEquals() && object != null) { for (int i = data.length - 1; i >= 0; --i) { if (object.equals(data[i])) { return i; } } } else { for (int i = data.length - 1; i >= 0; --i) { if (data[i] == object) { return i; } } } } return -1; } /** * Returns an array containing all the objects in sequence. * Clients may override {@link #newData newData} to create typed storage in this case. * @return an array containing all the objects in sequence. * @see #newData */ @Override public Object[] toArray() { Object[] data = data(); int size = data == null ? 0 : data.length; Object[] result = newData(size); // Guard for no data. // if (size > 0) { System.arraycopy(data, 0, result, 0, size); } return result; } /** * Returns an array containing all the objects in sequence. * @param array the array that will be filled and returned, if it's big enough; * otherwise, a suitably large array of the same type will be allocated and used instead. * @return an array containing all the objects in sequence. * @see #newData */ @Override public <T> T[] toArray(T[] array) { // Guard for no data. // Object[] data = data(); int size = data == null ? 0 : data.length; if (size > 0) { if (array.length < size) { @SuppressWarnings("unchecked") T [] newArray = (T[])Array.newInstance(array.getClass().getComponentType(), size); array = newArray; } System.arraycopy(data, 0, array, 0, size); } if (array.length > size) { array[size] = null; } return array; } /** * Returns direct <b>unsafe</b> access to the underlying data storage. * Clients may <b>not</b> modify this array's elements * but <b>may</b> assume that the array remains valid even as the list is modified. * The array's length is exactly the same as the list's {@link #size() size}; * <code>null</code> is used to represent the empty list. * @return direct <b>unsafe</b> access to the underlying data storage. */ public abstract Object [] data(); /** * Updates directly and <b>unsafely</b> the underlying data storage. * Clients <b>must</b> be aware that this subverts all callbacks * and hence possibly the integrity of the list. * The list implementation itself calls this method whenever a modification of the list requires a new backing array. * @param data the new underlying data storage. */ public void setData(Object [] data) { ++modCount; } /** * Returns the object at the index. * This implementation delegates to {@link #resolve resolve} * so that clients may transform the fetched object. * @param index the position in question. * @return the object at the index. * @exception IndexOutOfBoundsException if the index isn't within the size range. * @see #resolve * @see #basicGet */ @SuppressWarnings("unchecked") @Override public E get(int index) { Object[] data = data(); int size = data == null ? 0 : data.length; if (index >= size) throw new BasicIndexOutOfBoundsException(index, size); return resolve(index, (E)data[index]); } /** * Returns the object at the index without {@link #resolve resolving} it. * @param index the position in question. * @return the object at the index. * @exception IndexOutOfBoundsException if the index isn't within the size range. * @see #resolve * @see #get */ @SuppressWarnings("unchecked") @Override public E basicGet(int index) { Object[] data = data(); int size = data == null ? 0 : data.length; if (index >= size) throw new BasicIndexOutOfBoundsException(index, size); return (E)data[index]; } @Override @SuppressWarnings({"unchecked"}) protected E primitiveGet(int index) { return (E)data()[index]; } /** * Sets the object at the index * and returns the old object at the index; * it does no ranging checking or uniqueness checking. * This implementation delegates to {@link #assign assign}, {@link #didSet didSet}, and {@link #didChange didChange}. * @param index the position in question. * @param object the object to set. * @return the old object at the index. * @see #set */ @Override public E setUnique(int index, E object) { Object[] data = copy(); @SuppressWarnings("unchecked") E oldObject = (E)data[index]; assign(data, index, validate(index, object)); setData(data); didSet(index, object, oldObject); didChange(); return oldObject; } /** * Adds the object at the end of the list; * it does no uniqueness checking. * This implementation delegates to {@link #assign assign}, {@link #didAdd didAdd}, and {@link #didChange didChange}. * after uniqueness checking. * @param object the object to be added. * @see #add(Object) */ @Override public void addUnique(E object) { int size = size(); Object [] data = grow(size + 1); assign(data, size, validate(size, object)); setData(data); didAdd(size, object); didChange(); } /** * Adds the object at the given index in the list; * it does no ranging checking or uniqueness checking. * This implementation delegates to {@link #assign assign}, {@link #didAdd didAdd}, and {@link #didChange didChange}. * @param object the object to be added. * @see #add(int, Object) */ @Override public void addUnique(int index, E object) { Object[] oldData = data(); int size = oldData == null ? 0 : oldData.length; Object[] data = grow(size + 1); E validatedObject = validate(index, object); if (index != size) { System.arraycopy(oldData, index, data, index + 1, size - index); } assign(data, index, validatedObject); setData(data); didAdd(index, object); didChange(); } /** * Adds each object of the collection to the end of the list; * it does no uniqueness checking. * This implementation delegates to {@link #assign assign}, {@link #didAdd didAdd}, and {@link #didChange didChange}. * @param collection the collection of objects to be added. * @see #addAll(Collection) */ @Override public boolean addAllUnique(Collection<? extends E> collection) { int growth = collection.size(); if (growth != 0) { int oldSize = size(); int size = oldSize + growth; Object[] data = grow(size); Iterator<? extends E> objects = collection.iterator(); for (int i = oldSize; i < size; ++i) { E object = objects.next(); assign(data, i, validate(i, object)); } setData(data); for (int i = oldSize; i < size; ++i) { @SuppressWarnings("unchecked") E object = (E)data[i]; didAdd(i, object); didChange(); } return true; } else { ++modCount; return false; } } /** * Adds each object of the collection at each successive index in the list * and returns whether any objects were added; * it does no ranging checking or uniqueness checking. * This implementation delegates to {@link #assign assign}, {@link #didAdd didAdd}, and {@link #didChange didChange}. * @param index the index at which to add. * @param collection the collection of objects to be added. * @return whether any objects were added. * @see #addAll(int, Collection) */ @Override public boolean addAllUnique(int index, Collection<? extends E> collection) { int growth = collection.size(); if (growth != 0) { Object[] oldData = data(); int oldSize = oldData == null ? 0 : oldData.length; int size = oldSize + growth; Object[] data = grow(size); int shifted = oldSize - index; if (shifted > 0) { System.arraycopy(oldData, index, data, index + growth, shifted); } Iterator<? extends E> objects = collection.iterator(); for (int i = 0; i < growth; ++i) { E object = objects.next(); int currentIndex = index + i; assign(data, currentIndex, validate(currentIndex, object)); } setData(data); for (int i = 0; i < growth; ++i) { @SuppressWarnings("unchecked") E object = (E)data[index]; didAdd(index, object); didChange(); index++; } return true; } else { ++modCount; return false; } } /** * Adds each object from start to end of the array at the index of list * and returns whether any objects were added; * it does no ranging checking or uniqueness checking. * This implementation delegates to {@link #assign assign}, {@link #didAdd didAdd}, and {@link #didChange didChange}. * @param objects the objects to be added. * @param start the index of first object to be added. * @param end the index past the last object to be added. * @return whether any objects were added. * @see #addAllUnique(Object[], int, int) */ @Override public boolean addAllUnique(Object [] objects, int start, int end) { int growth = end - start; if (growth > 0) { int oldSize = size(); int size = oldSize + growth; Object[] data = grow(size); for (int i = start, index = size; i < end; ++i, ++index) { @SuppressWarnings("unchecked") E object = (E)objects[i]; assign(data, index, validate(index, object)); } setData(data); for (int i = start, index = size; i < end; ++i, ++index) { @SuppressWarnings("unchecked") E object = (E)objects[i]; didAdd(index, object); didChange(); } return true; } else { ++modCount; return false; } } /** * Adds each object from start to end of the array at each successive index in the list * and returns whether any objects were added; * it does no ranging checking or uniqueness checking. * This implementation delegates to {@link #assign assign}, {@link #didAdd didAdd}, and {@link #didChange didChange}. * @param index the index at which to add. * @param objects the objects to be added. * @param start the index of first object to be added. * @param end the index past the last object to be added. * @return whether any objects were added. * @see #addAllUnique(Object[], int, int) */ @Override public boolean addAllUnique(int index, Object [] objects, int start, int end) { int growth = end - start; if (growth > 0) { Object[] oldData = data(); int oldSize = oldData == null ? 0 : oldData.length; int size = oldSize + growth; Object[] data = grow(size); int shifted = oldSize - index; if (shifted > 0) { System.arraycopy(oldData, index, data, index + growth, shifted); } for (int i = start, currentIndex = index; i < end; ++i, ++currentIndex) { @SuppressWarnings("unchecked") E object = (E)objects[i]; assign(data, currentIndex, validate(currentIndex, object)); } setData(data); for (int i = start, currentIndex = index; i < end; ++i, ++currentIndex) { @SuppressWarnings("unchecked") E object = (E)objects[i]; didAdd(currentIndex, object); didChange(); } return true; } else { ++modCount; return false; } } /** * Removes each object of the collection from the list and returns whether any object was actually contained by the list. * @param collection the collection of objects to be removed. * @return whether any object was actually contained by the list. */ @Override public boolean removeAll(Collection<?> collection) { Object[] data = data(); int size = data == null ? 0 : data.length; boolean modified = false; for (int i = size; --i >= 0; ) { if (collection.contains(data[i])) { remove(i); modified = true; } } return modified; } /** * Removes the object at the index from the list and returns it. * This implementation delegates to {@link #didRemove didRemove} and {@link #didChange didChange}. * @param index the position of the object to remove. * @return the removed object. * @exception IndexOutOfBoundsException if the index isn't within the size range. */ @Override public E remove(int index) { Object[] data = data(); int size = data == null ? 0 : data.length; if (index >= size) throw new BasicIndexOutOfBoundsException(index, size); @SuppressWarnings("unchecked") E oldObject = (E)data[index]; Object[] newData; if (size == 1) { newData = null; } else { newData = newData(size - 1); System.arraycopy(data, 0, newData, 0, index); int shifted = size - index - 1; if (shifted > 0) { System.arraycopy(data, index+1, newData, index, shifted); } } setData(newData); didRemove(index, oldObject); didChange(); return oldObject; } /** * Removes from the list each object not contained by the collection * and returns whether any object was actually removed. * This delegates to {@link #remove(int) remove(int)} * in the case that it finds an object that isn't retained. * @param collection the collection of objects to be retained. * @return whether any object was actually removed. */ @Override public boolean retainAll(Collection<?> collection) { boolean modified = false; Object[] data = data(); int size = data == null ? 0 : data.length; for (int i = size; --i >= 0; ) { if (!collection.contains(data[i])) { remove(i); modified = true; } } return modified; } /** * Clears the list of all objects. * This implementation discards the data storage without modifying it * and delegates to {@link #didClear didClear} and {@link #didChange didChange}. */ @Override public void clear() { ++modCount; Object[] oldData = data(); int oldSize = oldData == null ? 0 : oldData.length; // Give it all back to the garbage collector. // setData(null); didClear(oldSize, oldData); didChange(); } /** * Moves the object at the source index of the list to the target index of the list * and returns the moved object. * This implementation delegates to {@link #assign assign}, {@link #didMove didMove}, and {@link #didChange didChange}. * @param targetIndex the new position for the object in the list. * @param sourceIndex the old position of the object in the list. * @return the moved object. * @exception IndexOutOfBoundsException if either index isn't within the size range. */ @Override public E move(int targetIndex, int sourceIndex) { Object[] data = copy(); int size = data == null ? 0 : data.length; if (targetIndex >= size) throw new IndexOutOfBoundsException("targetIndex=" + targetIndex + ", size=" + size); if (sourceIndex >= size) throw new IndexOutOfBoundsException("sourceIndex=" + sourceIndex + ", size=" + size); @SuppressWarnings("unchecked") E object = (E)data[sourceIndex]; if (targetIndex != sourceIndex) { if (targetIndex < sourceIndex) { System.arraycopy(data, targetIndex, data, targetIndex + 1, sourceIndex - targetIndex); } else { System.arraycopy(data, sourceIndex + 1, data, sourceIndex, targetIndex - sourceIndex); } assign(data, targetIndex, object); setData(data); didMove(targetIndex, object, sourceIndex); didChange(); } return object; } /** * Grows the capacity of the list to exactly the new size. */ protected Object[] grow(int size) { Object[] oldData = data(); Object[] data = newData(size); if (oldData != null) { System.arraycopy(oldData, 0, data, 0, oldData.length); } return data; } private static Object [] EMPTY_ARRAY = new Object [0]; /** * Returns a copy of the existing data array. */ protected Object[] copy() { Object[] data = data(); if (data != null) { Object[] newData = newData(data.length); System.arraycopy(data, 0, newData, 0, data.length); return newData; } else { return EMPTY_ARRAY; } } private synchronized void writeObject(ObjectOutputStream objectOutputStream) throws IOException { objectOutputStream.defaultWriteObject(); Object[] data = data(); int size = data == null ? 0 : data.length; if (data == null) { objectOutputStream.writeInt(0); } else { objectOutputStream.writeInt(size); for (int i = 0; i < size; ++i) { objectOutputStream.writeObject(data[i]); } } } private synchronized void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException { objectInputStream.defaultReadObject(); int size = objectInputStream.readInt(); Object[] data; if (size > 0) { try { data = newData(size); } catch (Throwable exception) { data = new Object[size]; } setData(data); for (int i = 0; i < size; ++i) { @SuppressWarnings("unchecked") E object = (E)objectInputStream.readObject(); didAdd(i, assign(data, i, object)); } } } /** * Returns a shallow copy of this list. * @return a shallow copy of this list. */ @Override public Object clone() { try { @SuppressWarnings("unchecked") ArrayDelegatingEList<E> clone = (ArrayDelegatingEList<E>)super.clone(); Object[] data = data(); int size = data == null ? 0 : data.length; if (size > 0) { Object[] newData = newData(size); clone.setData(newData); System.arraycopy(data, 0, newData, 0, size); } return clone; } catch (CloneNotSupportedException exception) { throw new InternalError(); } } /** * Returns an iterator. * This implementation allocates a {@link ArrayDelegatingEList.EIterator}. * @return an iterator. * @see ArrayDelegatingEList.EIterator */ @Override public Iterator<E> iterator() { return new EIterator<E>(); } /** * An extensible iterator implementation. */ protected class EIterator<E1> extends AbstractEList<E>.EIterator<E1> { /** * The expected data array of the containing list. */ protected Object [] expectedData = data(); /** * Checks that the modification count and data array are as expected. * @exception ConcurrentModificationException if the modification count is not as expected. */ @Override protected void checkModCount() { if (modCount != expectedModCount || data() != expectedData) { throw new ConcurrentModificationException(); } } @Override public void remove() { super.remove(); expectedData = data(); } } /** * Returns a read-only iterator that does not {@link #resolve resolve} objects. * This implementation allocates a {@link NonResolvingEIterator}. * @return a read-only iterator that does not resolve objects. */ @Override protected Iterator<E> basicIterator() { return new NonResolvingEIterator<E>(); } /** * An extended read-only iterator that does not {@link ArrayDelegatingEList#resolve resolve} objects. */ protected class NonResolvingEIterator<E1> extends AbstractEList<E>.NonResolvingEIterator<E1> { /** * The expected data array of the containing list. */ protected Object [] expectedData = data(); /** * Checks that the modification count and data array are as expected. * @exception ConcurrentModificationException if the modification count is not as expected. */ @Override protected void checkModCount() { if (modCount != expectedModCount || data() != expectedData) { throw new ConcurrentModificationException(); } } } /** * Returns a list iterator. * This implementation allocates a {@link ArrayDelegatingEList.EListIterator}. * @return a list iterator. * @see ArrayDelegatingEList.EListIterator */ @Override public ListIterator<E> listIterator() { return new EListIterator<E>(); } /** * Returns a list iterator advanced to the given index. * This implementation allocates a {@link ArrayDelegatingEList.EListIterator}. * @param index the starting index. * @return a list iterator advanced to the index. * @see ArrayDelegatingEList.EListIterator * @exception IndexOutOfBoundsException if the index isn't within the size range. */ @Override public ListIterator<E> listIterator(int index) { int size = size(); if (index < 0 || index > size) throw new BasicIndexOutOfBoundsException(index, size); return new EListIterator<E>(index); } /** * An extensible list iterator implementation. */ protected class EListIterator<E1> extends AbstractEList<E>.EListIterator<E1> { /** * The expected data array of the containing list. */ protected Object [] expectedData = data(); /** * Creates an instance. */ public EListIterator() { super(); } /** * Creates an instance advanced to the index. * @param index the starting index. */ public EListIterator(int index) { super(index); } /** * Checks that the modification count and data array are as expected. * @exception ConcurrentModificationException if the modification count is not as expected. */ @Override protected void checkModCount() { if (modCount != expectedModCount || data() != expectedData) { throw new ConcurrentModificationException(); } } @Override public void remove() { super.remove(); expectedData = data(); } @Override protected void doSet(E object) { super.doSet(object); expectedData = data(); } } /** * Returns a read-only list iterator that does not {@link #resolve resolve} objects. * This implementation allocates a {@link NonResolvingEListIterator}. * @return a read-only list iterator that does not resolve objects. */ @Override protected ListIterator<E> basicListIterator() { return new NonResolvingEListIterator<E>(); } /** * Returns a read-only list iterator advanced to the given index that does not {@link #resolve resolve} objects. * This implementation allocates a {@link NonResolvingEListIterator}. * @param index the starting index. * @return a read-only list iterator advanced to the index. * @exception IndexOutOfBoundsException if the index isn't within the size range. */ @Override protected ListIterator<E> basicListIterator(int index) { int size = size(); if (index < 0 || index > size) throw new BasicIndexOutOfBoundsException(index, size); return new NonResolvingEListIterator<E>(index); } /** * An extended read-only list iterator that does not {@link ArrayDelegatingEList#resolve resolve} objects. */ protected class NonResolvingEListIterator<E1> extends AbstractEList<E>.NonResolvingEListIterator<E1> { /** * The expected data array of the containing list. */ protected Object [] expectedData = data(); /** * Creates an instance. */ public NonResolvingEListIterator() { super(); } /** * Creates an instance advanced to the index. * @param index the starting index. */ public NonResolvingEListIterator(int index) { super(index); } /** * Checks that the modification count and data array are as expected. * @exception ConcurrentModificationException if the modification count is not as expected. */ @Override protected void checkModCount() { if (modCount != expectedModCount || data() != expectedData) { throw new ConcurrentModificationException(); } } } /** * Returns an <b>unsafe</b> list that provides a {@link #resolve non-resolving} view of the underlying data storage. * @return an <b>unsafe</b> list that provides a non-resolving view of the underlying data storage. */ @Override protected List<E> basicList() { Object[] data = data(); int size = data == null ? 0 : data.length; if (size == 0) { return ECollections.emptyEList(); } else { return new BasicEList.UnmodifiableEList<E>(size, data); } } }