blob: d2bbf612e16a566c025ac2de5502b9c362ce3b53 [file] [log] [blame]
/*
* Copyright (C) 2010-2012 The Async HBase Authors. All rights reserved.
* This file is part of Async HBase.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the StumbleUpon nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.hbase.async;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import com.google.protobuf.ByteString;
import com.google.protobuf.ZeroCopyLiteralByteString;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.util.CharsetUtil;
/**
* Helper functions to manipulate byte arrays.
*/
public final class Bytes {
private Bytes() { // Can't instantiate.
}
// ------------------------------ //
// Byte array conversion utilies. //
// ------------------------------ //
/**
* Reads a big-endian 2-byte short from the begining of the given array.
* @param b The array to read from.
* @return A short integer.
* @throws IndexOutOfBoundsException if the byte array is too small.
*/
public static short getShort(final byte[] b) {
return getShort(b, 0);
}
/**
* Reads a big-endian 2-byte short from an offset in the given array.
* @param b The array to read from.
* @param offset The offset in the array to start reading from.
* @return A short integer.
* @throws IndexOutOfBoundsException if the byte array is too small.
*/
public static short getShort(final byte[] b, final int offset) {
return (short) (b[offset] << 8 | b[offset + 1] & 0xFF);
}
/**
* Reads a big-endian 2-byte unsigned short from the begining of the
* given array.
* @param b The array to read from.
* @return A positive short integer.
* @throws IndexOutOfBoundsException if the byte array is too small.
*/
public static int getUnsignedShort(final byte[] b) {
return getUnsignedShort(b, 0);
}
/**
* Reads a big-endian 2-byte unsigned short from an offset in the
* given array.
* @param b The array to read from.
* @param offset The offset in the array to start reading from.
* @return A positive short integer.
* @throws IndexOutOfBoundsException if the byte array is too small.
*/
public static int getUnsignedShort(final byte[] b, final int offset) {
return getShort(b, offset) & 0x0000FFFF;
}
/**
* Writes a big-endian 2-byte short at the begining of the given array.
* @param b The array to write to.
* @param n A short integer.
* @throws IndexOutOfBoundsException if the byte array is too small.
*/
public static void setShort(final byte[] b, final short n) {
setShort(b, n, 0);
}
/**
* Writes a big-endian 2-byte short at an offset in the given array.
* @param b The array to write to.
* @param offset The offset in the array to start writing at.
* @throws IndexOutOfBoundsException if the byte array is too small.
*/
public static void setShort(final byte[] b, final short n,
final int offset) {
b[offset + 0] = (byte) (n >>> 8);
b[offset + 1] = (byte) (n >>> 0);
}
/**
* Creates a new byte array containing a big-endian 2-byte short integer.
* @param n A short integer.
* @return A new byte array containing the given value.
*/
public static byte[] fromShort(final short n) {
final byte[] b = new byte[2];
setShort(b, n);
return b;
}
/**
* Reads a big-endian 4-byte integer from the begining of the given array.
* @param b The array to read from.
* @return An integer.
* @throws IndexOutOfBoundsException if the byte array is too small.
*/
public static int getInt(final byte[] b) {
return getInt(b, 0);
}
/**
* Reads a big-endian 4-byte integer from an offset in the given array.
* @param b The array to read from.
* @param offset The offset in the array to start reading from.
* @return An integer.
* @throws IndexOutOfBoundsException if the byte array is too small.
*/
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;
}
/**
* Reads a big-endian 4-byte unsigned integer from the begining of the
* given array.
* @param b The array to read from.
* @return A positive integer.
* @throws IndexOutOfBoundsException if the byte array is too small.
*/
public static long getUnsignedInt(final byte[] b) {
return getUnsignedInt(b, 0);
}
/**
* Reads a big-endian 4-byte unsigned integer from an offset in the
* given array.
* @param b The array to read from.
* @param offset The offset in the array to start reading from.
* @return A positive integer.
* @throws IndexOutOfBoundsException if the byte array is too small.
*/
public static long getUnsignedInt(final byte[] b, final int offset) {
return getInt(b, offset) & 0x00000000FFFFFFFFL;
}
/**
* Writes a big-endian 4-byte int at the begining of the given array.
* @param b The array to write to.
* @param n An integer.
* @throws IndexOutOfBoundsException if the byte array is too small.
*/
public static void setInt(final byte[] b, final int n) {
setInt(b, n, 0);
}
/**
* Writes a big-endian 4-byte int at an offset in the given array.
* @param b The array to write to.
* @param offset The offset in the array to start writing at.
* @throws IndexOutOfBoundsException if the byte array is too small.
*/
public static void setInt(final byte[] b, final int n, final int offset) {
b[offset + 0] = (byte) (n >>> 24);
b[offset + 1] = (byte) (n >>> 16);
b[offset + 2] = (byte) (n >>> 8);
b[offset + 3] = (byte) (n >>> 0);
}
/**
* Creates a new byte array containing a big-endian 4-byte integer.
* @param n An integer.
* @return A new byte array containing the given value.
*/
public static byte[] fromInt(final int n) {
final byte[] b = new byte[4];
setInt(b, n);
return b;
}
/**
* Reads a big-endian 8-byte long from the begining of the given array.
* @param b The array to read from.
* @return A long integer.
* @throws IndexOutOfBoundsException if the byte array is too small.
*/
public static long getLong(final byte[] b) {
return getLong(b, 0);
}
/**
* Reads a big-endian 8-byte long from an offset in the given array.
* @param b The array to read from.
* @param offset The offset in the array to start reading from.
* @return A long integer.
* @throws IndexOutOfBoundsException if the byte array is too small.
*/
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;
}
/**
* Writes a big-endian 8-byte long at the begining of the given array.
* @param b The array to write to.
* @param n A long integer.
* @throws IndexOutOfBoundsException if the byte array is too small.
*/
public static void setLong(final byte[] b, final long n) {
setLong(b, n, 0);
}
/**
* Writes a big-endian 8-byte long at an offset in the given array.
* @param b The array to write to.
* @param offset The offset in the array to start writing at.
* @throws IndexOutOfBoundsException if the byte array is too small.
*/
public static void setLong(final byte[] b, final long n, final int offset) {
b[offset + 0] = (byte) (n >>> 56);
b[offset + 1] = (byte) (n >>> 48);
b[offset + 2] = (byte) (n >>> 40);
b[offset + 3] = (byte) (n >>> 32);
b[offset + 4] = (byte) (n >>> 24);
b[offset + 5] = (byte) (n >>> 16);
b[offset + 6] = (byte) (n >>> 8);
b[offset + 7] = (byte) (n >>> 0);
}
/**
* Creates a new byte array containing a big-endian 8-byte long integer.
* @param n A long integer.
* @return A new byte array containing the given value.
*/
public static byte[] fromLong(final long n) {
final byte[] b = new byte[8];
setLong(b, n);
return b;
}
/**
* Wraps a byte array in a {@link ByteString} without copying it.
* @param array A byte array that must be considered read-only from there on.
* @since 1.5
*/
public static ByteString wrap(final byte[] array) {
return ZeroCopyLiteralByteString.wrap(array);
}
/**
* Extracts the byte array from the given {@link ByteString} without copy.
* @param buf A buffer from which to extract the array. This buffer must be
* actually an instance of a {@code LiteralByteString}.
* @since 1.5
*/
public static byte[] get(final ByteString buf) {
return ZeroCopyLiteralByteString.zeroCopyGetBytes(buf);
}
/** Transforms a string into an UTF-8 encoded byte array. */
public static byte[] UTF8(final String s) {
return s.getBytes(CharsetUtil.UTF_8);
}
/** Transforms a string into an ISO-8859-1 encoded byte array. */
public static byte[] ISO88591(final String s) {
return s.getBytes(CharsetUtil.ISO_8859_1);
}
// ---------------------------- //
// Pretty-printing byte arrays. //
// ---------------------------- //
private static final byte[] HEX = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F'
};
/**
* Pretty-prints a byte array into a human-readable output buffer.
* @param outbuf The buffer where to write the output.
* @param array The (possibly {@code null}) array to pretty-print.
*/
public static void pretty(final StringBuilder outbuf, final byte[] array) {
if (array == null) {
outbuf.append("null");
return;
}
int ascii = 0;
final int start_length = outbuf.length();
final int n = array.length;
outbuf.ensureCapacity(start_length + 1 + n + 1);
outbuf.append('"');
for (int i = 0; i < n; i++) {
final byte b = array[i];
if (' ' <= b && b <= '~') {
ascii++;
outbuf.append((char) b);
} else if (b == '\n') {
outbuf.append('\\').append('n');
} else if (b == '\t') {
outbuf.append('\\').append('t');
} else {
outbuf.append("\\x")
.append((char) HEX[(b >>> 4) & 0x0F])
.append((char) HEX[b & 0x0F]);
}
}
if (ascii < n / 2) {
outbuf.setLength(start_length);
outbuf.append(Arrays.toString(array));
} else {
outbuf.append('"');
}
}
/**
* Pretty-prints an array of byte arrays into a human-readable output buffer.
* @param outbuf The buffer where to write the output.
* @param arrays The (possibly {@code null}) array of arrays to pretty-print.
* @since 1.3
*/
public static void pretty(final StringBuilder outbuf, final byte[][] arrays) {
if (arrays == null) {
outbuf.append("null");
return;
} else { // Do some right-sizing.
int size = 2;
for (int i = 0; i < arrays.length; i++) {
size += 2 + 2 + arrays[i].length;
}
outbuf.ensureCapacity(outbuf.length() + size);
}
outbuf.append('[');
for (int i = 0; i < arrays.length; i++) {
Bytes.pretty(outbuf, arrays[i]);
outbuf.append(", ");
}
outbuf.setLength(outbuf.length() - 2); // Remove the last ", "
outbuf.append(']');
}
/**
* Pretty-prints a byte array into a human-readable string.
* @param array The (possibly {@code null}) array to pretty-print.
* @return The array in a pretty-printed string.
*/
public static String pretty(final byte[] array) {
if (array == null) {
return "null";
}
final StringBuilder buf = new StringBuilder(1 + array.length + 1);
pretty(buf, array);
return buf.toString();
}
// This doesn't really belong here but it doesn't belong anywhere else
// either, so let's put it close to the other pretty-printing functions.
/**
* Pretty-prints a {@code long} into a fixed-width hexadecimal number.
* @return A string of the form {@code 0x0123456789ABCDEF}.
*/
public static String hex(long v) {
final byte[] buf = new byte[2 + 16];
buf[0] = '0';
buf[1] = 'x';
int i = 2 + 16;
do {
buf[--i] = HEX[(int) v & 0x0F];
v >>>= 4;
} while (v != 0);
for (/**/; i > 1; i--) {
buf[i] = '0';
}
return new String(buf);
}
// Ugly stuff
// ----------
// Background: when using ReplayingDecoder (which makes it easy to deal with
// unframed RPC responses), the ChannelBuffer we manipulate is in fact a
// ReplayingDecoderBuffer, a package-private class that Netty uses. This
// class, for some reason, throws UnsupportedOperationException on its
// array() method. This method is unfortunately the only way to easily dump
// the contents of a ChannelBuffer, which is useful for debugging or logging
// unexpected buffers. An issue (NETTY-346) has been filed to get access to
// the buffer, but the resolution was useless: instead of making the array()
// method work, a new internalBuffer() method was added on ReplayingDecoder,
// which would require that we keep a reference on the ReplayingDecoder all
// along in order to properly convert the buffer to a string.
// So we instead use ugly reflection to gain access to the underlying buffer
// while taking into account that the implementation of Netty has changed
// over time, so depending which version of Netty we're working with, we do
// a different hack. Yes this is horrible, but it's for the greater good as
// this is what allows us to debug unexpected buffers when deserializing RPCs
// and what's more important than being able to debug unexpected stuff?
private static final Class<?> ReplayingDecoderBuffer;
private static final Field RDB_buffer; // For Netty 3.5.0 and before.
private static final Method RDB_buf; // For Netty 3.5.1 and above.
static {
try {
ReplayingDecoderBuffer = Class.forName("org.jboss.netty.handler.codec."
+ "replay.ReplayingDecoderBuffer");
Field field = null;
try {
field = ReplayingDecoderBuffer.getDeclaredField("buffer");
field.setAccessible(true);
} catch (NoSuchFieldException e) {
// Ignore. Field has been removed in Netty 3.5.1.
}
RDB_buffer = field;
if (field != null) { // Netty 3.5.0 or before.
RDB_buf = null;
} else {
RDB_buf = ReplayingDecoderBuffer.getDeclaredMethod("buf");
RDB_buf.setAccessible(true);
}
} catch (Exception e) {
throw new RuntimeException("static initializer failed", e);
}
}
/**
* Pretty-prints all the bytes of a buffer into a human-readable string.
* @param buf The (possibly {@code null}) buffer to pretty-print.
* @return The buffer in a pretty-printed string.
*/
public static String pretty(final ChannelBuffer buf) {
if (buf == null) {
return "null";
}
byte[] array;
try {
if (buf.getClass() != ReplayingDecoderBuffer) {
array = buf.array();
} else if (RDB_buf != null) { // Netty 3.5.1 and above.
array = ((ChannelBuffer) RDB_buf.invoke(buf)).array();
} else { // Netty 3.5.0 and before.
final ChannelBuffer wrapped_buf = (ChannelBuffer) RDB_buffer.get(buf);
array = wrapped_buf.array();
}
} catch (UnsupportedOperationException e) {
return "(failed to extract content of buffer of type "
+ buf.getClass().getName() + ')';
} catch (IllegalAccessException e) {
throw new AssertionError("Should not happen: " + e);
} catch (InvocationTargetException e) {
throw new AssertionError("Should not happen: " + e);
}
return pretty(array);
}
// ---------------------- //
// Comparing byte arrays. //
// ---------------------- //
// Don't ask me why this isn't in java.util.Arrays.
/**
* A singleton {@link Comparator} for non-{@code null} byte arrays.
* @see #memcmp
*/
public static final MemCmp MEMCMP = new MemCmp();
/** {@link Comparator} for non-{@code null} byte arrays. */
private final static class MemCmp implements Comparator<byte[]> {
private MemCmp() { // Can't instantiate outside of this class.
}
@Override
public int compare(final byte[] a, final byte[] b) {
return memcmp(a, b);
}
}
/**
* {@code memcmp} in Java, hooray.
* @param a First non-{@code null} byte array to compare.
* @param b Second non-{@code null} byte array to compare.
* @return 0 if the two arrays are identical, otherwise the difference
* between the first two different bytes, otherwise the different between
* their lengths.
*/
public static int memcmp(final byte[] a, final byte[] b) {
final int length = Math.min(a.length, b.length);
if (a == b) { // Do this after accessing a.length and b.length
return 0; // in order to NPE if either a or b is null.
}
for (int i = 0; i < length; i++) {
if (a[i] != b[i]) {
return (a[i] & 0xFF) - (b[i] & 0xFF); // "promote" to unsigned.
}
}
return a.length - b.length;
}
/**
* {@code memcmp(3)} with a given offset and length.
* @param a First non-{@code null} byte array to compare.
* @param b Second non-{@code null} byte array to compare.
* @param offset The offset at which to start comparing both arrays.
* @param length The number of bytes to compare.
* @return 0 if the two arrays are identical, otherwise the difference
* between the first two different bytes (treated as unsigned), otherwise
* the different between their lengths.
* @throws IndexOutOfBoundsException if either array isn't large enough.
*/
public static int memcmp(final byte[] a, final byte[] b,
final int offset, int length) {
if (a == b && a != null) {
return 0;
}
length += offset;
for (int i = offset; i < length; i++) {
if (a[i] != b[i]) {
return (a[i] & 0xFF) - (b[i] & 0xFF); // "promote" to unsigned.
}
}
return 0;
}
/**
* De-duplicates two byte arrays.
* <p>
* If two byte arrays have the same contents but are different, this
* function helps to re-use the old one and discard the new copy.
* @param old The existing byte array.
* @param neww The new byte array we're trying to de-duplicate.
* @return {@code old} if {@code neww} is a different array with the same
* contents, otherwise {@code neww}.
*/
public static byte[] deDup(final byte[] old, final byte[] neww) {
return memcmp(old, neww) == 0 ? old : neww;
}
/**
* Tests whether two byte arrays have the same contents.
* @param a First non-{@code null} byte array to compare.
* @param b Second non-{@code null} byte array to compare.
* @return {@code true} if the two arrays are identical,
* {@code false} otherwise.
*/
public static boolean equals(final byte[] a, final byte[] b) {
return memcmp(a, b) == 0;
}
/**
* {@code memcmp(3)} in Java for possibly {@code null} arrays, hooray.
* @param a First possibly {@code null} byte array to compare.
* @param b Second possibly {@code null} byte array to compare.
* @return 0 if the two arrays are identical (or both are {@code null}),
* otherwise the difference between the first two different bytes (treated
* as unsigned), otherwise the different between their lengths (a {@code
* null} byte array is considered shorter than an empty byte array).
*/
public static int memcmpMaybeNull(final byte[] a, final byte[] b) {
if (a == null) {
if (b == null) {
return 0;
}
return -1;
} else if (b == null) {
return 1;
}
return memcmp(a, b);
}
/** A convenient map keyed with a byte array. */
public static final class ByteMap<V> extends TreeMap<byte[], V>
implements Iterable<Map.Entry<byte[], V>> {
public ByteMap() {
super(MEMCMP);
}
/** Returns an iterator that goes through all the entries in this map. */
public Iterator<Map.Entry<byte[], V>> iterator() {
return super.entrySet().iterator();
}
/** {@code byte[]} friendly implementation. */
public String toString() {
final int size = size();
if (size == 0) {
return "{}";
}
final StringBuilder buf = new StringBuilder(size << 4);
buf.append('{');
for (final Map.Entry<byte[], V> e : this) {
Bytes.pretty(buf, e.getKey());
buf.append('=');
final V value = e.getValue();
if (value instanceof byte[]) {
Bytes.pretty(buf, (byte[]) value);
} else {
buf.append(value == this ? "(this map)" : value);
}
buf.append(", ");
}
buf.setLength(buf.length() - 2); // Remove the extra ", ".
buf.append('}');
return buf.toString();
}
private static final long serialVersionUID = 1280744742;
}
}