/** * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.deephacks.confit.internal.hbase; import com.google.common.primitives.Longs; import com.google.common.primitives.UnsignedBytes; import sun.misc.Unsafe; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; public class BytesUtils { /** * Adds a big-endian 4-byte integer to a sorted array of bytes. * * @param arr This byte array is assumed to be sorted array * of signed ints. * @param n value to add. * @return new array with the added value. */ public static byte[] add(byte[] arr, int n) { int index = binarySearch(arr, n); byte[] arr2 = new byte[arr.length + 4]; System.arraycopy(arr, 0, arr2, 0, index); System.arraycopy(arr, index, arr2, index + 4, arr.length - index); org.deephacks.confit.internal.hbase.Bytes.setInt(arr2, n, index); return arr2; } /** * Remove a big-endian 4-byte integer to a sorted array of bytes. * * @param arr This byte array is assumed to be sorted array * of signed ints. * @param n value to remove. * @return new array with the added value. */ public static byte[] remove(byte[] arr, int n) { int index = binarySearch(arr, n); byte[] arr2 = new byte[arr.length - 4]; System.arraycopy(arr, 0, arr2, 0, index); System.arraycopy(arr, index + 4, arr2, index, arr.length - index - 4); return arr2; } /** * Search for a big-endian 4-byte integer in a array of bytes. * * @param a array of containing only big-endian 4-byte integers. * @param key the value to seach for. * @return the index found. */ public static int binarySearch(byte[] a, int key) { int low = 0; int high = a.length; while (low < high) { int mid = (low + high) >>> 1; if (mid % 4 != 0) { if (high == a.length) { mid = low; } else { mid = high; } } int midVal = getInt(a, mid); if (midVal < key) low = mid + 4; else if (midVal > key) high = mid - 4; else return mid; // key found } if (low == a.length) { return low; } return key > getInt(a, low) ? low + 4 : low; } public static void write(DataOutput out, Object value) { try { if (value instanceof Byte) { out.write(DataType.BYTE.getId()); out.write((byte) value); } else if (value instanceof Short) { out.write(DataType.SHORT.getId()); out.writeShort((short) value); } else if (value instanceof Integer) { out.write(DataType.INTEGER.getId()); out.writeInt((int) value); } else if (value instanceof Long) { out.write(DataType.LONG.getId()); out.writeLong((long) value); } else if (value instanceof Float) { out.write(DataType.FLOAT.getId()); out.writeFloat((float) value); } else if (value instanceof Double) { out.write(DataType.DOUBLE.getId()); out.writeDouble((double) value); } else if (value instanceof Boolean) { out.write(DataType.BOOLEAN.getId()); out.writeBoolean((boolean) value); } else if (value instanceof String) { out.write(DataType.STRING.getId()); out.writeUTF((String) value); } else { throw new UnsupportedOperationException("Did not recognize type " + value.getClass()); } } catch (IOException e) { throw new IllegalArgumentException(e); } } public static Object read(DataInput in) { try { DataType type = DataType.getDataType(in.readByte()); switch (type) { case BYTE: return in.readByte(); case SHORT: return in.readShort(); case INTEGER: return in.readInt(); case LONG: return in.readLong(); case FLOAT: return in.readFloat(); case DOUBLE: return in.readDouble(); case BOOLEAN: return in.readBoolean(); case STRING: return in.readUTF(); case BYTE_LIST: throw new UnsupportedOperationException("Did not recognize type " + type); case SHORT_LIST: throw new UnsupportedOperationException("Did not recognize type " + type); case INTEGER_LIST: throw new UnsupportedOperationException("Did not recognize type " + type); case LONG_LIST: throw new UnsupportedOperationException("Did not recognize type " + type); case FLOAT_LIST: throw new UnsupportedOperationException("Did not recognize type " + type); case DOUBLE_LIST: throw new UnsupportedOperationException("Did not recognize type " + type); case BOOLEAN_LIST: throw new UnsupportedOperationException("Did not recognize type " + type); case STRING_LIST: throw new UnsupportedOperationException("Did not recognize type " + type); } } catch (IOException e) { throw new IllegalArgumentException(e); } throw new UnsupportedOperationException("Did not recognize type"); } public static byte[] toBytes(long[] values) { ByteBuffer buf = ByteBuffer.allocate(values.length * 8); for (int i = 0; i < values.length; i++){ buf.putLong(i * 8, values[i]); } return buf.array(); } public static byte[] toBytes(int[] values) { ByteBuffer buf = ByteBuffer.allocate(values.length * 4); for (int i = 0; i < values.length; i++){ buf.putInt(i * 4, values[i]); } return buf.array(); } public static byte[] toBytes(short[] values) { ByteBuffer buf = ByteBuffer.allocate(values.length * 2); for (int i = 0; i < values.length; i++){ buf.putShort(i * 2, values[i]); } return buf.array(); } public static byte[] toBytes(byte[] values) { ByteBuffer buf = ByteBuffer.allocate(values.length); for (int i = 0; i < values.length; i++){ buf.put(i, values[i]); } return buf.array(); } public static byte[] toBytes(boolean[] values) { ByteBuffer buf = ByteBuffer.allocate(values.length); for (int i = 0; i < values.length; i++){ byte val = values[i] ? (byte) 1 : (byte) 0; buf.put(i, val); } return buf.array(); } public static byte[] toBytes(float[] values) { ByteBuffer buf = ByteBuffer.allocate(values.length * 4); for (int i = 0; i < values.length; i++){ buf.putFloat(i * 4, values[i]); } return buf.array(); } public static byte[] toBytes(double[] values) { ByteBuffer buf = ByteBuffer.allocate(values.length * 8); for (int i = 0; i < values.length; i++){ buf.putDouble(i * 8, values[i]); } return buf.array(); } public static byte[] toBytes(String[] strings) { List<byte[]> stringBytes = new ArrayList<>(); int size = 0; for (String str : strings) { byte[] bytes = str.getBytes(); ByteBuffer buffer = ByteBuffer.allocate(bytes.length + 4); buffer.putInt(bytes.length); buffer.put(bytes); stringBytes.add(buffer.array()); size += 4 + bytes.length; } ByteBuffer buffer = ByteBuffer.allocate(size + 4); buffer.putInt(strings.length); for (byte[] bytes : stringBytes) { buffer.put(bytes); } return buffer.array(); } public static byte[] toBytes(Reference reference) { byte[] bytes = reference.getInstance().getBytes(); ByteBuffer buffer = ByteBuffer.allocate(bytes.length + 4 + 4); buffer.putInt(reference.getSid()); buffer.putInt(bytes.length); buffer.put(bytes); return buffer.array(); } public static byte[] toBytes(ReferenceList reference) { List<byte[]> stringBytes = new ArrayList<>(); int size = 0; for (String str : reference.getInstances()) { byte[] bytes = str.getBytes(); ByteBuffer buffer = ByteBuffer.allocate(bytes.length + 4); buffer.putInt(bytes.length); buffer.put(bytes); stringBytes.add(buffer.array()); size += 4 + bytes.length; } ByteBuffer buffer = ByteBuffer.allocate(size + 4 + 4); buffer.putInt(reference.getInstances().size()); buffer.putInt(reference.getSid()); for (byte[] bytes : stringBytes) { buffer.put(bytes); } return buffer.array(); } public static Reference toReference(byte[] value, int offset) { Reference reference = new Reference(getInt(value, offset)); int size = getInt(value, offset); byte[] bytes = new byte[size]; System.arraycopy(value, offset + 4, bytes, 0, size); reference.setInstance(new String(bytes)); return reference; } public static ReferenceList toReferences(byte[] value, int offset) { int length = getInt(value, offset); return toReferences(value, offset + 4, length); } public static ReferenceList toReferences(byte[] value, int offset, int length) { ReferenceList reference = new ReferenceList(getInt(value, offset)); offset += 4; for (int i = 0; i < length; i++) { int size = getInt(value, offset); byte[] bytes = new byte[size]; System.arraycopy(value, offset + 4, bytes, 0, size); reference.addInstance(new String(bytes)); offset += 4 + size; } return reference; } public static boolean[] toBooleans(byte[] value, int offset, int num) { boolean[] values = new boolean[num]; int idx = 0; for (int i = offset; i < num + offset; i++) { values[idx++] = getBoolean(value[i]); } return values; } public static boolean[] toBooleans(byte[] value, int offset) { int length = getInt(value, offset); return toBooleans(value, offset + 4, length); } public static List<Boolean> toBooleanList(byte[] value, int offset) { int length = getInt(value, offset); boolean[] values = toBooleans(value, offset + 4, length); ArrayList<Boolean> list = new ArrayList<>(); for (Boolean v : values) { list.add(v); } return list; } public static long[] toLongs(byte[] value, int offset, int num) { long[] values = new long[num]; int idx = 0; for (int i = offset; i < offset + (num * 8); i += 8) { values[idx++] = getLong(value, i); } return values; } public static long[] toLongs(byte[] value, int offset) { int length = getInt(value, offset); return toLongs(value, offset + 4, length); } public static List<Long> toLongList(byte[] value, int offset) { int length = getInt(value, offset); long[] values = toLongs(value, offset + 4, length); ArrayList<Long> list = new ArrayList<>(); for (Long v : values) { list.add(v); } return list; } public static int[] toInts(byte[] value, int offset, int num) { int[] values = new int[num]; int idx = 0; for (int i = offset; i < offset + (num * 4); i += 4) { values[idx++] = getInt(value, i); } return values; } public static int[] toInts(byte[] value, int offset) { int length = getInt(value, offset); return toInts(value, offset + 4, length); } public static List<Integer> toIntList(byte[] value, int offset) { int length = getInt(value, offset); int[] values = toInts(value, offset + 4, length); ArrayList<Integer> list = new ArrayList<>(); for (Integer v : values) { list.add(v); } return list; } public static short[] toShorts(byte[] value, int offset, int num) { short[] values = new short[num]; int idx = 0; for (int i = offset; i < offset + (num * 2); i += 2) { values[idx++] = getShort(value, i); } return values; } public static short[] toShorts(byte[] value, int offset) { int length = getInt(value, offset); return toShorts(value, offset + 4, length); } public static List<Short> toShortList(byte[] value, int offset) { int length = getInt(value, offset); short[] values = toShorts(value, offset + 4, length); ArrayList<Short> list = new ArrayList<>(); for (short v : values) { list.add(v); } return list; } public static byte[] toBytes(byte[] value, int offset, int num) { byte[] values = new byte[num]; int idx = 0; for (int i = offset; i < offset + num; i++) { values[idx++] = value[i]; } return values; } public static byte[] toBytes(byte[] value, int offset) { int length = getInt(value, offset); return toBytes(value, offset + 4, length); } public static List<Byte> toByteList(byte[] value, int offset) { int length = getInt(value, offset); byte[] bytes = toBytes(value, offset + 4, length); ArrayList<Byte> list = new ArrayList<>(); for (byte v : bytes) { list.add(v); } return list; } public static double[] toDoubles(byte[] value, int offset, int num) { double[] values = new double[num]; int idx = 0; for (int i = offset; i < offset + (num * 8); i += 8) { values[idx++] = getDouble(value, i); } return values; } public static double[] toDoubles(byte[] value, int offset) { int length = getInt(value, offset); return toDoubles(value, offset + 4, length); } public static List<Double> toDoubleList(byte[] value, int offset) { int length = getInt(value, offset); double[] values = toDoubles(value, offset + 4, length); ArrayList<Double> list = new ArrayList<>(); for (double v : values) { list.add(v); } return list; } public static float[] toFloats(byte[] value, int offset, int num) { float[] values = new float[num]; int idx = 0; for (int i = offset; i < offset + (num * 4); i += 4) { values[idx++] = getFloat(value, i); } return values; } public static float[] toFloats(byte[] value, int offset) { int length = getInt(value, offset); return toFloats(value, offset + 4, length); } public static List<Float> toFloatList(byte[] value, int offset) { int length = getInt(value, offset); float[] values = toFloats(value, offset + 4, length); ArrayList<Float> list = new ArrayList<>(); for (float v : values) { list.add(v); } return list; } public static String[] toStrings(byte[] value, int offset, int length) { ArrayList<String> values = new ArrayList<>(); for (int i = 0; i < length; i++) { int size = getInt(value, offset); byte[] bytes = new byte[size]; System.arraycopy(value, offset + 4, bytes, 0, size); values.add(new String(bytes)); offset += 4 + size; } return values.toArray(new String[values.size()]); } public static String[] toStrings(byte[] value, int offset) { int length = getInt(value, offset); return toStrings(value, offset + 4, length); } public static List<String> toStringList(byte[] value, int offset) { int length = getInt(value, offset); String[] values = toStrings(value, offset + 4, length); ArrayList<String> list = new ArrayList<>(); Collections.addAll(list, values); return list; } public static long getLong(final byte[] b, final int offset) { return (b[offset + 0] & 0xFFL) << 56 | (b[offset + 1] & 0xFFL) << 48 | (b[offset + 2] & 0xFFL) << 40 | (b[offset + 3] & 0xFFL) << 32 | (b[offset + 4] & 0xFFL) << 24 | (b[offset + 5] & 0xFFL) << 16 | (b[offset + 6] & 0xFFL) << 8 | (b[offset + 7] & 0xFFL) << 0; } public static int getInt(final byte[] b, final int offset) { return (b[offset + 0] & 0xFF) << 24 | (b[offset + 1] & 0xFF) << 16 | (b[offset + 2] & 0xFF) << 8 | (b[offset + 3] & 0xFF) << 0; } public static short getShort(final byte[] b, final int offset) { return (short) (b[offset] << 8 | b[offset + 1] & 0xFF); } public static float getFloat(final byte[] b, final int offset) { return Float.intBitsToFloat(getInt(b, offset)); } public static double getDouble(final byte[] b, final int offset) { return Double.longBitsToDouble(getLong(b, offset)); } public static String getString(final byte[] b, final int offset) { int length = getInt(b, offset); byte[] bytes = new byte[length]; System.arraycopy(b, offset + 4, bytes, 0, length); return new String(bytes); } private static boolean getBoolean(byte b) { return b != 0; } static final Unsafe theUnsafe; /** The offset to the first element in a byte array. */ static final int BYTE_ARRAY_BASE_OFFSET; static { theUnsafe = (Unsafe) AccessController.doPrivileged( new PrivilegedAction<Object>() { @Override public Object run() { try { Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); return f.get(null); } catch (NoSuchFieldException e) { // It doesn't matter what we throw; // it's swallowed in getBestComparer(). throw new Error(); } catch (IllegalAccessException e) { throw new Error(); } } }); BYTE_ARRAY_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class); // sanity check - this should never fail if (theUnsafe.arrayIndexScale(byte[].class) != 1) { throw new AssertionError(); } } /** * Lexicographically compare two arrays. * * @param buffer1 left operand * @param buffer2 right operand * @param offset1 Where to start comparing in the left buffer * @param offset2 Where to start comparing in the right buffer * @param length1 How much to compare from the left buffer * @param length2 How much to compare from the right buffer * @return 0 if equal, < 0 if left is less than right, etc. */ public static int compareTo(byte[] buffer1, int offset1, int length1, byte[] buffer2, int offset2, int length2) { // Short circuit equal case if (buffer1 == buffer2 && offset1 == offset2 && length1 == length2) { return 0; } int minLength = Math.min(length1, length2); int minWords = minLength / Longs.BYTES; int offset1Adj = offset1 + BYTE_ARRAY_BASE_OFFSET; int offset2Adj = offset2 + BYTE_ARRAY_BASE_OFFSET; /* * Compare 8 bytes at a time. Benchmarking shows comparing 8 bytes at a * time is no slower than comparing 4 bytes at a time even on 32-bit. * On the other hand, it is substantially faster on 64-bit. */ for (int i = 0; i < minWords * Longs.BYTES; i += Longs.BYTES) { long lw = theUnsafe.getLong(buffer1, offset1Adj + (long) i); long rw = theUnsafe.getLong(buffer2, offset2Adj + (long) i); long diff = lw ^ rw; if (diff != 0) { if (!littleEndian) { return lessThanUnsigned(lw, rw) ? -1 : 1; } // Use binary search int n = 0; int y; int x = (int) diff; if (x == 0) { x = (int) (diff >>> 32); n = 32; } y = x << 16; if (y == 0) { n += 16; } else { x = y; } y = x << 8; if (y == 0) { n += 8; } return (int) (((lw >>> n) & 0xFFL) - ((rw >>> n) & 0xFFL)); } } // The epilogue to cover the last (minLength % 8) elements. for (int i = minWords * Longs.BYTES; i < minLength; i++) { int result = UnsignedBytes.compare( buffer1[offset1 + i], buffer2[offset2 + i]); if (result != 0) { return result; } } return length1 - length2; } static final boolean littleEndian = ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN); /** * Returns true if x1 is less than x2, when both values are treated as * unsigned. */ static boolean lessThanUnsigned(long x1, long x2) { return (x1 + Long.MIN_VALUE) < (x2 + Long.MIN_VALUE); } public static enum DataType { BYTE(1), SHORT(2), INTEGER(3), LONG(4), FLOAT(5), DOUBLE(6), BOOLEAN(7), STRING(8), REFERENCE(9), BYTE_LIST(11), SHORT_LIST(12), INTEGER_LIST(13), LONG_LIST(14), FLOAT_LIST(15), DOUBLE_LIST(16), BOOLEAN_LIST(17), STRING_LIST(18), REFERENCE_LIST(19); private int id; DataType(int id) { this.id = id; } private static final Map<Byte, DataType> idToEnumMap = new HashMap<>(); static { for (DataType type : DataType.values()) { idToEnumMap.put(type.getId(), type); } } public byte getId() { return (byte) id; } public static DataType getDataType(byte id) { return idToEnumMap.get(id); } public static DataType getDataType(Class<?> cls) { if(Byte.class.isAssignableFrom(cls)) { return BYTE; } else if(byte.class.isAssignableFrom(cls)) { return BYTE; } else if (Short.class.isAssignableFrom(cls)) { return SHORT; } else if (short.class.isAssignableFrom(cls)) { return SHORT; } else if (Integer.class.isAssignableFrom(cls)) { return INTEGER; } else if (int.class.isAssignableFrom(cls)) { return INTEGER; } else if (Long.class.isAssignableFrom(cls)) { return LONG; } else if (long.class.isAssignableFrom(cls)) { return LONG; } else if (Float.class.isAssignableFrom(cls)) { return FLOAT; } else if (float.class.isAssignableFrom(cls)) { return FLOAT; } else if (Double.class.isAssignableFrom(cls)) { return DOUBLE; } else if (double.class.isAssignableFrom(cls)) { return DOUBLE; } else if (Boolean.class.isAssignableFrom(cls)) { return BOOLEAN; } else if (boolean.class.isAssignableFrom(cls)) { return BOOLEAN; } else if (String.class.isAssignableFrom(cls)) { return STRING; } else if (Reference.class.isAssignableFrom(cls)) { return REFERENCE; } else if (ReferenceList.class.isAssignableFrom(cls)) { return REFERENCE_LIST; } else { throw new UnsupportedOperationException("Did not recognize " + cls); } } } public static class ReferenceList { private int sid; private ArrayList<String> instances = new ArrayList<>(); public ReferenceList(int sid) { this.sid = sid; } public int getSid() { return sid; } public void addInstance(String instance) { instances.add(instance); } public List<String> getInstances() { return instances; } } public static class Reference { private int sid; private String instance; public Reference(int sid) { this.sid = sid; } public Reference(int sid, String instance) { this.sid = sid; this.instance = instance; } public int getSid() { return sid; } public void setInstance(String instance) { this.instance = instance; } public String getInstance() { return instance; } } }