package mil.nga.giat.geowave.core.index; import java.nio.ByteBuffer; import java.util.UUID; import org.apache.commons.lang3.tuple.Pair; import com.google.common.base.Preconditions; import com.google.common.io.BaseEncoding; /** * Convenience methods for converting binary data to and from strings. The * encoding and decoding is done in base-64. These methods should be used for * converting data that is binary in nature to a String representation for * transport. Use StringUtils for serializing and deserializing text-based data. * * Additionally, this class has methods for manipulating byte arrays, such as * combining or incrementing them. */ public class ByteArrayUtils { private static byte[] internalCombineArrays( final byte[] beginning, final byte[] end ) { final byte[] combined = new byte[beginning.length + end.length]; System.arraycopy( beginning, 0, combined, 0, beginning.length); System.arraycopy( end, 0, combined, beginning.length, end.length); return combined; } /** * Convert binary data to a string for transport * * @param byteArray * the binary data * @return the base64url encoded string */ public static String byteArrayToString( final byte[] byteArray ) { return BaseEncoding.base64Url().encode( byteArray); } /** * Convert a string representation of binary data back to a String * * @param str * the string representation of binary data * @return the base64url decoded binary data */ public static byte[] byteArrayFromString( final String str ) { return BaseEncoding.base64Url().decode( str); } /** * Combine 2 arrays into one large array. If both are not null it will * append id2 to id1 and the result will be of length id1.length + * id2.length * * @param id1 * the first byte array to use (the start of the result) * @param id2 * the second byte array to combine (appended to id1) * @return the concatenated byte array */ public static byte[] combineArrays( final byte[] id1, final byte[] id2 ) { byte[] combinedId; if (id1 == null || id1.length == 0) { combinedId = id2; } else if (id2 == null || id2.length == 0) { combinedId = id1; } else { // concatenate bin ID 2 to the end of bin ID 1 combinedId = ByteArrayUtils.internalCombineArrays( id1, id2); } return combinedId; } /** * add 1 to the least significant bit in this byte array (the last byte in * the array) * * @param value * the array to increment * @return will return true as long as the value did not overflow */ public static boolean increment( final byte[] value ) { for (int i = value.length - 1; i >= 0; i--) { value[i]++; if (value[i] != 0) { return true; } } return value[0] != 0; } /** * Converts a UUID to a byte array * * @param uuid * the uuid * @return the byte array representing that UUID */ public static byte[] uuidToByteArray( final UUID uuid ) { final ByteBuffer bb = ByteBuffer.wrap(new byte[16]); bb.putLong(uuid.getMostSignificantBits()); bb.putLong(uuid.getLeastSignificantBits()); return bb.array(); } /** * Converts a long to a byte array * * @param l * the long * @return the byte array representing that long */ public static byte[] longToByteArray( final long l ) { ByteBuffer bb = ByteBuffer.allocate(Long.BYTES); bb.putLong(l); return bb.array(); } /** * Converts a byte array to a long * * @param bytes * the byte array the long * @return the long represented by the byte array */ public static long byteArrayToLong( byte[] bytes ) { ByteBuffer bb = ByteBuffer.allocate(Long.BYTES); bb.put(bytes); bb.flip(); return bb.getLong(); } /** * Combines two variable length byte arrays into one large byte array and * appends the length of each individual byte array in sequential order at * the end of the combined byte array. * * Given byte_array_1 of length 8 + byte_array_2 of length 16, the result * will be byte_array1 + byte_array_2 + 8 + 16. * * Lengths are put after the individual arrays so they don't impact sorting * when used within the key of a sorted key-value data store. * * @param array1 * the first byte array * @param array2 * the second byte array * @return the combined byte array including the individual byte array * lengths */ public static byte[] combineVariableLengthArrays( final byte[] array1, final byte[] array2 ) { Preconditions.checkNotNull( array1, "First byte array cannot be null"); Preconditions.checkNotNull( array2, "Second byte array cannot be null"); Preconditions.checkArgument( array1.length > 1, "First byte array cannot have length 0"); Preconditions.checkArgument( array2.length > 1, "Second byte array cannot have length 0"); final byte[] combinedWithoutLengths = ByteArrayUtils.internalCombineArrays( array1, array2); final ByteBuffer combinedWithLengthsAppended = ByteBuffer.allocate(combinedWithoutLengths.length + 8); // 8 // for // two // integer // lengths combinedWithLengthsAppended.put(combinedWithoutLengths); combinedWithLengthsAppended.putInt(array1.length); combinedWithLengthsAppended.putInt(array2.length); return combinedWithLengthsAppended.array(); } public static Pair<byte[], byte[]> splitVariableLengthArrays( final byte[] combinedArray ) { final ByteBuffer combined = ByteBuffer.wrap(combinedArray); final byte[] combinedArrays = new byte[combinedArray.length - 8]; combined.get(combinedArrays); final ByteBuffer bb = ByteBuffer.wrap(combinedArrays); final int len1 = combined.getInt(); final int len2 = combined.getInt(); final byte[] part1 = new byte[len1]; final byte[] part2 = new byte[len2]; bb.get(part1); bb.get(part2); return Pair.of( part1, part2); } }