View Javadoc
1   /*
2    * The baseCode project
3    * 
4    * Copyright (c) 2006 University of British Columbia
5    * 
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *       http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   *
18   */
19  package ubic.basecode.io;
20  
21  import java.io.ByteArrayInputStream;
22  import java.io.ByteArrayOutputStream;
23  import java.io.DataInputStream;
24  import java.io.DataOutputStream;
25  import java.io.IOException;
26  import java.io.UnsupportedEncodingException;
27  import java.nio.ByteBuffer;
28  import java.nio.CharBuffer;
29  import java.nio.DoubleBuffer;
30  import java.nio.IntBuffer;
31  import java.nio.LongBuffer;
32  import java.util.ArrayList;
33  import java.util.List;
34  
35  import org.apache.commons.lang3.ArrayUtils;
36  
37  import cern.colt.list.ByteArrayList;
38  import cern.colt.list.DoubleArrayList;
39  
40  /**
41   * Class to convert byte arrays (e.g., Blobs) to and from other types of arrays. TODO these could be static methods.
42   * 
43   * @author Kiran Keshav
44   * @author Paul Pavlidis
45   * 
46   */
47  public final class ByteArrayConverter {
48  
49      // sizes are in bytes.
50  
51      /**
52       * 3.3.4 The boolean Type
53       * <p>
54       * Although the Java virtual machine defines a boolean type, it only provides very limited support for it. There are
55       * no Java virtual machine instructions solely dedicated to operations on boolean values. Instead, expressions in
56       * the Java programming language that operate on boolean values are compiled to use values of the Java virtual
57       * machine int data type.
58       * <p>
59       * The Java virtual machine does directly support boolean arrays. Its newarray instruction enables creation of
60       * boolean arrays. Arrays of type boolean are accessed and modified using the byte array instructions baload and
61       * bastore.2
62       * <p>
63       * The Java virtual machine encodes boolean array components using 1 to represent true and 0 to represent false.
64       * Where Java programming language boolean values are mapped by compilers to values of Java virtual machine type
65       * int, the compilers must use the same encoding.
66       * 
67       * @see http://java.sun.com/docs/books/vmspec/2nd-edition/html/Overview.doc.html#12237
68       */
69      private static final int BOOL_SIZE = 1; // erm...this seems to work.
70  
71      private static final int DOUBLE_SIZE = 8;
72  
73      /**
74       * @param boolarray
75       * @return byte[]
76       */
77      public byte[] booleanArrayToBytes( boolean[] boolarray ) {
78          if ( boolarray == null ) return null;
79          ByteArrayOutputStream bos = new ByteArrayOutputStream();
80          DataOutputStream dos = new DataOutputStream( bos );
81          try {
82              for ( boolean element : boolarray ) {
83                  dos.writeBoolean( element );
84              }
85          } catch ( IOException e ) {
86              // do nothing
87          }
88          return bos.toByteArray();
89      }
90  
91      /**
92       * Convert a byte array with one-byte-per-character ASCII encoding (aka ISO-8859-1).
93       * 
94       * @param barray
95       * @return
96       */
97      public String byteArrayToAsciiString( byte[] barray ) {
98          if ( barray == null ) return null;
99          try {
100             return new String( barray, "ISO-8859-1" );
101         } catch ( UnsupportedEncodingException e ) {
102             throw new RuntimeException( "Conversion error", e );
103         }
104     }
105 
106     /**
107      * @param barray
108      * @return boolean[]
109      */
110     public boolean[] byteArrayToBooleans( byte[] barray ) {
111         if ( barray == null ) return null;
112         ByteArrayInputStream bis = new ByteArrayInputStream( barray );
113         DataInputStream dis = new DataInputStream( bis );
114         boolean[] iarray = new boolean[barray.length / BOOL_SIZE];
115         int i = 0;
116 
117         try {
118             while ( dis.available() > 0 ) {
119                 iarray[i] = dis.readBoolean();
120                 i++;
121             }
122             return iarray;
123 
124         } catch ( IOException e ) {
125             throw new RuntimeException( e );
126         } finally {
127             try {
128                 dis.close();
129                 bis.close();
130 
131             } catch ( IOException e ) {
132                 throw new RuntimeException( e );
133 
134             }
135         }
136 
137     }
138 
139     /**
140      * @param barray
141      * @return char[]
142      */
143     public char[] byteArrayToChars( byte[] barray ) {
144         if ( barray == null ) return null;
145 
146         CharBuffer buf = ByteBuffer.wrap( barray ).asCharBuffer();
147         char[] array = new char[buf.remaining()];
148         buf.get( array );
149         return array;
150 
151     }
152 
153     /**
154      * @param barray
155      * @param width how many items per row.
156      * @return double[][]
157      */
158     public double[][] byteArrayToDoubleMatrix( byte[] barray, int width ) throws IllegalArgumentException {
159 
160         int numDoubles = barray.length / DOUBLE_SIZE;
161         if ( numDoubles % width != 0 ) {
162             throw new IllegalArgumentException( "The number of doubles in the byte array (" + numDoubles
163                     + ") does not divide evenly into the number of items expected per row (" + width + ")." );
164         }
165 
166         int numRows = numDoubles / width;
167 
168         double[][] answer = new double[numRows][];
169 
170         byte[] row = new byte[width * DOUBLE_SIZE];
171         int bytesPerRow = width * DOUBLE_SIZE;
172         for ( int rownum = 0; rownum < numRows; rownum++ ) {
173 
174             int offset = rownum * bytesPerRow;
175             System.arraycopy( barray, offset, row, 0, bytesPerRow );
176             // for ( int i = 0; i < bytesPerRow; i++ ) {
177             // row[i] = barray[i + offset];
178             // }
179 
180             answer[rownum] = byteArrayToDoubles( row );
181         }
182         return answer;
183     }
184 
185     /**
186      * @param barray
187      * @return double[]
188      */
189     public double[] byteArrayToDoubles( byte[] barray ) {
190         if ( barray == null ) return null;
191 
192         DoubleBuffer buf = ByteBuffer.wrap( barray ).asDoubleBuffer();
193         double[] array = new double[buf.remaining()];
194         buf.get( array );
195 
196         return array;
197 
198     }
199 
200     /**
201      * @param barray
202      * @return int[]
203      */
204     public int[] byteArrayToInts( byte[] barray ) {
205         if ( barray == null ) return null;
206 
207         IntBuffer intBuf = ByteBuffer.wrap( barray ).asIntBuffer();
208         int[] array = new int[intBuf.remaining()];
209         intBuf.get( array );
210 
211         return array;
212 
213     }
214 
215     /**
216      * @param barray
217      * @return long[] resulting from parse of the bytes.
218      */
219     public long[] byteArrayToLongs( byte[] barray ) {
220         if ( barray == null ) return null;
221 
222         LongBuffer buf = ByteBuffer.wrap( barray ).asLongBuffer();
223         long[] array = new long[buf.remaining()];
224         buf.get( array );
225 
226         return array;
227     }
228 
229     /**
230      * Convert a byte array into a array of Strings. It is assumed that separate strings are delimited by '\u0000'
231      * (NUL). Note that this method cannot differentiate between empty strings and null strings. A string that is empty
232      * will be returned as an empty string, not null.
233      * 
234      * @param bytes
235      * @return
236      */
237     public String[] byteArrayToStrings( byte[] bytes ) {
238         List<String> strings = new ArrayList<String>();
239         ByteArrayList buf = new ByteArrayList();
240         for ( byte element : bytes ) {
241             if ( element == '\u0000' ) {
242                 String newString = new String( buf.elements() );
243                 newString = newString.trim();
244                 strings.add( newString );
245                 buf = new ByteArrayList();
246             } else {
247                 buf.add( element );
248             }
249         }
250 
251         String[] result = new String[strings.size()];
252         for ( int i = 0; i < strings.size(); i++ ) {
253             result[i] = strings.get( i );
254         }
255         return result;
256     }
257 
258     /**
259      * Convert a byte array to a tab-delimited string.
260      * 
261      * @param bytes
262      * @param type The Class of primitives the bytes are to be interpreted as. If this is String, then the bytes are
263      *        directly interpreted as tab-delimited string (e.g., no extra tabs are added).
264      * @return
265      * @throws UnsupportedOperationException if Class is a type that can't be converted by this.
266      */
267     public String byteArrayToTabbedString( byte[] bytes, Class<?> type ) {
268         if ( bytes == null ) return null;
269 
270         if ( type.equals( Double.class ) ) {
271             Double[] array = ArrayUtils.toObject( byteArrayToDoubles( bytes ) );
272             return formatAsString( array );
273         } else if ( type.equals( Integer.class ) ) {
274             Integer[] array = ArrayUtils.toObject( byteArrayToInts( bytes ) );
275             return formatAsString( array );
276         } else if ( type.equals( Long.class ) ) {
277             Long[] array = ArrayUtils.toObject( byteArrayToLongs( bytes ) );
278             return formatAsString( array );
279         } else if ( type.equals( String.class ) ) {
280             return byteArrayToAsciiString( bytes );
281         } else if ( type.equals( Boolean.class ) ) {
282             Boolean[] array = ArrayUtils.toObject( byteArrayToBooleans( bytes ) );
283             return formatAsString( array );
284         } else if ( type.equals( Character.class ) ) {
285             Character[] array = ArrayUtils.toObject( byteArrayToChars( bytes ) );
286             return formatAsString( array );
287         } else {
288             throw new UnsupportedOperationException( "Can't convert " + type.getName() );
289         }
290 
291     }
292 
293     /**
294      * @param carray
295      * @return byte[]
296      */
297     public byte[] charArrayToBytes( char[] carray ) {
298         if ( carray == null ) return null;
299         ByteArrayOutputStream bos = new ByteArrayOutputStream();
300         DataOutputStream dos = new DataOutputStream( bos );
301 
302         try {
303             for ( char element : carray ) {
304                 dos.writeChar( element );
305             }
306             dos.close();
307             bos.close();
308 
309         } catch ( IOException e ) {
310             // do nothing.
311         }
312 
313         return bos.toByteArray();
314     }
315 
316     /**
317      * @param darray
318      * @return byte[]
319      */
320     public byte[] doubleArrayToBytes( double[] darray ) {
321         if ( darray == null ) return null;
322         ByteArrayOutputStream bos = new ByteArrayOutputStream();
323         DataOutputStream dos = new DataOutputStream( bos );
324         try {
325             for ( double element : darray ) {
326                 dos.writeDouble( element );
327             }
328         } catch ( IOException e ) {
329             // do nothing
330         }
331         return bos.toByteArray();
332     }
333 
334     /**
335      * @param darray
336      * @return byte[]
337      */
338     public byte[] doubleArrayToBytes( Double[] darray ) {
339         return doubleArrayToBytes( ArrayUtils.toPrimitive( darray ) );
340     }
341 
342     /**
343      * @param darray
344      * @return
345      */
346     public byte[] doubleArrayToBytes( DoubleArrayList darray ) {
347         return doubleArrayToBytes( ( Double[] ) darray.toList().toArray( new Double[] {} ) );
348     }
349 
350     /**
351      * @param testm
352      * @return
353      */
354     public byte[] doubleMatrixToBytes( double[][] testm ) {
355 
356         if ( testm == null || testm.length == 0 ) throw new IllegalArgumentException( "Null or empty matrix" );
357 
358         int rowSize = testm[0].length;
359 
360         double[] a = new double[testm.length * rowSize];
361 
362         for ( int i = 0; i < testm.length; i++ ) {
363             if ( testm[i].length != rowSize ) throw new IllegalArgumentException( "Cannot serialize ragged matrix" );
364             for ( int j = 0; j < rowSize; j++ ) {
365                 a[j + rowSize * i] = testm[i][j];
366             }
367         }
368         return doubleArrayToBytes( a );
369 
370     }
371 
372     /**
373      * @param iarray
374      * @return byte[]
375      */
376     public byte[] intArrayToBytes( int[] iarray ) {
377         if ( iarray == null ) return null;
378         ByteArrayOutputStream bos = new ByteArrayOutputStream();
379         DataOutputStream dos = new DataOutputStream( bos );
380         try {
381             for ( int element : iarray ) {
382                 dos.writeInt( element );
383             }
384             dos.close();
385             bos.close();
386         } catch ( IOException e ) {
387             // do nothing
388         }
389         return bos.toByteArray();
390     }
391 
392     /**
393      * @param larray
394      * @return byte[]
395      */
396     public byte[] longArrayToBytes( long[] larray ) {
397         if ( larray == null ) return null;
398         ByteArrayOutputStream bos = new ByteArrayOutputStream();
399         DataOutputStream dos = new DataOutputStream( bos );
400         try {
401             for ( long element : larray ) {
402                 dos.writeLong( element );
403             }
404             dos.close();
405             bos.close();
406         } catch ( IOException e ) {
407             // do nothing
408         }
409         return bos.toByteArray();
410     }
411 
412     /**
413      * Note that this method cannot differentiate between empty strings and null strings. A string that is empty will be
414      * returned as an empty string, not null, while a null string will be stored as an empty string.
415      * 
416      * @param stringArray
417      * @return byte[]
418      */
419     public byte[] stringArrayToBytes( Object[] stringArray ) {
420         if ( stringArray == null ) return null;
421         ByteArrayOutputStream bos = new ByteArrayOutputStream();
422         DataOutputStream dos = new DataOutputStream( bos );
423 
424         try {
425             for ( Object element : stringArray ) {
426                 String string = ( String ) element;
427                 if ( string != null ) {
428                     dos.write( string.getBytes() );
429                 }
430                 dos.write( '\u0000' );
431             }
432             dos.close();
433             bos.close();
434 
435         } catch ( IOException e ) {
436             // do nothing.
437         }
438 
439         return bos.toByteArray();
440     }
441 
442     /**
443      * @param data
444      */
445     public byte[] toBytes( Object data ) {
446         return toBytes( new Object[] { data } );
447     }
448 
449     /**
450      * Convert an array of Objects into an array of bytes. If the array contains Strings, it is converted to a
451      * tab-delimited string, and then converted to bytes.
452      * 
453      * @param array of Objects to be converted to bytes.
454      * @return
455      * @throws UnsupportedOperationException if Objects are a type that can't be converted by this.
456      */
457     public byte[] toBytes( Object[] array ) {
458         if ( array == null ) return null;
459         if ( array.length == 0 ) return new byte[] {};
460 
461         // sanity check, catches obvious errors.
462         if ( array[0] == null ) throw new IllegalArgumentException( "Null values cannot be converted" );
463 
464         if ( array[0] instanceof Boolean ) {
465             boolean[] toConvert = new boolean[array.length];
466             for ( int i = 0; i < array.length; i++ ) {
467                 boolean object = ( ( Boolean ) array[i] ).booleanValue();
468                 toConvert[i] = object;
469             }
470             return booleanArrayToBytes( toConvert );
471         } else if ( array[0] instanceof Double ) {
472             double[] toConvert = new double[array.length];
473             for ( int i = 0; i < array.length; i++ ) {
474                 double object = ( ( Double ) array[i] ).doubleValue();
475                 toConvert[i] = object;
476             }
477             return doubleArrayToBytes( toConvert );
478         } else if ( array[0] instanceof Character ) {
479             char[] toConvert = new char[array.length];
480             for ( int i = 0; i < array.length; i++ ) {
481                 char object = ( ( Character ) array[i] ).charValue();
482                 toConvert[i] = object;
483             }
484             return charArrayToBytes( toConvert );
485         } else if ( array[0] instanceof String ) {
486             return stringArrayToBytes( array );
487         } else if ( array[0] instanceof Integer ) {
488             int[] toConvert = new int[array.length];
489             for ( int i = 0; i < array.length; i++ ) {
490                 int object = ( ( Integer ) array[i] ).intValue();
491                 toConvert[i] = object;
492             }
493             return intArrayToBytes( toConvert );
494         } else if ( array[0] instanceof Long ) {
495             long[] toConvert = new long[array.length];
496             for ( int i = 0; i < array.length; i++ ) {
497                 int object = ( ( Long ) array[i] ).intValue();
498                 toConvert[i] = object;
499             }
500             return longArrayToBytes( toConvert );
501         } else {
502             throw new UnsupportedOperationException( "Can't convert " + array[0].getClass() + " to bytes" );
503         }
504 
505     }
506 
507     /**
508      * @param array
509      * @return
510      */
511     private String formatAsString( Object[] array ) {
512         StringBuffer buf = new StringBuffer();
513         for ( int i = 0; i < array.length; i++ ) {
514             buf.append( array[i] );
515             if ( i != array.length - 1 ) buf.append( "\t" ); // so we don't have a trailing tab.
516         }
517         return buf.toString();
518     }
519 }