| /* |
| * 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 beginning 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 beginning 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 beginning 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 beginning 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 beginning 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 beginning 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 beginning 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 beginning 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; |
| |
| } |
| |
| } |