package com.webobjects.foundation;
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.Vector;
/**
* <div class="en">
* Bugfix reimplementation of NSMutableArray. To be able to use this class, the framework this class resides in must be
* before JavaFoundation.framework in your classpath.
* <p>
* It fixes a lot of issues:
* <ul>
* <li>implements the correct Collection methods, so you can <code>anArray.add(anObject)</code>
* <li>has several performance improvements that make EOF faster by several orders of magnitude when you have large record sets
* <li>fixes a bug when the actual objects were handed out replaceObjectAtIndex()
* <li>fixes a bug when the iterator method wasn't firing a fault in <code>_EOCheapCopyMutableArray</code>
* </ul>
*
* Once these issues are resolved in a WO distribution, this class will go away and the Apple
* supplied will will be used again without changes in code on your side.
* </div>
*
* <div class="ja">
* NSMutableArray のバッグフィックス再実装。
* このクラスを使用する為には現フレームワークのクラスパスが JavaFoundation.framework の前にある必要があります。
* <p>
* 次の問題を対応しています:
* <ul>
* <li>正しいコレクション・メソッドの実装:<code>anArray.add(anObject)</code> が可能
* <li>大きなレコード・セットの場合でのスピード改良で EOF が早くなるのです
* <li>実際のオブジェクトが replaceObjectAtIndex() の外で処理されるバッグフィックス
* <li><code>_EOCheapCopyMutableArray</code> でのフォルトはトリーガされない問題のバッグフィックス
* </ul>
* </div>
*
* @param <E> type of array contents
*
* @author ak
*/
public class NSMutableArray <E> extends NSArray<E> implements RandomAccess {
static final long serialVersionUID = -3909373569895711876L;
public static final Class _CLASS = _NSUtilitiesExtra._classWithFullySpecifiedNamePrime("com.webobjects.foundation.NSMutableArray");
public static final Object ERX_MARKER = "Wonder";
protected transient int modCount = 0;
protected transient int _capacity;
protected transient Object[] _objectsCache;
protected transient int _count;
public NSMutableArray() {
}
public NSMutableArray(Collection<? extends E> collection) {
super(collection);
}
public NSMutableArray(int capacity) {
if (capacity < 0) {
throw new IllegalArgumentException("Capacity cannot be less than 0");
}
_ensureCapacity(capacity);
}
public NSMutableArray(E object) {
super(object);
}
public NSMutableArray(E[] objects) {
super(objects);
}
public NSMutableArray(E object, E... objects) {
super(object, objects);
}
public NSMutableArray(E[] objects, NSRange range) {
super(objects, range);
}
public NSMutableArray(NSArray<? extends E> otherArray) {
super(otherArray);
}
public NSMutableArray(Vector<? extends E> vector, NSRange range, boolean ignoreNull) {
super(vector, range, ignoreNull);
}
public NSMutableArray(List<? extends E> list, NSRange range, boolean ignoreNull) {
super(list, range, ignoreNull);
}
@Override
protected void _initializeWithCapacity(int capacity) {
_capacity = capacity;
_objectsCache = null;
super._initializeWithCapacity(capacity);
}
protected void _ensureCapacity(int capacity) {
if (capacity > _capacity) {
if (capacity == 0) {
_setObjects(null);
} else {
if (capacity < 4) {
capacity = 4;
} else {
int testCapacity = 2 * _capacity;
if (testCapacity > capacity) {
capacity = testCapacity;
}
}
Object[] objs = _objects();
objs = objs != null ? _NSCollectionPrimitives.copyArray(objs, capacity) : new Object[capacity];
_setObjects(objs);
}
_capacity = capacity;
}
}
public void ensureCapacity(int capacity) {
_ensureCapacity(capacity);
}
public void trimToSize() {
// no op
}
@Override
protected void _setCount(int count) {
_count = count;
}
public void setArray(NSArray<? extends E> otherArray) {
if (otherArray != this) {
if (otherArray == null) {
_setCount(0);
} else {
Object[] objects = otherArray.objectsNoCopy();
_ensureCapacity(objects.length);
int count = count();
Object[] objs = _objects();
if (objects.length > 0)
System.arraycopy(objects, 0, objs, 0, objects.length);
for (int i = objects.length; i < count; i++)
objs[i] = null;
_setCount(objects.length);
}
clearCache();
}
}
@Override
protected Object[] objectsNoCopy() {
if (_objectsCache == null) {
int count = count();
if (count == 0) {
_objectsCache = _NSCollectionPrimitives.EmptyArray;
}
else if (_count == _capacity) {
_objectsCache = _objects();
}
else {
_objectsCache = _NSCollectionPrimitives.copyArray(_objects(), count);
}
}
return _objectsCache;
}
@Override
public int count() {
return _count;
}
public void addObject(E object) {
if (object == null) {
throw new IllegalArgumentException("Attempt to insert null into an " + getClass().getName() + ".");
}
int count = count();
_ensureCapacity(count+1);
_objects()[count] = object;
_setCount(count+1);
clearCache();
}
public void addObjects(E... objects) {
if (objects != null && objects.length > 0) {
for (int i = 0; i < objects.length; i++)
if (objects[i] == null)
throw new IllegalArgumentException("Attempt to insert null into an " + getClass().getName() + ".");
int count = count();
_ensureCapacity(count + objects.length);
System.arraycopy(objects, 0, _objects(), count, objects.length);
_setCount(count + objects.length);
clearCache();
}
}
/**
* @deprecated use {@link #replaceObjectAtIndex(Object, int)}
*/
@Deprecated
public void replaceObjectAtIndex(int index, E object) {
replaceObjectAtIndex(object, index);
}
public void insertObjectAtIndex(E object, int index) {
if (object == null)
throw new IllegalArgumentException("Attempt to insert null into an " + getClass().getName() + ".");
int count = count();
if (index >= 0 && index <= count) {
_ensureCapacity(count + 1);
Object[] objs = _objects();
if (index < count)
System.arraycopy(objs, index, objs, index + 1, count - index);
objs[index] = object;
_setCount(count+1);
clearCache();
return;
}
throw new IndexOutOfBoundsException("Index (" + index + ") out of bounds [0, " + (count - 1) + "]");
}
public E removeObjectAtIndex(int index) {
int count = count();
if (index >= 0 && index < count) {
count--;
Object[] objs = _objects();
Object result = objs[index];
if (index < count)
System.arraycopy(objs, index + 1, objs, index, count - index);
objs[count] = null;
_setCount(count);
clearCache();
return (E) result;
}
if (count == 0)
throw new IndexOutOfBoundsException("Array is empty");
throw new IndexOutOfBoundsException("Index (" + index + ") out of bounds [0, " + (count - 1) + "]");
}
public void removeAllObjects() {
if (count() > 0) {
_setObjects(new Object[_capacity]);
_setCount(0);
clearCache();
}
}
public void sortUsingComparator(NSComparator comparator) throws NSComparator.ComparisonException {
if (comparator == null)
throw new IllegalArgumentException("Comparator not specified");
int count = count();
if (count < 2) {
return;
}
_NSCollectionPrimitives.K2SortArray(_objects(), count, comparator);
clearCache();
}
public void addObjectsFromArray(NSArray<? extends E> otherArray) {
if (otherArray != null)
addObjects((E[])otherArray.objectsNoCopy());
}
public void replaceObjectsInRange(NSRange range, NSArray<? extends E> otherArray, NSRange otherRange) {
if (range == null || otherRange == null) {
throw new IllegalArgumentException("Both ranges cannot be null");
}
if (otherArray == null) {
throw new IllegalArgumentException("Other array cannot be null");
}
int rangeLength = range.length();
int rangeLocation = range.location();
int otherRangeLength = otherRange.length();
int otherRangeLocation = otherRange.location();
for (; 0 < rangeLength && 0 < otherRangeLength; otherRangeLength--) {
replaceObjectAtIndex(otherArray.objectAtIndex(otherRangeLocation), rangeLocation);
rangeLocation++;
rangeLength--;
otherRangeLocation++;
}
for (; 0 < otherRangeLength; otherRangeLength--) {
insertObjectAtIndex(otherArray.objectAtIndex(otherRangeLocation), rangeLocation);
rangeLocation++;
otherRangeLocation++;
}
for (; 0 < rangeLength; rangeLength--) {
removeObjectAtIndex(rangeLocation);
}
}
public E removeLastObject() {
if (count() == 0) {
return null;
}
return removeObjectAtIndex(count() - 1);
}
private boolean _removeObject(Object object, int index, int length, boolean identical) {
boolean wasRemoved = false;
if (object == null) {
throw new IllegalArgumentException("Attempt to remove null from an " + getClass().getName() + ".");
}
if (count() > 0) {
Object[] objects = objectsNoCopy();
int maxIndex = (index + length) - 1;
if (identical) {
for (int i = maxIndex; i >= index; i--)
if (objects[i] == object) {
removeObjectAtIndex(i);
wasRemoved = true;
}
} else if (!identical) {
for (int i = maxIndex; i >= index; i--)
if (objects[i] == object || object.equals(objects[i])) {
removeObjectAtIndex(i);
wasRemoved = true;
}
}
}
return wasRemoved;
}
public boolean removeObject(Object object) {
return _removeObject(object, 0, count(), false);
}
public boolean removeObject(Object object, NSRange range) {
boolean wasRemoved = false;
if (range != null) {
int count = count();
int rangeLocation = range.location();
int rangeLength = range.length();
if (rangeLocation + rangeLength > count || rangeLocation >= count)
throw new IllegalArgumentException("Range [" + rangeLocation + "; " + rangeLength
+ "] out of bounds [0, " + (count - 1) + "]");
wasRemoved = _removeObject(object, rangeLocation, rangeLength, false);
}
return wasRemoved;
}
public boolean removeIdenticalObject(Object object) {
return _removeObject(object, 0, count(), true);
}
public boolean removeIdenticalObject(Object object, NSRange range) {
if (range != null) {
int count = count();
int rangeLocation = range.location();
int rangeLength = range.length();
if (rangeLocation + rangeLength > count || rangeLocation >= count) {
throw new IllegalArgumentException("Range [" + rangeLocation + "; " + rangeLength
+ "] out of bounds [0, " + (count - 1) + "]");
}
return _removeObject(object, rangeLocation, rangeLength, true);
}
return false;
}
public void removeObjectsInArray(NSArray<?> otherArray) {
if (otherArray != null) {
removeObjects(otherArray.objectsNoCopy());
}
}
public void removeObjectsInRange(NSRange range) {
if (range != null) {
int count = count();
int rangeLocation = range.location();
int rangeLength = range.length();
if (rangeLocation + rangeLength > count || rangeLocation >= count)
throw new IllegalArgumentException("Range [" + rangeLocation + "; " + rangeLength
+ "] out of bounds [0, " + (count - 1) + "]");
while (rangeLength-- > 0)
removeObjectAtIndex(rangeLocation);
}
}
@Override
public Object clone() {
return new NSMutableArray<E>(this);
}
@Override
public NSArray<E> immutableClone() {
return new NSArray<E>(this);
}
@Override
public NSMutableArray<E> mutableClone() {
return (NSMutableArray<E>) clone();
}
public void _moveObjectAtIndexToIndex(int sourceIndex, int destIndex) {
if (sourceIndex == destIndex)
return;
int count = count();
if (sourceIndex < 0 || sourceIndex >= count || destIndex < 0 || destIndex >= count)
throw new IllegalArgumentException("Either source(" + sourceIndex + ") or destination(" + destIndex + ") is illegal.");
Object objs[] = _objects();
Object temp = objs[sourceIndex];
int boundary;
int index;
int direction;
if (sourceIndex < destIndex) {
index = destIndex;
boundary = sourceIndex;
direction = 1;
} else {
index = sourceIndex;
boundary = destIndex;
direction = -1;
}
for (; index != boundary; index += direction)
objs[index] = objs[index + direction];
objs[destIndex] = temp;
_objectsCache = null;
}
// AK: Bugfixes and enhancements from here on
/**
* Clears out the object cache and tell us to recompute the hash.
*
*/
private void clearCache() {
_objectsCache = null;
_setMustRecomputeHash(true);
}
/**
* Clears the objectsNoCopy too. It's wrong not to clear it.
*
* @param object the replacement object
* @param index index of object to replace
* @return object that has been replaced
*/
public E replaceObjectAtIndex(E object, int index) {
if (object == null) {
throw new IllegalArgumentException("Attempt to insert null into an " + getClass().getName() + ".");
}
int count = count();
if (index >= 0 && index < count) {
Object[] objs = _objects();
Object result = objs[index];
objs[index] = object;
clearCache();
return (E) result;
}
throw new IllegalArgumentException("Index (" + index + ") out of bounds [0, " + (count - 1) + "]");
}
/**
* Much faster implementation of the remove method for larger arrays.
*
* @param otherObjects objects to remove
*/
public void removeObjects(Object... otherObjects) {
if (otherObjects != null) {
int count = count();
if(count > 0) {
int otherCount = otherObjects.length;
if(count * otherCount > 100) {
if(count > 0) {
NSMutableSet<Object> table = new NSMutableSet<>(otherCount);
for (int i = 0; i < otherCount; i++) {
Object o = otherObjects[i];
if(o != null) {
table.addObject(o);
}
}
int offset = 0;
Object[] objs = _objects();
for(int i = 0; i < count; i++) {
Object o = objs[i];
objs[i] = null;
if (!table.containsObject(o)) {
objs[offset++] = o;
}
}
_setCount(offset);
clearCache();
}
} else {
for (int i = 0; i < otherObjects.length; i++)
removeObject(otherObjects[i]);
}
}
}
}
/**
* Bugfix for the broken implementation in NSArray.
*/
@Override
public <T> T[] toArray(T[] array) {
int i = size();
if (array.length < i) {
array = (T[]) Array.newInstance(array.getClass().getComponentType(), i);
}
Object[] result = array;
for (int j = 0; j < i; j++) {
result[j] = objectAtIndex(j);
}
if (array.length > i) {
array[i] = null;
}
return array;
}
//AK: from here on only java.util.List stuff
@Override
public E set(int index, E element) {
E old = objectAtIndex(index);
if(element != old) {
replaceObjectAtIndex(element, index);
}
return old;
}
@Override
public void add(int index, E element) {
insertObjectAtIndex(element, index);
}
@Override
public boolean add(E element) {
addObject(element);
return true;
}
@Override
public boolean addAll(Collection<? extends E> collection) {
addObjects((E[]) collection.toArray());
return true;
}
@Override
public boolean addAll(int index, Collection<? extends E> collection) {
boolean modified = false;
if(collection == this) {
collection = ((NSMutableArray<? extends E>)collection).immutableClone();
}
Iterator<? extends E> e = collection.iterator();
while (e.hasNext()) {
add(index++, e.next());
modified = true;
}
return modified;
}
@Override
public E remove(int index) {
return removeObjectAtIndex(index);
}
@Override
public boolean remove(Object o) {
boolean modified = false;
int index = indexOf(o);
if (index != NotFound) {
removeObjectAtIndex(index);
modified = true;
}
return modified;
}
@Override
public void clear() {
removeAllObjects();
}
@Override
public boolean retainAll(Collection<?> c) {
boolean modified = false;
Iterator<?> e = iterator();
while (e.hasNext()) {
if (!c.contains(e.next())) {
e.remove();
modified = true;
}
}
return modified;
}
@Override
public boolean removeAll(Collection<?> collection) {
int count = count();
removeObjects(collection.toArray());
return count != count();
}
@Override
public Iterator<E> iterator() {
return new Itr();
}
@Override
public ListIterator<E> listIterator() {
return listIterator(0);
}
@Override
public ListIterator<E> listIterator(final int index) {
if (index < 0 || index > size())
throw new IndexOutOfBoundsException("Index: " + index);
return new ListItr(index);
}
private class Itr implements Iterator<E> {
int cursor = 0;
int lastRet = NotFound;
int expectedModCount = modCount;
protected Itr() { }
public boolean hasNext() {
return cursor != size();
}
public E next() {
try {
E next = get(cursor);
checkForComodification();
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if (lastRet == NotFound)
throw new IllegalStateException();
checkForComodification();
try {
NSMutableArray.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = NotFound;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
cursor = index;
}
public boolean hasPrevious() {
return cursor != 0;
}
public E previous() {
try {
int i = cursor - 1;
E previous = get(i);
checkForComodification();
lastRet = cursor = i;
return previous;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor - 1;
}
public void set(Object o) {
if (lastRet == NotFound)
throw new IllegalStateException();
checkForComodification();
try {
NSMutableArray.this.set(lastRet, (E)o);
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
public void add(Object o) {
checkForComodification();
try {
NSMutableArray.this.add(cursor++, (E)o);
lastRet = NotFound;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
}
@SuppressWarnings("cast")
@Override
public List<E> subList(int fromIndex, int toIndex) {
return (this instanceof RandomAccess ? new RandomAccessSubList<E>(this,
fromIndex, toIndex) : new SubList<E>(this, fromIndex, toIndex));
}
protected void removeRange(int fromIndex, int toIndex) {
ListIterator<?> it = listIterator(fromIndex);
for (int i = 0, n = toIndex - fromIndex; i < n; i++) {
it.next();
it.remove();
}
}
}
class SubList<E> extends NSMutableArray<E> {
/**
* Do I need to update serialVersionUID?
* See section 5.6 <cite>Type Changes Affecting Serialization</cite> on page 51 of the
* <a href="http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf">Java Object Serialization Spec</a>
*/
private static final long serialVersionUID = 1L;
NSMutableArray<E> l;
int offset;
int size;
int expectedModCount;
SubList(NSMutableArray<E> list, int fromIndex, int toIndex) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
if (toIndex > list.size())
throw new IndexOutOfBoundsException("toIndex = " + toIndex);
if (fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex(" + fromIndex
+ ") > toIndex(" + toIndex + ")");
l = list;
offset = fromIndex;
size = toIndex - fromIndex;
expectedModCount = l.modCount;
}
@Override
public E set(int index, E element) {
rangeCheck(index);
checkForComodification();
return l.set(index + offset, element);
}
@Override
public E get(int index) {
rangeCheck(index);
checkForComodification();
return l.get(index + offset);
}
@Override
public int size() {
checkForComodification();
return size;
}
@Override
public int count() {
return size();
}
@Override
public boolean add(E element) {
add(size(), element);
return true;
}
@Override
public void add(int index, E element) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException();
checkForComodification();
l.add(index + offset, element);
expectedModCount = l.modCount;
size++;
modCount++;
}
@Override
public boolean remove(Object o) {
Iterator<E> e = iterator();
while (e.hasNext()) {
if (o.equals(e.next())) {
e.remove();
return true;
}
}
return false;
}
@Override
public E remove(int index) {
rangeCheck(index);
checkForComodification();
E result = l.remove(index + offset);
expectedModCount = l.modCount;
size--;
modCount++;
return result;
}
@Override
protected void removeRange(int fromIndex, int toIndex) {
checkForComodification();
l.removeRange(fromIndex + offset, toIndex + offset);
expectedModCount = l.modCount;
size -= (toIndex - fromIndex);
modCount++;
}
@Override
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
@Override
public boolean addAll(int index, Collection<? extends E> c) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
+ size);
int cSize = c.size();
if (cSize == 0)
return false;
checkForComodification();
l.addAll(offset + index, c);
expectedModCount = l.modCount;
size += cSize;
modCount++;
return true;
}
@Override
public Iterator<E> iterator() {
return listIterator();
}
@Override
public ListIterator<E> listIterator(final int index) {
checkForComodification();
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
+ size);
return new ListIterator<E>() {
private ListIterator<E> i = l.listIterator(index + offset);
public boolean hasNext() {
return nextIndex() < size;
}
public E next() {
if (hasNext()) {
return i.next();
}
throw new NoSuchElementException();
}
public boolean hasPrevious() {
return previousIndex() >= 0;
}
public E previous() {
if (hasPrevious()) {
return i.previous();
}
throw new NoSuchElementException();
}
public int nextIndex() {
return i.nextIndex() - offset;
}
public int previousIndex() {
return i.previousIndex() - offset;
}
public void remove() {
i.remove();
expectedModCount = l.modCount;
size--;
modCount++;
}
public void set(E o) {
i.set(o);
}
public void add(E o) {
i.add(o);
expectedModCount = l.modCount;
size++;
modCount++;
}
};
}
@Override
public List<E> subList(int fromIndex, int toIndex) {
return new SubList<E>(this, fromIndex, toIndex);
}
@Override
public Object[] toArray() {
int count = count();
Object[] objects = new Object[count];
if (count > 0) {
System.arraycopy(l.objectsNoCopy(), offset, objects, 0, count);
}
return objects;
}
private void rangeCheck(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index: " + index + ",Size: "
+ size);
}
private void checkForComodification() {
if (l.modCount != expectedModCount)
throw new ConcurrentModificationException();
}
@Override
public String toString() {
int count = count();
if (count == 0) {
return "()";
}
StringBuilder sb = new StringBuilder(128);
sb.append('(');
for (int i = 0; i < count; i++) {
Object object = l.get(i + offset);
if (i > 0) {
sb.append(", ");
}
if (object instanceof String) {
sb.append('"');
sb.append(object);
sb.append('"');
} else {
sb.append(object == this ? "THIS" : object);
}
}
sb.append(')');
return sb.toString();
}
}
class RandomAccessSubList<E> extends SubList<E> implements RandomAccess {
/**
* Do I need to update serialVersionUID?
* See section 5.6 <cite>Type Changes Affecting Serialization</cite> on page 51 of the
* <a href="http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf">Java Object Serialization Spec</a>
*/
private static final long serialVersionUID = 1L;
RandomAccessSubList(NSMutableArray<E> list, int fromIndex, int toIndex) {
super(list, fromIndex, toIndex);
}
@Override
public List<E> subList(int fromIndex, int toIndex) {
return new RandomAccessSubList<E>(this, fromIndex, toIndex);
}
}