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 "&lt;unprintable&gt;".  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    }