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 }