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 org.joe_e.Immutable;
009    import org.joe_e.JoeE;
010    import org.joe_e.reflect.Reflection;
011    
012    /**
013     * An immutable array containing immutable objects.
014     * 
015     * @param <E> the element type of objects contained in the array
016     */
017    public class ImmutableArray<E> extends ConstArray<E> implements Immutable { 
018        static private final long serialVersionUID = 1L;
019        
020        
021        /**
022         * Package-scope back-door constructor for use by subclasses that
023         * override all methods that make use of the field arr.  Nullity of arr is
024         * used to distinguish between instances with which this class must interact
025         * by using the public interface rather than through their arr field.
026         */
027            ImmutableArray(Object[] arr) {
028                    super(arr);
029            }
030        
031        /**
032         * Construct a <code>ImmutableArray</code>.
033         * @param values    each value
034         * @throws ClassCastException if the runtime component type of 
035         *     <code>values</code> is not immutable in the overlay type system
036         */
037        static public <T> ImmutableArray<T> array(final T... values) {
038            final Class<?> e = values.getClass().getComponentType();
039            if (!JoeE.isSubtypeOf(e, Immutable.class)) {
040                throw new ClassCastException(Reflection.getName(e) +
041                                             " is not Immutable");
042            }
043            return new ImmutableArray<T>(values.clone());
044        }
045        
046        /* 
047         * See the comment in ConstArray for why the following methods exist.
048         */
049    
050        /**
051         * Construct an empty <code>ConstArray</code>.
052         */
053        static public <T> ImmutableArray<T> array() {
054            return new ImmutableArray<T>(new Object[]{});
055        }  
056        
057        /**
058         * Checks an array to see that all elements are immutable; if so wraps
059         * it WITHOUT a defensive copy
060         */
061        static private <T> ImmutableArray<T> array2(final Object... values) {
062            for (Object v: values) {
063                Class<?> vType = v.getClass();
064                if (!JoeE.isSubtypeOf(vType, Immutable.class)) {
065                    throw new ClassCastException(Reflection.getName(vType) +
066                                                 " is not Immutable");
067                }
068            }
069            return new ImmutableArray<T>(values);
070        }
071    
072        /**
073         * Construct a <code>ConstArray</code> with one element.
074         * @param value    the value
075         */
076        static public <T> ImmutableArray<T> array(final T value) {
077            return array2(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> ImmutableArray<T> array(final T value1, final T value2) {
086            return array2(value1, value2);
087        }
088    
089        /**
090         * Construct an <code>ImmutableArray</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> ImmutableArray<T> array(final T value1, final T value2, 
096                                              final T value3) {
097            return array2(value1, value2, value3);
098        }
099    
100        /**
101         * Construct an <code>ImmutableArray</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> ImmutableArray<T> array(final T value1, final T value2, 
108                                              final T value3, final T value4) {
109            return array2(value1, value2, value3, value4);
110        }
111        
112        /**
113         * Is this object's equals() implementation known to be reflexive?
114         */
115        private transient boolean reflexive = false;
116    
117        public boolean equals(final Object x) {
118            return this == x ? reflexive || (reflexive = super.equals(x)) :
119                               super.equals(x);
120        }
121            
122        /**
123         * Return a new <code>PowerlessArray</code> that contains the same elements
124         * as this one but with a new element added to the end.
125         * @param newE an element to add
126         * @return the new array
127         * @throws ClassCastException if <code>newE</code> is not immutable 
128         */ 
129        public ImmutableArray<E> with(final E newE) {
130            if (!JoeE.instanceOf(newE, Immutable.class)) {
131                throw new ClassCastException(Reflection.getName(newE.getClass()) +
132                                             "is not Immutable");
133            }
134            // We use a new Object array here, because we don't know the static type
135            // of E that was used; it may not match the dynamic component type of
136            // arr due to array covariance.
137            final Object[] newArr = new Object[arr.length + 1];
138            System.arraycopy(arr, 0, newArr, 0, arr.length);
139            newArr[arr.length] = newE;
140            return new ImmutableArray<E>(newArr);
141        }
142        
143        /**
144         * Return a new <code>ImmutableArray</code> that contains the same elements
145         * as this one excluding the element at a specified index
146         * @param i the index of the element to exclude
147         * @return  the new array
148         */
149        public ImmutableArray<E> without(final int i) {
150            final Object[] newArr = new Object[arr.length - 1];
151            System.arraycopy(arr, 0, newArr, 0, i);
152            System.arraycopy(arr, i + 1, newArr, i, newArr.length - i);
153            return new ImmutableArray<E>(newArr);
154        }
155        
156        
157        /**
158         * An {@link ImmutableArray} factory.
159         */
160        public static class Builder<E> extends ConstArray.Builder<E> {
161            /**
162             * Construct an instance with the default internal array length.
163             */
164            Builder() {
165                super();
166            }
167            
168            /**
169             * Construct an instance.
170             * @param estimate  estimated array length
171             */
172            Builder(int estimate) {
173                super(estimate);
174            }        
175    
176            /** 
177             * Appends an element to the Array
178             * @param newE the element to append
179             * @throws ClassCastException if the <code>newE</code> is not immutable
180             * @throws NegativeArraySizeException if the resulting internal array
181             *  would exceed the maximum length of a Java array.  The builder is
182             *  unmodified.
183             */
184             public void append(E newE) {
185                if (!JoeE.instanceOf(newE, Immutable.class)) {
186                    throw new ClassCastException(Reflection.getName(newE.getClass())
187                                                 + "is not Immutable");
188                }
189                
190                appendInternal(newE);
191            }
192    
193            /** 
194             * Appends all elements from a Java array to the Array
195             * @param newEs the element to append
196             * @throws ClassCastException if the <code>newEs</code> is not immutable
197             * @throws IndexOutOfBoundsException if the resulting internal array
198             *  would exceed the maximum length of a Java array.  The builder is
199             *  unmodified. 
200             */
201            public void append(E[] newEs) {
202                append(newEs, 0, newEs.length);
203            }
204    
205            /** 
206             * Appends a range of elements from a Java array to the Array
207             * @param newEs the source array
208             * @param off   the index of the first element to append
209             * @param len   the number of elements to append
210             * @throws ClassCastException if the <code>newEs</code> is not immutable
211             * @throws IndexOutOfBoundsException if an out-of-bounds index would
212             *  be referenced or the resulting internal array would exceed the
213             *  maximum length of a Java array.  The builder is unmodified.
214             */
215            public void append(E[] newEs, int off, int len) {
216                final Class<?> e = newEs.getClass().getComponentType();
217                if (!JoeE.isSubtypeOf(e, Immutable.class)) {
218                    throw new ClassCastException(Reflection.getName(e) + 
219                                                 " is not Immutable");
220                }
221                
222                appendInternal(newEs, off, len);
223            }
224            
225            /**
226             * Create a snapshot of the current content.
227             * @return an <code>ImmutableArray<E></code> containing the elements so far
228             */
229            public ImmutableArray<E> snapshot() {
230                final Object[] arr;
231                if (size == buffer.length) {
232                    arr = buffer;
233                } else {
234                    arr = new Object[size];
235                    System.arraycopy(buffer, 0, arr, 0, size);
236                }
237                return new ImmutableArray<E>(arr);
238            }
239        }   
240        
241        /**
242         * Get an <code>ImmutableArray.Builder</code>.  This is equivalent to the
243         * constructor.
244         * @return a new builder instance, with the default internal array length
245         */
246        public static <E> Builder<E> builder() {
247            return new Builder<E>(0);
248        }
249        
250        /**
251         * Get an <code>ImmutableArray.Builder</code>.  This is equivalent to the
252         * constructor.
253         * @param estimate  estimated array length  
254         * @return a new builder instance
255         */    
256        public static <E> Builder<E> builder(final int estimate) {
257            return new Builder<E>(estimate);
258        }
259    }