blob: 85610710eaa65e9f7c7dc99ae3d192880dcb2546 [file] [log] [blame]
/**
* 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", "null"})
@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", "null"})
@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.
*/
@SuppressWarnings("null")
@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.
*/
@SuppressWarnings("null")
@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.
*/
@SuppressWarnings("null")
@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.
*/
@SuppressWarnings("null")
@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);
}
}
}