Adjusted byte array method to remove most of magic numbers.
[bgpcep.git] / util / src / main / java / org / opendaylight / protocol / util / ByteArray.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.protocol.util;
9
10 import java.io.File;
11 import java.io.FileInputStream;
12 import java.io.IOException;
13 import java.nio.ByteBuffer;
14 import java.nio.charset.CharacterCodingException;
15 import java.nio.charset.Charset;
16 import java.util.Arrays;
17 import java.util.BitSet;
18
19 import org.apache.commons.codec.binary.Hex;
20
21 /**
22  * 
23  * Util class for methods working with byte array.
24  * 
25  */
26 public final class ByteArray {
27         private ByteArray() {
28         }
29
30         /**
31          * Returns a new byte array from given byte array, starting at start index with the size of the length parameter.
32          * Byte array given as parameter stays untouched.
33          * 
34          * @param bytes original byte array
35          * @param startIndex beginning index, inclusive
36          * @param length how many bytes should be in the sub-array
37          * @return a new byte array that is a sub-array of the original
38          */
39         public static byte[] subByte(final byte[] bytes, final int startIndex, final int length) {
40                 if (bytes.length == 0 || length < 0 || length > bytes.length || startIndex < 0 || startIndex > bytes.length
41                                 || startIndex + length > bytes.length) {
42                         throw new IllegalArgumentException("Cannot create subByte, invalid arguments: Length: " + length + " startIndex: " + startIndex);
43                 }
44                 final byte[] res = new byte[length];
45                 System.arraycopy(bytes, startIndex, res, 0, length);
46                 return res;
47         }
48
49         /**
50          * Converts byte array to Integer. If there are less bytes in the array as required (4), the method will push
51          * adequate number of zero bytes prepending given byte array.
52          * 
53          * @param bytes array to be converted to int
54          * @return int
55          */
56         public static int bytesToInt(final byte[] bytes) {
57                 if (bytes.length > Integer.SIZE / 8) {
58                         throw new IllegalArgumentException("Cannot convert bytes to integer. Byte array too big.");
59                 }
60                 byte[] res = new byte[Integer.SIZE / 8];
61                 if (bytes.length != Integer.SIZE / 8) {
62                         System.arraycopy(bytes, 0, res, Integer.SIZE / 8 - bytes.length, bytes.length);
63                 } else {
64                         res = bytes;
65                 }
66                 final ByteBuffer buff = ByteBuffer.wrap(res);
67                 return buff.getInt();
68         }
69
70         /**
71          * Converts byte array to long. If there are less bytes in the array as required (Long.Size), the method will push
72          * adequate number of zero bytes prepending given byte array.
73          * 
74          * @param bytes array to be converted to long
75          * @return long
76          */
77         public static long bytesToLong(final byte[] bytes) {
78                 if (bytes.length > Long.SIZE / 8) {
79                         throw new IllegalArgumentException("Cannot convert bytes to long.Byte array too big.");
80                 }
81                 byte[] res = new byte[Long.SIZE / 8];
82                 if (bytes.length != Long.SIZE / 8) {
83                         System.arraycopy(bytes, 0, res, Long.SIZE / 8 - bytes.length, bytes.length);
84                 } else {
85                         res = bytes;
86                 }
87                 final ByteBuffer buff = ByteBuffer.wrap(res);
88                 return buff.getLong();
89         }
90
91         /**
92          * Converts byte array to float IEEE 754 format. If there are less bytes in the array as required (Float.Size), the
93          * method will push adequate number of zero bytes prepending given byte array.
94          * 
95          * @param bytes array to be converted to float
96          * @return float
97          */
98         public static float bytesToFloat(final byte[] bytes) {
99                 if (bytes.length > Float.SIZE / 8) {
100                         throw new IllegalArgumentException("Cannot convert bytes to float.Byte array too big.");
101                 }
102                 byte[] res = new byte[Float.SIZE / 8];
103                 if (bytes.length != Float.SIZE / 8) {
104                         System.arraycopy(bytes, 0, res, Float.SIZE / 8 - bytes.length, bytes.length);
105                 } else {
106                         res = bytes;
107                 }
108                 final ByteBuffer buff = ByteBuffer.wrap(res);
109                 return buff.getFloat();
110         }
111
112         /**
113          * Cuts 'count' number of bytes from the beginning of given byte array.
114          * 
115          * @param bytes array to be cut, cannot be null
116          * @param count how many bytes needed to be cut, needs to be > 0
117          * @return bytes array without first 'count' bytes
118          */
119         public static byte[] cutBytes(final byte[] bytes, final int count) {
120                 if (bytes.length == 0 || count > bytes.length || count <= 0) {
121                         throw new IllegalArgumentException("Cannot cut bytes, invalid arguments: Count: " + count + " bytes.length: " + bytes.length);
122                 }
123                 return Arrays.copyOfRange(bytes, count, bytes.length);
124         }
125
126         /**
127          * Parse byte to bits, from the leftmost bit.
128          * 
129          * @param b byte to be parsed
130          * @return array of booleans with size of 8
131          */
132         public static boolean[] parseBits(final byte b) {
133                 final boolean[] bits = new boolean[Byte.SIZE];
134                 int j = 0;
135                 for (int i = Byte.SIZE - 1; i >= 0; i--) {
136                         bits[j] = ((b & (1 << i)) != 0);
137                         j++;
138                 }
139                 return bits;
140         }
141
142         /**
143          * Parses array of bytes to BitSet, from left most bit.
144          * 
145          * @param bytes array of bytes to be parsed
146          * @return BitSet with length = bytes.length * Byte.SIZE
147          */
148         public static BitSet bytesToBitSet(final byte[] bytes) {
149                 final BitSet bitSet = new BitSet(bytes.length * Byte.SIZE);
150                 for (int bytes_iter = 0; bytes_iter < bytes.length; bytes_iter++) {
151                         final int offset = bytes_iter * Byte.SIZE;
152                         for (int byte_iter = Byte.SIZE - 1; byte_iter >= 0; byte_iter--) {
153                                 bitSet.set(offset + (Byte.SIZE - byte_iter - 1), (bytes[bytes_iter] & 1 << (byte_iter)) != 0);
154                         }
155                 }
156                 return bitSet;
157         }
158
159         /**
160          * Parses BitSet to bytes, from most left bit.
161          * 
162          * @param bitSet BitSet to be parsed
163          * @param returnedLength Length of returned array. Overlapping flags are truncated.
164          * @return parsed array of bytes with length of bitSet.length / Byte.SIZE
165          */
166         public static byte[] bitSetToBytes(final BitSet bitSet, final int returnedLength) {
167                 final byte[] bytes = new byte[returnedLength];
168
169                 for (int bytes_iter = 0; bytes_iter < bytes.length; bytes_iter++) {
170                         final int offset = bytes_iter * Byte.SIZE;
171
172                         for (int byte_iter = Byte.SIZE - 1; byte_iter >= 0; byte_iter--) {
173                                 bytes[bytes_iter] |= (bitSet.get(offset + (Byte.SIZE - byte_iter - 1)) ? 1 << byte_iter : 0);
174                         }
175                 }
176                 return bytes;
177         }
178
179         /**
180          * Parses file to array of bytes
181          * 
182          * @param name path to file to by parsed
183          * @return parsed array of bytes
184          */
185         public static byte[] fileToBytes(final String name) throws IOException {
186                 final File file = new File(name);
187                 int offset = 0;
188                 int numRead = 0;
189
190                 if (file.length() > Integer.MAX_VALUE) {
191                         throw new IOException("Too large file to load in byte array.");
192                 }
193
194                 final FileInputStream fin = new FileInputStream(file);
195                 final byte[] byteArray = new byte[(int) file.length()];
196
197                 while (offset < byteArray.length && (numRead = fin.read(byteArray, offset, byteArray.length - offset)) >= 0) {
198                         offset += numRead;
199                 }
200
201                 if (fin != null) {
202                         fin.close();
203                 }
204
205                 return byteArray;
206         }
207
208         /**
209          * Parses integer to array of bytes
210          * 
211          * @param num integer to be parsed
212          * @return parsed array of bytes with length of Integer.SIZE/Byte.SIZE
213          */
214         public static byte[] intToBytes(final int num) {
215                 return intToBytes(num, Integer.SIZE / Byte.SIZE);
216         }
217
218         /**
219          * Parses integer to array of bytes
220          * 
221          * @param num integer to be parsed
222          * @param size desired byte array length
223          * @return parsed array of bytes with length of size
224          */
225         public static byte[] intToBytes(final int num, final int size) {
226                 final int finalSize = Integer.SIZE / Byte.SIZE;
227                 final ByteBuffer bytesBuffer = ByteBuffer.allocate(finalSize);
228                 bytesBuffer.putInt(num);
229                 return ByteArray.subByte(bytesBuffer.array(), finalSize - size, size);
230         }
231
232         /**
233          * Parses long to array of bytes
234          * 
235          * @param num long to be parsed
236          * @return parsed array of bytes with length of Long.SIZE/Byte.SIZE
237          */
238         public static byte[] longToBytes(final int num) {
239                 return longToBytes(num, Long.SIZE / Byte.SIZE);
240         }
241
242         /**
243          * Parses long to array of bytes
244          * 
245          * @param num long to be parsed
246          * @param size desired byte array length
247          * @return parsed array of bytes with length of size
248          */
249         public static byte[] longToBytes(final long num, final int size) {
250                 final int finalSize = Long.SIZE / Byte.SIZE;
251                 final ByteBuffer bytesBuffer = ByteBuffer.allocate(finalSize);
252                 bytesBuffer.putLong(num);
253                 return ByteArray.subByte(bytesBuffer.array(), finalSize - size, size);
254         }
255
256         /**
257          * Copies range of bits from passed byte and align to right.<br/>
258          * 
259          * @param src source byte to copy from
260          * @param fromBit bit from which will copy (inclusive) - numbered from 0
261          * @param length of bits to by copied - <1,8>
262          * @return copied value aligned to right
263          */
264         public static byte copyBitsRange(final byte src, final int fromBit, final int length) {
265                 if (fromBit < 0 | fromBit > Byte.SIZE - 1 | length < 1 | length > Byte.SIZE) {
266                         throw new IllegalArgumentException("fromBit or toBit is out of range.");
267                 }
268                 if (fromBit + length > Byte.SIZE) {
269                         throw new IllegalArgumentException("Out of range.");
270                 }
271
272                 byte retByte = 0;
273                 int retI = 0;
274
275                 for (int i = fromBit + length - 1; i >= fromBit; i--) {
276
277                         if ((src & 1 << (Byte.SIZE - i - 1)) != 0) {
278                                 retByte |= 1 << retI;
279                         }
280
281                         retI++;
282                 }
283
284                 return retByte;
285         }
286
287         /**
288          * Copies whole source byte array to destination from offset.<br/>
289          * Length of src can't be bigger than dest length minus offset
290          * 
291          * @param src byte[]
292          * @param dest byte[]
293          * @param offset int
294          */
295         public static void copyWhole(final byte[] src, final byte[] dest, final int offset) {
296                 if (dest.length - offset < src.length) {
297                         throw new ArrayIndexOutOfBoundsException("Can't copy whole array.");
298                 }
299
300                 System.arraycopy(src, 0, dest, offset, src.length);
301         }
302
303         /**
304          * Convert array of bytes to java short.<br/>
305          * Size can't be bigger than size of short in bytes.
306          * 
307          * @param bytes byte[]
308          * @return array of bytes
309          */
310         public static short bytesToShort(final byte[] bytes) {
311                 if (bytes.length > Short.SIZE / Byte.SIZE) {
312                         throw new IllegalArgumentException("Cannot convert bytes to short. Byte array too big.");
313                 }
314                 byte[] res = new byte[Short.SIZE / Byte.SIZE];
315                 if (bytes.length != Short.SIZE / Byte.SIZE) {
316                         System.arraycopy(bytes, 0, res, Integer.SIZE / Byte.SIZE - bytes.length, bytes.length);
317                 } else {
318                         res = bytes;
319                 }
320                 final ByteBuffer buff = ByteBuffer.wrap(res);
321                 return buff.getShort();
322         }
323
324         /**
325          * Convert short java representation to array of bytes.
326          * 
327          * @param num short
328          * @return short represented as array of bytes
329          */
330         public static byte[] shortToBytes(final short num) {
331                 final ByteBuffer bytesBuffer = ByteBuffer.allocate(Short.SIZE / Byte.SIZE);
332                 bytesBuffer.putShort(num);
333
334                 return bytesBuffer.array();
335         }
336
337         /**
338          * Convert float java representation to array of bytes.
339          * 
340          * @param num float
341          * @return float represented as array of bytes
342          */
343         public static byte[] floatToBytes(final float num) {
344                 final ByteBuffer bytesBuffer = ByteBuffer.allocate(Float.SIZE / Byte.SIZE);
345                 bytesBuffer.putFloat(num);
346
347                 return bytesBuffer.array();
348         }
349
350         /**
351          * Pretty print array of bytes as hex encoded string with 16 bytes per line. Each byte is separated by space, after
352          * first 8 bytes there are 2 spaces instead of one.
353          */
354         public static String bytesToHexString(final byte[] array) {
355                 return bytesToHexString(array, 16, " ", 8, " ");
356         }
357
358         /**
359          * Pretty-print an array of bytes as hex-encoded string. Separate them with specified separator.
360          */
361         public static String toHexString(final byte[] array, final String separator) {
362                 final StringBuilder sb = new StringBuilder();
363                 for (int i = 0; i < array.length; i++) {
364                         sb.append(Hex.encodeHexString(new byte[] { array[i] }));
365                         if (i + 1 != array.length) {
366                                 sb.append(separator);
367                         }
368                 }
369                 return sb.toString();
370         }
371
372         /**
373          * Convert array of bytes to hexadecimal String.
374          * 
375          * @param array
376          * @param bytesOnLine number of bytes that should by displayed in one line
377          * @param byteSeparator string that will be placed after each byte
378          * @param wordCount number of bytes that make a 'word' (group of bytes)
379          * @param wordSeparator string that will be placed after each word
380          * @return Hexadecimal string representation of given byte array
381          */
382         public static String bytesToHexString(final byte[] array, final int bytesOnLine, final String byteSeparator, final int wordCount,
383                         final String wordSeparator) {
384                 final StringBuilder sb = new StringBuilder();
385                 for (int i = 0; i < array.length; i++) {
386                         sb.append(Hex.encodeHexString(new byte[] { array[i] }));
387                         if ((i + 1) % bytesOnLine == 0) {
388                                 sb.append("\n");
389                         } else {
390                                 sb.append(byteSeparator);
391                                 if ((i + 1) % wordCount == 0) {
392                                         sb.append(wordSeparator);
393                                 }
394                         }
395
396                 }
397                 return sb.toString();
398         }
399
400         /**
401          * Decodes bytes to human readable UTF-8 string. If bytes are not valid UTF-8, they are represented as raw binary.
402          * 
403          * @param bytes bytes to be decoded to string
404          * @return String representation of passed bytes
405          */
406         public static String bytesToHRString(final byte[] bytes) {
407                 try {
408                         return Charset.forName("UTF-8").newDecoder().decode(ByteBuffer.wrap(bytes)).toString();
409                 } catch (final CharacterCodingException e) {
410                         return Arrays.toString(bytes);
411                 }
412         }
413
414         /**
415          * Searches for byte sequence in given array. Returns the index of first occurrence of this sequence (where it
416          * starts).
417          * 
418          * @param bytes byte array where to search for sequence
419          * @param sequence to be searched in given byte array
420          * @return -1 if the sequence could not be found in given byte array int index of first occurrence of the sequence
421          *         in bytes
422          */
423         public static int findByteSequence(final byte[] bytes, final byte[] sequence) {
424                 if (bytes.length < sequence.length) {
425                         throw new IllegalArgumentException("Sequence to be found is longer than the given byte array.");
426                 }
427                 if (bytes.length == sequence.length) {
428                         if (Arrays.equals(bytes, sequence)) {
429                                 return 0;
430                         } else {
431                                 return -1;
432                         }
433                 }
434                 int j = 0;
435                 for (int i = 0; i < bytes.length; i++) {
436                         if (bytes[i] == sequence[j]) {
437                                 j++;
438                                 if (j == sequence.length) {
439                                         return i - j + 1;
440                                 }
441                         } else {
442                                 j = 0;
443                         }
444                 }
445                 return -1;
446         }
447
448         private static final byte maskBits[] = new byte[] { 0, -128, -64, -32, -16, -8, -4, -2 };
449
450         public static final byte[] maskBytes(final byte[] original, final int bits) {
451                 if (original.length * 8 < bits) {
452                         throw new IllegalArgumentException("Attempted to apply invalid mask (too long)");
453                 }
454
455                 final int needbytes = (bits + 7) / 8;
456                 // We need to have a new copy of the underlying byte array, so that
457                 // the original bytes stay untouched
458                 final byte[] bytes = Arrays.copyOf(original, original.length);
459
460                 final int needmask = bits % 8;
461                 if (needmask != 0) {
462                         bytes[needbytes - 1] &= maskBits[needmask];
463                 }
464
465                 // zero-out the rest of the bytes
466                 for (int i = needbytes; i < bytes.length; i++) {
467                         bytes[i] = 0;
468                 }
469                 return bytes;
470         }
471 }