org.python.core.AbstractArray Maven / Gradle / Ivy
package org.python.core;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.Arrays;
/**
* Abstract class that manages bulk structural and data operations
* on arrays, defering type-specific element-wise operations to the
* subclass. Subclasses supply the underlying array and the
* type-specific operations--greatly reducing the need for casting
* (thus achieving array-like performances with collection-like
* flexibility). Also includes
* functionality to support integration with the the jdk's
* collections (via methods that return a modification increment).
* Subclasses will want to provide the following methods (which are
* not declared in this class since subclasses should specify the
* explicit return type):
*
* <type> get(int)
* void set(int, <type>)
* void add(<type>)
* void add(int, <type>)
* <type>[] toArray()
*
* Clone cannot be supported since the array is not held locally.
* But the @link #AbstractArray(AbstractArray) constructor can be used
* for suclasses that need to support clone.
*
* This "type-specific collections" approach was originally developed
* by Dennis Sosnoski, who provides a more complete library at the
* referenced URL. Sosnoski's library does not integrate with the
* jdk collection classes but provides collection-like classes.
*
* @author Clark Updike
* @see
* Sosnoski's Type-Specific Collection Library
*/
public abstract class AbstractArray implements Serializable{
/**
* Size of the current array, which can be larger than the
* size
field.
*/
protected int capacity;
/**
* The number of values currently present in the array.
*/
protected int size;
/**
* The modification count increment indicates if a structural change
* occured as a result of an operation that would make concurrent iteration
* over the array invalid. It is typically used by subclasses that
* extend AbstractList
, by adding the value to
* AbstractList.modCount
after performing a potentially
* structure-altering operation. A value of 0 indicates that
* it is still valid to iterate over the array. A value of 1
* indicates it is no longer valid to iterate over the range.
* This class uses a somewhat stricter semantic for modCount
.
* Namely, modCountIncr
is only set to 1 if a structural
* change occurred. The jdk collections generally increment
* modCount
if a potentially structure-altering method
* is called, regardless of whether or not a change actually occurred.
*
* See also: java.util.AbstractList#modCount
*/
protected int modCountIncr;
/**
* Since AbstractArray can support a clone method, this facilitates
* sublcasses that want to implement clone (poor man's cloning).
* Sublclasses can then do this:
*
* public MyManagedArray(MyManagedArray toCopy) {
* super(this);
* this.baseArray = ()toCopy.copyArray();
* this.someProp = toCopy.someProp;
*
* }
*
* public Object clone() {
* return new MyManagedArray(this);
* }
*
*
* @param toCopy
*/
public AbstractArray(AbstractArray toCopy) {
this.capacity = toCopy.capacity;
// let modCountIncr default to 0
this.size = toCopy.size;
}
/**
* Use when the subclass has a preexisting array.
*
* @param size the initial size of the array
*/
public AbstractArray(int size) {
this.size = size;
this.capacity = size;
}
/**
* Creates the managed array with a default size of 10.
*
* @param type array element type (primitive type or object class)
*/
public AbstractArray(Class type) {
this(type, 10);
}
/**
* Construtor for multi-dimensional array types.
* For example, char[][]
. This class only manages the
* top level dimension of the array. For single dimension
* arrays (the more typical usage), use the other constructors.
*
* @param type Array element type (primitive type or object class).
* @param dimensions An int array specifying the dimensions. For
* a 2D array, something like new int[] {10,0}
to
* create 10 elements each of which can hold an reference to an
* array of the same type.
* @see Array#newInstance(java.lang.Class, int[])
*/
public AbstractArray(Class type, int[] dimensions) {
Object array = Array.newInstance(type, dimensions);
this.capacity = dimensions[0];
setArray(array);
}
/**
* Creates the managed array with the specified size.
*
* @param type array element type (primitive type or object class)
* @param size number of elements initially allowed in array
*/
public AbstractArray(Class type, int size) {
Object array = Array.newInstance(type, size);
this.capacity = Math.max(size, 10);
setArray(array);
}
/**
* Appends the supplied array, which must be an array of the same
* type as this
, to the end of this
.
*
AbstractList
subclasses should update their
* modCount
after calling this method.
*
* @param ofArrayType the array to append
*/
public void appendArray(Object ofArrayType) {
replaceSubArray(ofArrayType, this.size);
}
/**
* Set the array to the empty state, clearing all the data out and
* nulling objects (or "zero-ing" primitives).
*
Note: This method does not set modCountIncr
to
* 1
even though java.util.ArrayList
* would.
*
* AbstractList
subclasses should update their
* modCount
after calling this method.
*/
public void clear() {
this.modCountIncr = 0;
if (this.size != 0) {
this.modCountIncr = 1;
clearRange(0, this.size);
setSize(0);
}
}
/**
* Clears out the values in the specified range. For object arrays,
* the cleared range is nullified. For primitve arrays, it is
* "zero-ed" out.
*
Note: This method does not set modCountIncr
to
* 1
even though java.util.ArrayList
* would.
*
* @param start the start index, inclusive
* @param stop the stop index, exclusive
*/
protected void clearRange(int start, int stop) {
if (start < stop && start >= 0 && stop <= this.size) {
clearRangeInternal(start, stop);
} else {
if (start == stop && start >= 0 && stop <= this.size) {
return;
}
throw new ArrayIndexOutOfBoundsException("start and stop must follow: 0 <= start <= stop <= " +
(this.size) + ", but found start= " + start + " and stop=" + stop);
}
}
/**
* Used internally, no bounds checking.
*
* @param start the start index, inclusive
* @param stop the stop index, exclusive
*/
private void clearRangeInternal(int start, int stop) {
Object base = getArray();
Class arrayType = base.getClass().getComponentType();
if (arrayType.isPrimitive()) {
if (arrayType == Boolean.TYPE) {
Arrays.fill((boolean[]) base, start, stop, false);
} else if (arrayType == Character.TYPE) {
Arrays.fill((char[]) base, start, stop, '\u0000');
} else if (arrayType == Byte.TYPE) {
Arrays.fill((byte[]) base, start, stop, (byte) 0);
} else if (arrayType == Short.TYPE) {
Arrays.fill((short[]) base, start, stop, (short) 0);
} else if (arrayType == Integer.TYPE) {
Arrays.fill((int[]) base, start, stop, 0);
} else if (arrayType == Long.TYPE) {
Arrays.fill((long[]) base, start, stop, 0);
} else if (arrayType == Float.TYPE) {
Arrays.fill((float[]) base, start, stop, 0.f);
} else if (arrayType == Double.TYPE) {
Arrays.fill((double[]) base, start, stop, 0.);
}
} else {
Arrays.fill((Object[]) base, start, stop, null);
}
}
/**
* Constructs and returns a simple array containing the same data as held
* in this growable array.
*
* @return array containing a shallow copy of the data.
*/
public Object copyArray() {
Object copy = Array.newInstance(getArray().getClass().getComponentType(), this.size);
System.arraycopy(getArray(), 0, copy, 0, this.size);
return copy;
}
/**
* Ensures that the base array has at least the specified
* minimum capacity.
*
AbstractList
subclasses should update their
* modCount
after calling this method.
*
* @param minCapacity new minimum size required
*/
protected void ensureCapacity(int minCapacity) {
// ArrayList always increments the mod count, even if no
// structural change is made (not sure why).
// This only indicates a mod count change if a change is made.
this.modCountIncr = 0;
if (minCapacity > this.capacity) {
this.modCountIncr = 1;
int newCapacity = (this.capacity * 2) + 1;
newCapacity = (newCapacity < minCapacity)
? minCapacity
: newCapacity;
setNewBase(newCapacity);
this.capacity = newCapacity;
}
}
/**
* Gets the next add position for appending a value to those in the array.
* If the underlying array is full, it is grown by the appropriate size
* increment so that the index value returned is always valid for the
* array in use by the time of the return.
*
AbstractList
subclasses should update their
* modCount
after calling this method.
*
* @return index position for next added element
*/
protected int getAddIndex() {
int index = this.size++;
if (this.size > this.capacity) {
ensureCapacity(this.size);
}
return index;
}
/**
* Get the backing array. This method is used by the type-agnostic base
* class code to access the array used for type-specific storage by the
* child class.
*
* @return backing array object
*/
protected abstract Object getArray();
protected boolean isEmpty() {
return this.size == 0;
}
/**
* Makes room to insert a value at a specified index in the array.
*
AbstractList
subclasses should update their
* modCount
after calling this method. Does not change
* the size
property of the array.
*
* @param index index position at which to insert element
*/
protected void makeInsertSpace(int index) {
makeInsertSpace(index, 1);
}
protected void makeInsertSpace(int index, int length) {
this.modCountIncr = 0;
if (index >= 0 && index <= this.size) {
int toCopy = this.size - index;
this.size = this.size + length;
// First increase array size if needed
if (this.size > this.capacity) {
ensureCapacity(this.size);
}
if (index < this.size - 1) {
this.modCountIncr = 1;
Object array = getArray();
System.arraycopy(array, index, array, index + length, toCopy);
}
} else {
throw new ArrayIndexOutOfBoundsException("Index must be between 0 and " +
this.size + ", but was " + index);
}
}
/**
* Remove a value from the array. All values above the index removed
* are moved down one index position.
*
AbstractList
subclasses should always increment
* their modCount
method after calling this, as
* remove
always causes a structural modification.
*
* @param index index number of value to be removed
*/
public void remove(int index) {
if (index >= 0 && index < this.size) {
this.size = this.size - 1;
if (index < this.size) {
Object base = getArray();
System.arraycopy(base, index + 1, base, index, this.size - index);
clearRangeInternal(this.size, this.size);
}
} else {
if (this.size == 0) {
throw new IllegalStateException("Cannot remove data from an empty array");
}
throw new IndexOutOfBoundsException("Index must be between 0 and " +
(this.size - 1) + ", but was " + index);
}
}
/**
* Removes a range from the array at the specified indices.
* @param start inclusive
* @param stop exclusive
*/
public void remove(int start, int stop) {
if (start >= 0 && stop <= this.size && start <= stop) {
Object base = getArray();
int nRemove = stop - start;
if (nRemove == 0) {
return;
}
System.arraycopy(base, stop, base, start, this.size - stop);
this.size = this.size - nRemove;
clearRangeInternal(this.size, this.size + nRemove - 1);
setArray(base);
return;
}
throw new IndexOutOfBoundsException("start and stop must follow: 0 <= start <= stop <= " +
(this.size - 1) + ", but found start= " + start + " and stop=" + stop);
}
/**
* Allows an array type to overwrite a segment of the array.
* Will expand the array if (atIndex + 1) + ofArrayType
's length
* is greater than the current length.
*
AbstractList
subclasses should update their
* modCount
after calling this method.
*
* @param array
* @param atIndex
*/
public void replaceSubArray(Object array, int atIndex) {
int arrayLen = Array.getLength(array);
replaceSubArray(atIndex, Math.min(this.size, atIndex + arrayLen), array, 0, arrayLen);
}
/**
* Replace a range of this array with another subarray.
* @param thisStart the start index (inclusive) of the subarray in this
* array to be replaced
* @param thisStop the stop index (exclusive) of the subarray in this
* array to be replaced
* @param srcArray the source array from which to copy
* @param srcStart the start index (inclusive) of the replacement subarray
* @param srcStop the stop index (exclusive) of the replacement subarray
*/
public void replaceSubArray(int thisStart, int thisStop, Object srcArray,
int srcStart, int srcStop) {
this.modCountIncr = 0;
if (!srcArray.getClass().isArray()) {
throw new IllegalArgumentException("'array' must be an array type");
}
int replacedLen = thisStop - thisStart;
if (thisStart < 0 || replacedLen < 0 || thisStop > this.size) {
String message = null;
if (thisStart < 0) {
message = "thisStart < 0 (thisStart = " + thisStart + ")";
} else if (replacedLen < 0) {
message = "thisStart > thistStop (thisStart = " + thisStart +
", thisStop = " + thisStop + ")";
} else if (thisStop > this.size) {
message = "thisStop > size (thisStop = " + thisStop +
", size = " + this.size + ")";
} else {
throw new InternalError("Incorrect validation logic");
}
throw new ArrayIndexOutOfBoundsException(message);
}
int srcLen = Array.getLength(srcArray);
int replacementLen = srcStop - srcStart;
if (srcStart < 0 || replacementLen < 0 || srcStop > srcLen) {
String message = null;
if (srcStart < 0) {
message = "srcStart < 0 (srcStart = " + srcStart +")";
} else if (replacementLen < 0) {
message = "srcStart > srcStop (srcStart = " + srcStart +
", srcStop = " + srcStop + ")";
} else if (srcStop > srcLen) {
message = "srcStop > srcArray length (srcStop = " + srcStop +
", srcArray length = " +srcLen + ")";
} else {
throw new InternalError("Incorrect validation logic");
}
throw new IllegalArgumentException("start, stop and array must follow:\n\t"
+ "0 <= start <= stop <= array length\nBut found\n\t" +
message);
}
int lengthChange = replacementLen - replacedLen;
// Adjust array size if needed.
if (lengthChange < 0) {
remove(thisStop + lengthChange, thisStop);
} else if (lengthChange > 0) {
makeInsertSpace(thisStop, lengthChange);
}
try {
this.modCountIncr = 1;
System.arraycopy(srcArray, srcStart, getArray(), thisStart, replacementLen);
} catch (ArrayStoreException e) {
throw new IllegalArgumentException("'ofArrayType' must be compatible with existing array type of " +
getArray().getClass().getName() + "\tsee java.lang.Class.getName().");
}
}
/**
* Set the backing array. This method is used by the type-agnostic base
* class code to set the array used for type-specific storage by the
* child class.
*
* @param array the backing array object
*/
protected abstract void setArray(Object array);
/**
* Replaces the existing base array in the subclass with a new
* base array resized to the specified capacity.
*
* @param newCapacity
*/
private void setNewBase(int newCapacity) {
this.modCountIncr = 1;
Object base = getArray();
Class baseType = base.getClass().getComponentType();
Object newBase = Array.newInstance(baseType, newCapacity);
System.arraycopy(base, 0, newBase, 0, this.capacity);
setArray(newBase);
}
/**
* Sets the number of values currently present in the array. If the new
* size is greater than the current size, the added values are initialized
* to the default values. If the new size is less than the current size,
* all values dropped from the array are discarded.
*
AbstractList
subclasses should update their
* modCount
after calling this method.
*
* @param count number of values to be set
*/
public void setSize(int count) {
if (count > this.capacity) {
ensureCapacity(count);
} else if (count < this.size) {
clearRange(count, this.size);
}
this.size = count;
}
/**
* Get the number of values currently present in the array.
*
* @return count of values present
*/
public int getSize() {
return this.size;
}
/**
* Provides a default comma-delimited representation of array.
*
* @see java.lang.Object#toString()
*/
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("[");
Object base = getArray();
Class arrayType = base.getClass().getComponentType();
int n = this.size - 1;
if (arrayType.isPrimitive()) {
for (int i = 0; i < n; i++) {
buf.append(Array.get(base, i)).append(", ");
}
if (n >= 0) buf.append(Array.get(base, n));
} else {
Object[] objects = (Object[]) base;
for (int i = 0; i < n; i++) {
buf.append(objects[i]).append(", ");
}
if (n >= 0) {
buf.append(objects[n]);
}
}
buf.append("]");
return buf.toString();
}
/**
* Removes any excess capacity in the backing array so it is
* just big enough to hold the amount of data actually in the array.
*/
protected void trimToSize() {
// Don't need to adjust modCountIncr since AbstractList subclasses
// should only ever see up to the size (and not the capacity--which
// is encapsulated).
if (this.size < this.capacity) {
setNewBase(this.size);
}
}
/**
* Returns the modification count increment, which is used by
* AbstractList
subclasses to adjust modCount
* AbstractList
uses it's modCount
field
* to invalidate concurrent operations (like iteration) that should
* fail if the underlying array changes structurally during the
* operation.
*
* @return the modification count increment (0 if no change, 1 if changed)
*/
public int getModCountIncr() {
return this.modCountIncr;
}
}