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