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>int</code>.
016     */
017    public final class IntArray extends PowerlessArray<Integer> {
018        static private final long serialVersionUID = 1L;   
019        
020        private /* final */ transient int[] ints;
021    
022        IntArray(int... ints) {
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.ints = ints;
028        }
029        
030        /**
031         * Constructs an array of <code>int</code>s.
032         * @param ints each element
033         */
034        static public IntArray array(final int... ints) {
035            return new IntArray(ints.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>IntArray</code>
045         */
046        @SuppressWarnings("unchecked")  // the warning here seems completely bogus
047        static public IntArray array() {
048            return new IntArray(new int[]{});
049        }
050    
051        /**
052         * Construct a <code>IntArray</code> with one element.
053         * @param value    the value
054         */    
055        static public IntArray array(int value) {
056            return new IntArray(new int[]{value});
057        }
058        
059        /**
060         * Construct a <code>IntArray</code> with two elements.
061         * @param value1    the first value
062         * @param value2    the second value
063         */     
064        static public IntArray array(int value1, int value2) {
065            return new IntArray(new int[]{value1, value2});
066        }
067        
068        /**
069         * Construct a <code>IntArray</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 IntArray array(int value1, int value2, int value3) {
075            return new IntArray(new int[]{value1, value2, value3});
076        }
077        
078        /**
079         * Construct a <code>IntArray</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 IntArray array(int value1, int value2, int value3,
086                                      int value4) {
087            return new IntArray(new int[]{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(ints.length);
102            for (int c : ints) {
103                out.writeInt(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            ints = new int[length];
113            for (int i = 0; i < length; ++i) {
114                ints[i] = in.readInt();
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 IntArray) {
132                // Simple case: just compare intArr fields
133                return Arrays.equals(ints, ((IntArray)other).ints);
134            } else if (other instanceof ConstArray<?>) {
135                // Other array does not have contents in intArr:
136                // check that length matches, and then compare elements one-by-one
137                final ConstArray<?> otherArray = (ConstArray<?>)other;
138                if (otherArray.length() != ints.length) {
139                    return false;
140                }            
141                for (int i = 0; i < ints.length; ++i) {
142                    final Object otherElement = otherArray.get(i);
143                    if (!(otherElement instanceof Integer) ||
144                        ((Integer)otherElement).intValue() != ints[i]) {
145                        return false;
146                    }
147                }            
148                return true;
149            } else {
150                // Only a ConstArray can be equal to a IntArray
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 IntArray has the same hashCode as a
164            // ConstArray<Integer> with the same contents.
165            return Arrays.hashCode(ints);
166        }
167        
168        /**
169         * Return a string representation of the array
170         */    
171        public String toString() { 
172            return Arrays.toString(ints);
173        }
174        
175        // org.joe_e.ConstArray interface
176    
177        /**
178         * Gets the length of the array.
179         */
180        public int length() { 
181            return ints.length;
182        }
183        
184        /**
185         * Creates a <code>Integer</code> for a specified <code>int</code>.
186         * @param i position of the element to return
187         * @throws ArrayIndexOutOfBoundsException <code>i</code> is out of bounds
188         */
189        public Integer get(int i) { 
190            return ints[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) (Integer) ints[i];
210            }
211            return prototype;
212        }
213        
214        /**
215         * Creates a <code>IntArray<code> with an appended <code>Integer</code>.
216         * @param newInt   the element to append
217         * @throws NullPointerException <code>newInt</code> is null
218         */
219        public IntArray with(final Integer newInt) {
220            return with(newInt.intValue());
221        }
222               
223        /*
224         * Convenience (more efficient) methods with int
225         */
226            
227        /**
228         * Gets the <code>int</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 int getInt(final int i) { 
233            return ints[i]; 
234        }
235    
236        /**
237         * Creates a mutable copy of the <code>int</code> array
238         */
239        public int[] toIntArray() {
240            return ints.clone(); 
241        }
242        
243        /** 
244         * Creates a <code>IntArray</code> with an appended <code>int</code>.
245         * @param newInt   the element to append
246         */
247        public IntArray with(final int newInt) {
248            final int[] newInts = new int[ints.length + 1];
249            System.arraycopy(ints, 0, newInts, 0, ints.length);
250            newInts[ints.length] = newInt;
251            return new IntArray(newInts);
252        }
253    
254        /**
255         * Return a new <code>IntArray</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 IntArray without(final int i) {
261            final int[] newArr = new int[ints.length - 1];
262            System.arraycopy(ints, 0, newArr, 0, i);
263            System.arraycopy(ints, i + 1, newArr, i, newArr.length - i);
264            return new IntArray(newArr);
265        }
266        
267        /**
268         * A {@link IntArray} factory.
269         */
270        public static final class Builder extends 
271                                            PowerlessArray.Builder<Integer> {
272            private int[] intBuffer;
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                intBuffer = new int[estimate > 0 ? estimate : 32];
287                size = 0;
288            }
289    
290            // ArrayBuilder<Integer> interface
291            /**
292             * Append a <code>Integer</code>
293             * @param newInt 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(Integer newInt) {
299                append ((int) newInt);
300            }
301    
302            /**
303             * Append an array of <code>Integer</code>s
304             * @param newInts 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 Integer[] newInts) {
310                append(newInts, 0, newInts.length);
311            }      
312    
313            /**
314             * Append a range of elements from an array of <code>Integer</code>s
315             * @param newInts 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 Integer[] newInts, 
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 > newInts.length) {
327                    throw new IndexOutOfBoundsException();
328                }
329                if (newSize > intBuffer.length) {
330                    int newLength = Math.max(newSize, 2 * intBuffer.length);
331                    System.arraycopy(intBuffer, 0, 
332                                     intBuffer = new int[newLength], 0, size);
333                }
334                
335                for (int i = 0; i < len; ++i) {
336                    intBuffer[size + i] = newInts[off + i];
337                }           
338                size = newSize;
339            }
340            
341            /**
342             * Create a snapshot of the current content.
343             * @return a <code>IntArray</code> containing the elements so far
344             */
345            public IntArray snapshot() {
346                final int[] arr;
347                if (size == intBuffer.length) {
348                    arr = intBuffer;
349                } else {
350                    arr = new int[size];
351                    System.arraycopy(intBuffer, 0, arr, 0, size);
352                }
353                return new IntArray(arr);
354            }
355            
356            /*
357             * Convenience (more efficient) methods with int
358             */       
359            /**
360             * Append a <code>int</code>
361             * @param newInt 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 int newInt) {
367                if (size == intBuffer.length) {
368                    System.arraycopy(intBuffer, 0,
369                                     intBuffer = new int[2 * size], 0, size);
370                }
371                intBuffer[size++] = newInt;
372            }
373    
374            /**
375             * Append an array of <code>int</code>s
376             * @param newInts 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 int[] newInts) {
382                append(newInts, 0, newInts.length);
383            }      
384    
385            /**
386             * Append a range of elements from an array of <code>int</code>s
387             * @param newInts 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 int[] newInts, 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 > newInts.length) {
399                    throw new IndexOutOfBoundsException();
400                }
401                if (newSize > intBuffer.length) {
402                    int newLength = Math.max(newSize, 2 * intBuffer.length);
403                    System.arraycopy(intBuffer, 0, 
404                                     intBuffer = new int[newLength], 0, size);
405                }
406                System.arraycopy(newInts, off, intBuffer, 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 (IntArray.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>IntArray.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>IntArray.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    }