001 // Copyright 2006-2008 Regents of the University of California. May be used
002 // under the terms of the revised BSD license. See LICENSING for details.
003 /**
004 * @author Adrian Mettler
005 */
006 package org.joe_e.array;
007
008 import java.io.IOException;
009 import java.io.ObjectInputStream;
010 import java.io.ObjectOutputStream;
011 import java.io.Serializable;
012 import java.lang.reflect.Array;
013
014 import org.joe_e.JoeE;
015 import org.joe_e.Selfless;
016
017 /**
018 * A read-only array containing elements of an arbitrary type.
019 * <p>
020 * Note: this class implements Serializable in order to avoid preventing
021 * trusted (non-Joe-E) code from serializing it. The Java Serialization API
022 * is tamed away as unsafe, and thus is not available to Joe-E code.
023 *
024 * @param <E> the element type of objects contained in the array
025 */
026 public class ConstArray<E> implements Selfless, Iterable<E>, Serializable {
027 static private final long serialVersionUID = 1L;
028
029 // Marked transient to hide from serialization; see writeObject()
030 // This field should act as if final.
031 // This array should only contain objects of type E provided that clients
032 // avoid generics typesafety violations.
033 transient /* final */ Object[] arr;
034
035
036 /**
037 * Package-scope back-door constructor for use by subclasses that override
038 * all methods that make use of the field arr. Nullity of arr is used to
039 * distinguish between instances with which this class must interact by
040 * using the public interface rather than through their arr field.
041 */
042 ConstArray(final Object[] arr) {
043 this.arr = arr;
044 }
045
046 /**
047 * Construct a <code>ConstArray</code>.
048 * @param values each value
049 */
050 static public <T> ConstArray<T> array(final T... values) {
051 return new ConstArray<T>(values.clone());
052 }
053
054
055 /*
056 * The following methods exist as a workaround to avoid warnings being
057 * issued for creating ConstArrays with parameterized types as their
058 * component type (e.g. ConstArray<ConstArray<String>>).
059 * While it is possible for Java to infer that an individual argument is
060 * of such a type, it does not infer arrays of them due to the
061 * unsoundness introduced via covariance. These methods also avoid an
062 * extra array copy varargs require since they take a mutable array.
063 */
064
065 /**
066 * Construct an empty <code>ConstArray</code>.
067 */
068 static public <T> ConstArray<T> array() {
069 return new ConstArray<T>(new Object[]{});
070 }
071
072 /**
073 * Construct a <code>ConstArray</code> with one element.
074 * @param value the value
075 */
076 static public <T> ConstArray<T> array(final T value) {
077 return new ConstArray<T>(new Object[]{value});
078 }
079
080 /**
081 * Construct a <code>ConstArray</code> with two elements.
082 * @param value1 the first value
083 * @param value2 the second value
084 */
085 static public <T> ConstArray<T> array(final T value1, final T value2) {
086 return new ConstArray<T>(new Object[]{value1, value2});
087 }
088
089 /**
090 * Construct a <code>ConstArray</code> with three elements.
091 * @param value1 the first value
092 * @param value2 the second value
093 * @param value3 the third value
094 */
095 static public <T> ConstArray<T> array(final T value1, final T value2,
096 final T value3) {
097 return new ConstArray<T>(new Object[]{value1, value2, value3});
098 }
099
100 /**
101 * Construct a <code>ConstArray</code> with four elements.
102 * @param value1 the first value
103 * @param value2 the second value
104 * @param value3 the third value
105 * @param value4 the fourth value
106 */
107 static public <T> ConstArray<T> array(final T value1, final T value2,
108 final T value3, final T value4) {
109 return new ConstArray<T>(new Object[]{value1, value2, value3, value4});
110 }
111
112 // java.io.Serializable interface
113
114 /*
115 * Serialization hacks to prevent the contents from being serialized as
116 * a mutable array. This improves efficiency for projects that serialize
117 * Joe-E objects using Java's serialization API to avoid treating
118 * immutable state as mutable. They can otherwise be ignored.
119 */
120 private void writeObject(final ObjectOutputStream out) throws IOException {
121 out.defaultWriteObject();
122
123 if (null == arr) {
124 out.writeObject(null);
125 } else {
126 out.writeObject(arr.getClass().getComponentType());
127 out.writeInt(arr.length);
128 for (final Object element : arr) {
129 out.writeObject(element);
130 }
131 }
132 }
133
134 private void readObject(final ObjectInputStream in) throws IOException,
135 ClassNotFoundException {
136 in.defaultReadObject();
137
138 final Class<?> e = (Class<?>)in.readObject();
139 if (null == e) {
140 arr = null;
141 } else {
142 final int length = in.readInt();
143 final Object[] array = (Object[])Array.newInstance(e, length);
144 for (int i = 0; i != length; ++i) {
145 array[i] = in.readObject();
146 }
147 this.arr = array;
148 }
149 }
150
151 // java.lang.Object interface
152
153 /**
154 * Test for equality with another object.
155 *
156 * @return true if the other object is a ConstArray with the same contents
157 * as this array (determined by calling equals() on array elements)
158 */
159 public boolean equals(final Object other) {
160 // Can't be equal if not a ConstArray
161 if (!(other instanceof ConstArray<?>)) {
162 return false;
163 }
164 ConstArray<?> otherArray = (ConstArray<?>) other;
165 // check that length matches
166 if (arr.length != otherArray.length()) {
167 return false;
168 }
169
170 // Compare elements, either both null or equals()
171 for (int i = 0; i < arr.length; ++i) {
172 if (arr[i] == null && otherArray.get(i) != null
173 || arr[i] != null && !arr[i].equals(otherArray.get(i))) {
174 return false;
175 }
176 }
177 return true;
178 }
179 /**
180 * Computes a digest of the array for hashing. The hash code is the same
181 * as called on a Java array
182 * containing the same elements.
183 * @return a hash code based on the contents of this array
184 */
185
186 /**
187 * Computes a digest of the array for hashing. If all of the elements of
188 * the array implement Selfless in the overlay type system, the hash will
189 * be the same as that computed by
190 * {@link java.util.Arrays#hashCode(Object[])} for a Java array with
191 * the same elements. The precise behavior when some elements are not
192 * Selfless is unspecified, and may change in future releases. It is,
193 * however, guaranteed to be deterministic for a given library version.
194 *
195 * @return a hash code based on the contents of this array
196 */
197 public int hashCode() {
198 int hashCode = 1;
199 for (final Object i : arr) {
200 hashCode *= 31;
201 // treat non-Selfless as nulls
202 if (JoeE.instanceOf(i, Selfless.class)) {
203 hashCode += i.hashCode();
204 }
205 }
206
207 return hashCode;
208 }
209
210 /**
211 * Return a string representation of the array. Equivalent to the result
212 * of Arrays.toString() except that some elements may be replaced with the
213 * string "<unprintable>". The set of elements for which this is
214 * the case is unspecified, and may change in future releases.
215 *
216 * @return a string representation of this array
217 */
218 /*
219 * TODO: Change this to support more types, either through
220 * the introduction of an interface for toString()able objects
221 * or through reflection.
222 */
223 public String toString() {
224 StringBuilder text = new StringBuilder("[");
225 boolean first = true;
226 for (Object element : arr) {
227 if (first) {
228 first = false;
229 } else {
230 text.append(", ");
231 }
232
233 if (element == null) {
234 text.append("null");
235 } else if (element instanceof String
236 || element instanceof ConstArray<?>
237 || element instanceof Boolean || element instanceof Byte
238 || element instanceof Character || element instanceof Double
239 || element instanceof Float || element instanceof Integer
240 || element instanceof Long || element instanceof Short) {
241 text.append(element.toString());
242 } else {
243 text.append("<unprintable>");
244 }
245 }
246
247 return text.append("]").toString();
248 }
249
250 // java.lang.Iterable interface
251
252 /**
253 * Return a new iterator over the array
254 */
255 public ArrayIterator<E> iterator() {
256 return new ArrayIterator<E>(this);
257 }
258
259 // org.joe_e.ConstArray interface
260
261 /**
262 * Gets the element at a specified position.
263 * @param i position of the element to return
264 * @throws ArrayIndexOutOfBoundsException <code>i</code> is out of bounds
265 */
266 @SuppressWarnings("unchecked")
267 public E get(int i) {
268 return (E) arr[i];
269 }
270
271 /**
272 * Return the length of the array
273 */
274 public int length() {
275 return arr.length;
276 }
277
278 /**
279 * Return a mutable copy of the array
280 * @param prototype prototype of the array to copy into
281 * @return an array containing the contents of this <code>ConstArray</code>
282 * of the same type as <code>prototype</code>
283 * @throws ArrayStoreException if an element cannot be stored in the array
284 */
285 @SuppressWarnings("unchecked")
286 public <T> T[] toArray(T[] prototype) {
287 final int len = length();
288 if (prototype.length < len) {
289 final Class<?> t = prototype.getClass().getComponentType();
290 prototype = (T[])Array.newInstance(t, len);
291 }
292
293 System.arraycopy(arr, 0, prototype, 0, len);
294 return prototype;
295 }
296
297 /**
298 * Return a new <code>ConstArray</code> that contains the same elements
299 * as this one but with a new element added to the end
300 * @param newE the element to add
301 * @return the new array
302 */
303 public ConstArray<E> with(E newE) {
304 // We use a new Object array here, because we don't know the static type
305 // of E that was used; it may not match the dynamic component type of
306 // arr due to array covariance.
307 final Object[] newArr = new Object[arr.length + 1];
308 System.arraycopy(arr, 0, newArr, 0, arr.length);
309 newArr[arr.length] = newE;
310 return new ConstArray<E>(newArr);
311 }
312
313 /**
314 * Return a new <code>ConstArray</code> that contains the same elements
315 * as this one excluding the element at a specified index
316 * @param i the index of the element to exclude
317 * @return the new array
318 */
319 public ConstArray<E> without(final int i) {
320 final Object[] newArr = new Object[arr.length - 1];
321 System.arraycopy(arr, 0, newArr, 0, i);
322 System.arraycopy(arr, i + 1, newArr, i, newArr.length - i);
323 return new ConstArray<E>(newArr);
324 }
325
326 /**
327 * A {@link ConstArray} factory.
328 */
329 public static class Builder<E> implements ArrayBuilder<E> {
330 Object[] buffer;
331 int size;
332
333 /**
334 * Construct an instance with the default internal array length.
335 */
336 Builder() {
337 this(0);
338 }
339
340 /**
341 * Construct an instance.
342 * @param estimate estimated array length
343 */
344 Builder(final int estimate) {
345 buffer = new Object[estimate > 0 ? estimate : 32];
346 size = 0;
347 }
348
349 /**
350 * Appends an element to the Array
351 * @param newE the element to append
352 * @throws NegativeArraySizeException if the resulting internal array
353 * would exceed the maximum length of a Java array. The builder is
354 * unmodified.
355 */
356 public void append(E newE) {
357 appendInternal(newE);
358 }
359
360 final void appendInternal(E newE) {
361 if (size == buffer.length) {
362 System.arraycopy(buffer, 0, buffer = new Object[2 * size], 0,
363 size);
364 }
365 buffer[size++] = newE;
366 }
367
368 /**
369 * Appends all elements from a Java array to the Array
370 * @param newEs the element to append
371 * @throws IndexOutOfBoundsException if the resulting internal array
372 * would exceed the maximum length of a Java array. The builder is
373 * unmodified.
374 */
375 public void append(E[] newEs) {
376 appendInternal(newEs, 0, newEs.length);
377 }
378
379 /**
380 * Appends a range of elements from a Java array to the Array
381 * @param newEs the source array
382 * @param off the index of the first element to append
383 * @param len the number of elements to append
384 * @throws IndexOutOfBoundsException if an out-of-bounds index would
385 * be referenced or the resulting internal array would exceed the
386 * maximum length of a Java array. The builder is unmodified.
387 */
388 public void append(E[] newEs, int off, int len) {
389 appendInternal(newEs, off, len);
390 }
391
392 final void appendInternal(E[] newEs, int off, int len) {
393 int newSize = size + len;
394 if (newSize < 0 || off < 0 || len < 0 || off + len < 0
395 || off + len > newEs.length) {
396 throw new IndexOutOfBoundsException();
397 }
398 if (newSize > buffer.length) {
399 int newLength = Math.max(newSize, 2 * buffer.length);
400 System.arraycopy(buffer, 0, buffer = new Object[newLength], 0,
401 size);
402 }
403 System.arraycopy(newEs, off, buffer, size, len);
404 size = newSize;
405 }
406
407 /**
408 * Gets the current number of elements in the Array
409 * @return the number of elements that have been appended
410 */
411 public int length() {
412 return size;
413 }
414
415 /**
416 * Create a snapshot of the current content.
417 * @return a <code>ConstArray<E></code> containing the elements so far
418 */
419 public ConstArray<E> snapshot() {
420 final Object[] arr;
421 if (size == buffer.length) {
422 arr = buffer;
423 } else {
424 arr = new Object[size];
425 System.arraycopy(buffer, 0, arr, 0, size);
426 }
427 return new ConstArray<E>(arr);
428 }
429 }
430
431 /**
432 * Get a <code>ConstArray.Builder</code>. This is equivalent to the
433 * constructor.
434 * @return a new builder instance, with the default internal array length
435 */
436 public static <E> Builder<E> builder() {
437 return new Builder<E>(0);
438 }
439
440 /**
441 * Get a <code>ConstArray.Builder</code>. This is equivalent to the
442 * constructor.
443 * @param estimate estimated array length
444 * @return a new builder instance
445 */
446 public static <E> Builder<E> builder(final int estimate) {
447 return new Builder<E>(estimate);
448 }
449 }