001 // Copyright 2006-08 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.util.Arrays; 012 import java.lang.reflect.Array; 013 014 /** 015 * An immutable array of <code>char</code>. 016 */ 017 public final class CharArray extends PowerlessArray<Character> { 018 static private final long serialVersionUID = 1L; 019 020 private /* final */ transient char[] chars; 021 022 CharArray(char... chars) { 023 // Use back door constructor that sets backing store to null. 024 // This lets ConstArray's methods know not to use the backing 025 // store for accessing this object. 026 super(null); 027 this.chars = chars; 028 } 029 030 /** 031 * Constructs an array of <code>char</code>s. 032 * @param chars each element 033 */ 034 static public CharArray array(final char... chars) { 035 return new CharArray(chars.clone()); 036 } 037 038 /* 039 * The following are necessary because otherwise calls with <=4 arguments 040 * are resolved to the superclass PowerlessArray 041 */ 042 043 /** 044 * Construct an empty <code>CharArray</code> 045 */ 046 @SuppressWarnings("unchecked") // the warning here seems completely bogus 047 static public CharArray array() { 048 return new CharArray(new char[]{}); 049 } 050 051 /** 052 * Construct a <code>CharArray</code> with one element. 053 * @param value the value 054 */ 055 static public CharArray array(char value) { 056 return new CharArray(new char[]{value}); 057 } 058 059 /** 060 * Construct a <code>CharArray</code> with two elements. 061 * @param value1 the first value 062 * @param value2 the second value 063 */ 064 static public CharArray array(char value1, char value2) { 065 return new CharArray(new char[]{value1, value2}); 066 } 067 068 /** 069 * Construct a <code>CharArray</code> with three elements. 070 * @param value1 the first value 071 * @param value2 the second value 072 * @param value3 the third value 073 */ 074 static public CharArray array(char value1, char value2, char value3) { 075 return new CharArray(new char[]{value1, value2, value3}); 076 } 077 078 /** 079 * Construct a <code>CharArray</code> with four elements. 080 * @param value1 the first value 081 * @param value2 the second value 082 * @param value3 the third value 083 * @param value4 the fourth value 084 */ 085 static public CharArray array(char value1, char value2, char value3, 086 char value4) { 087 return new CharArray(new char[]{value1, value2, value3, value4}); 088 } 089 090 // java.io.Serializable interface 091 092 /* 093 * Serialization hacks to prevent the contents from being serialized as a 094 * mutable array. This improves efficiency for projects that serialize 095 * Joe-E objects using Java's serialization API by avoiding treatment of 096 * immutable state as mutable. These methods can otherwise be ignored. 097 */ 098 private void writeObject(final ObjectOutputStream out) throws IOException { 099 out.defaultWriteObject(); 100 101 out.writeInt(chars.length); 102 for (char c : chars) { 103 out.writeChar(c); 104 } 105 } 106 107 private void readObject(final ObjectInputStream in) throws 108 IOException, ClassNotFoundException { 109 in.defaultReadObject(); 110 111 final int length = in.readInt(); 112 chars = new char[length]; 113 for (int i = 0; i < length; ++i) { 114 chars[i] = in.readChar(); 115 } 116 } 117 118 /* 119 * Methods that must be overriden, as the implementation in ConstArray 120 * would try to use arr, which is null. 121 */ 122 123 // java.lang.Object interface 124 125 /** 126 * Test for equality with another object 127 * @return true if the other object is a {@link ConstArray} with the same 128 * contents as this array 129 */ 130 public boolean equals(final Object other) { 131 if (other instanceof CharArray) { 132 // Simple case: just compare charArr fields 133 return Arrays.equals(chars, ((CharArray)other).chars); 134 } else if (other instanceof ConstArray<?>) { 135 // Other array does not have contents in charArr: 136 // check that length matches, and then compare elements one-by-one 137 final ConstArray<?> otherArray = (ConstArray<?>)other; 138 if (otherArray.length() != chars.length) { 139 return false; 140 } 141 for (int i = 0; i < chars.length; ++i) { 142 final Object otherElement = otherArray.get(i); 143 if (!(otherElement instanceof Character) || 144 ((Character)otherElement).charValue() != chars[i]) { 145 return false; 146 } 147 } 148 return true; 149 } else { 150 // Only a ConstArray can be equal to a CharArray 151 return false; 152 } 153 } 154 155 /** 156 * Computes a digest of the array for hashing. The hash code is the same 157 * as {@link java.util.Arrays#hashCode(Object[])} called on a Java array 158 * containing the same elements. 159 * @return a hash code based on the contents of this array 160 */ 161 public int hashCode() { 162 // Because wrappers for primitive types return the same hashCode as 163 // their primitive values, a CharArray has the same hashCode as a 164 // ConstArray<Character> with the same contents. 165 return Arrays.hashCode(chars); 166 } 167 168 /** 169 * Return a string representation of the array 170 */ 171 public String toString() { 172 return Arrays.toString(chars); 173 } 174 175 // org.joe_e.ConstArray interface 176 177 /** 178 * Gets the length of the array. 179 */ 180 public int length() { 181 return chars.length; 182 } 183 184 /** 185 * Creates a <code>Character</code> for a specified <code>char</code>. 186 * @param i position of the element to return 187 * @throws ArrayIndexOutOfBoundsException <code>i</code> is out of bounds 188 */ 189 public Character get(int i) { 190 return chars[i]; 191 } 192 193 /** 194 * Return a mutable copy of the array 195 * @param prototype prototype of the array to copy into 196 * @return an array containing the contents of this <code>ConstArray</code> 197 * of the same type as <code>prototype</code> 198 * @throws ArrayStoreException if an element cannot be stored in the array 199 */ 200 @SuppressWarnings("unchecked") 201 public <T> T[] toArray(T[] prototype) { 202 final int len = length(); 203 if (prototype.length < len) { 204 final Class<?> t = prototype.getClass().getComponentType(); 205 prototype = (T[])Array.newInstance(t, len); 206 } 207 208 for (int i = 0; i < len; ++i) { 209 prototype[i] = (T) (Character) chars[i]; 210 } 211 return prototype; 212 } 213 214 /** 215 * Creates a <code>CharArray<code> with an appended <code>Character</code>. 216 * @param newChar the element to append 217 * @throws NullPointerException <code>newChar</code> is null 218 */ 219 public CharArray with(final Character newChar) { 220 return with(newChar.charValue()); 221 } 222 223 /* 224 * Convenience (more efficient) methods with char 225 */ 226 227 /** 228 * Gets the <code>char</code> at a specified position. 229 * @param i position of the element to return 230 * @throws ArrayIndexOutOfBoundsException <code>i</code> is out of bounds 231 */ 232 public char getChar(final int i) { 233 return chars[i]; 234 } 235 236 /** 237 * Creates a mutable copy of the <code>char</code> array 238 */ 239 public char[] toCharArray() { 240 return chars.clone(); 241 } 242 243 /** 244 * Creates a <code>CharArray</code> with an appended <code>char</code>. 245 * @param newChar the element to append 246 */ 247 public CharArray with(final char newChar) { 248 final char[] newChars = new char[chars.length + 1]; 249 System.arraycopy(chars, 0, newChars, 0, chars.length); 250 newChars[chars.length] = newChar; 251 return new CharArray(newChars); 252 } 253 254 /** 255 * Return a new <code>CharArray</code> that contains the same elements 256 * as this one excluding the element at a specified index 257 * @param i the index of the element to exclude 258 * @return the new array 259 */ 260 public CharArray without(final int i) { 261 final char[] newArr = new char[chars.length - 1]; 262 System.arraycopy(chars, 0, newArr, 0, i); 263 System.arraycopy(chars, i + 1, newArr, i, newArr.length - i); 264 return new CharArray(newArr); 265 } 266 267 /** 268 * A {@link CharArray} factory. 269 */ 270 public static final class Builder extends 271 PowerlessArray.Builder<Character> { 272 private char[] charBuffer; 273 274 /** 275 * Construct an instance with the default internal array length. 276 */ 277 Builder() { 278 this(0); 279 } 280 281 /** 282 * Construct an instance. 283 * @param estimate estimated array length 284 */ 285 Builder(int estimate) { 286 charBuffer = new char[estimate > 0 ? estimate : 32]; 287 size = 0; 288 } 289 290 // ArrayBuilder<Character> interface 291 /** 292 * Append a <code>Character</code> 293 * @param newChar the element to add 294 * @throws NegativeArraySizeException if the resulting internal array 295 * would exceed the maximum length of a Java array. The builder is 296 * unmodified. 297 */ 298 public void append(Character newChar) { 299 append ((char) newChar); 300 } 301 302 /** 303 * Append an array of <code>Character</code>s 304 * @param newChars the elements to add 305 * @throws IndexOutOfBoundsException if the resulting internal array 306 * would exceed the maximum length of a Java array. The builder is 307 * unmodified. 308 */ 309 public void append(final Character[] newChars) { 310 append(newChars, 0, newChars.length); 311 } 312 313 /** 314 * Append a range of elements from an array of <code>Character</code>s 315 * @param newChars the array to add elements from 316 * @param off the index of the first element to add 317 * @param len the number of elements to add 318 * @throws IndexOutOfBoundsException if an out-of-bounds index would 319 * be referenced or the resulting internal array would exceed the 320 * maximum length of a Java array. The builder is unmodified. 321 */ 322 public void append(final Character[] newChars, 323 final int off, final int len) { 324 int newSize = size + len; 325 if (newSize < 0 || off < 0 || len < 0 || off + len < 0 326 || off + len > newChars.length) { 327 throw new IndexOutOfBoundsException(); 328 } 329 if (newSize > charBuffer.length) { 330 int newLength = Math.max(newSize, 2 * charBuffer.length); 331 System.arraycopy(charBuffer, 0, 332 charBuffer = new char[newLength], 0, size); 333 } 334 335 for (int i = 0; i < len; ++i) { 336 charBuffer[size + i] = newChars[off + i]; 337 } 338 size = newSize; 339 } 340 341 /** 342 * Create a snapshot of the current content. 343 * @return a <code>CharArray</code> containing the elements so far 344 */ 345 public CharArray snapshot() { 346 final char[] arr; 347 if (size == charBuffer.length) { 348 arr = charBuffer; 349 } else { 350 arr = new char[size]; 351 System.arraycopy(charBuffer, 0, arr, 0, size); 352 } 353 return new CharArray(arr); 354 } 355 356 /* 357 * Convenience (more efficient) methods with char 358 */ 359 /** 360 * Append a <code>char</code> 361 * @param newChar the element to add 362 * @throws NegativeArraySizeException if the resulting internal array 363 * would exceed the maximum length of a Java array. The builder is 364 * unmodified. 365 */ 366 public void append(final char newChar) { 367 if (size == charBuffer.length) { 368 System.arraycopy(charBuffer, 0, 369 charBuffer = new char[2 * size], 0, size); 370 } 371 charBuffer[size++] = newChar; 372 } 373 374 /** 375 * Append an array of <code>char</code>s 376 * @param newChars the elements to add 377 * @throws IndexOutOfBoundsException if the resulting internal array 378 * would exceed the maximum length of a Java array. The builder is 379 * unmodified. 380 */ 381 public void append(final char[] newChars) { 382 append(newChars, 0, newChars.length); 383 } 384 385 /** 386 * Append a range of elements from an array of <code>char</code>s 387 * @param newChars the array to add elements from 388 * @param off the index of the first element to add 389 * @param len the number of elements to add 390 * @throws IndexOutOfBoundsException if an out-of-bounds index would 391 * be referenced or the resulting internal array would exceed the 392 * maximum length of a Java array. The builder is unmodified. 393 */ 394 public void append(final char[] newChars, final int off, 395 final int len) { 396 int newSize = size + len; 397 if (newSize < 0 || off < 0 || len < 0 || off + len < 0 398 || off + len > newChars.length) { 399 throw new IndexOutOfBoundsException(); 400 } 401 if (newSize > charBuffer.length) { 402 int newLength = Math.max(newSize, 2 * charBuffer.length); 403 System.arraycopy(charBuffer, 0, 404 charBuffer = new char[newLength], 0, size); 405 } 406 System.arraycopy(newChars, off, charBuffer, size, len); 407 size = newSize; 408 } 409 } 410 411 /* If one only invokes static methods statically, this is sound, since 412 * ByteArray extends PowerlessArray<Byte> and thus this method is 413 * only required to return something of a type covariant with 414 * PowerlessArray.Builder<Byte>. Unfortunately, this is not completely 415 * sound because it is possible to invoke static methods on instances, e.g. 416 * ConstArray.Builder<String> = (ConstArray (CharArray.array())).builder() 417 * Invocations of append() can then throw ClassCastExceptions. 418 * 419 * I can't see a way to avoid this other than to de-genericize everything. 420 */ 421 422 /** 423 * Get a <code>CharArray.Builder</code>. This is equivalent to the 424 * constructor. 425 * @return a new builder instance, with the default internal array length 426 */ 427 @SuppressWarnings("unchecked") 428 public static Builder builder() { 429 return new Builder(0); 430 } 431 432 /** 433 * Get a <code>CharArray.Builder</code>. This is equivalent to the 434 * constructor. 435 * @param estimate estimated array length 436 * @return a new builder instance 437 */ 438 @SuppressWarnings("unchecked") 439 public static Builder builder(final int estimate) { 440 return new Builder(estimate); 441 } 442 }