Bytes.java

/*
 * Copyright © 2014 - 2021 Leipzig University (Database Research Group)
 *
 * 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.gradoop.common.model.impl.properties.bytes;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Comparator;

/**
 * Utility class that handles byte arrays, conversions to/from other types,
 * comparisons, hash code generation, manufacturing keys for HashMaps or HashSets, etc.
 *
 * This implementation reuses much of the code of HBase's Bytes (org.apache.hadoop.hbase.util.Bytes).
 * This can be found in org.apache.hbase:hbase-common
 * Much of the code is copied directly or has only small changes.
 */
public class Bytes implements Comparable<Bytes> {

  /**
   * Size of boolean in bytes
   */
  public static final int SIZEOF_BOOLEAN = Byte.SIZE / Byte.SIZE;

  /**
   * Size of byte in bytes
   */
  public static final int SIZEOF_BYTE = SIZEOF_BOOLEAN;

  /**
   * Size of char in bytes
   */
  public static final int SIZEOF_CHAR = Character.SIZE / Byte.SIZE;

  /**
   * Size of double in bytes
   */
  public static final int SIZEOF_DOUBLE = Double.SIZE / Byte.SIZE;

  /**
   * Size of float in bytes
   */
  public static final int SIZEOF_FLOAT = Float.SIZE / Byte.SIZE;

  /**
   * Size of int in bytes
   */
  public static final int SIZEOF_INT = Integer.SIZE / Byte.SIZE;

  /**
   * Size of long in bytes
   */
  public static final int SIZEOF_LONG = Long.SIZE / Byte.SIZE;

  /**
   * Size of short in bytes
   */
  public static final int SIZEOF_SHORT = Short.SIZE / Byte.SIZE;

  /**
   * true, if unsafe is available
   */
  private static final boolean UNSAFE_UNALIGNED = UnsafeAvailChecker.unaligned();

  /**
   * Logger
   */
  private static final Logger LOG = LoggerFactory.getLogger(Bytes.class);

  /**
   * Byte array
   */
  private byte[] bytes;

  /**
   * offset
   */
  private int offset;

  /**
   * length
   */
  private int length;

  /**
   * Use comparing byte arrays, byte-by-byte
   */
  private final ByteArrayComparator bytesRawcomparator = new ByteArrayComparator();

  /**
   * Create a zero-size sequence.
   */
  public Bytes() {
    super();
  }

  /**
   * Create a Bytes using the byte array as the initial value.
   *
   * @param bytes This array becomes the backing storage for the object.
   */
  public Bytes(byte[] bytes) {
    this(bytes, 0, bytes.length);
  }

  /**
   * Set the new Bytes to the contents of the passed
   * {@code ibw}
   *
   * @param ibw the value to set this Bytes to.
   */
  public Bytes(final Bytes ibw) {
    this(ibw.get(), ibw.getOffset(), ibw.getLength());
  }

  /**
   * Set the value to a given byte range
   *
   * @param bytes the new byte range to set to
   * @param offset the offset in newData to start at
   * @param length the number of bytes in the range
   */
  public Bytes(final byte[] bytes, final int offset,
               final int length) {
    this.bytes = bytes;
    this.offset = offset;
    this.length = length;
  }

  /**
   * Get the data from the Bytes.
   *
   * @return The data is only valid between offset and offset+length.
   */
  public byte[] get() {
    if (this.bytes == null) {
      throw new IllegalStateException("Uninitialiized. Null constructor " +
        "called w/o accompaying readFields invocation");
    }
    return this.bytes;
  }

  /**
   * Set byte array with offset 0 and length of the byte array.
   *
   * @param b Use passed bytes as backing array for this instance.
   */
  public void set(final byte[] b) {
    set(b, 0, b.length);
  }

  /**
   * Set byte array, offset and length.
   *
   * @param b Use passed bytes as backing array for this instance.
   * @param offset offset
   * @param length length
   */
  public void set(final byte[] b, final int offset, final int length) {
    this.bytes = b;
    this.offset = offset;
    this.length = length;
  }

  /**
   * Get length
   *
   * @return the number of valid bytes in the buffer
   */
  public int getLength() {
    if (this.bytes == null) {
      throw new IllegalStateException("Uninitialiized. Null constructor " +
        "called w/o accompaying readFields invocation");
    }
    return this.length;
  }

  /**
   * Get offset
   *
   * @return offset
   */
  public int getOffset() {
    return this.offset;
  }

  @Override
  public int hashCode() {
    return hashCode(bytes, offset, length);
  }

  /**
   * Define the sort order of the Bytes.
   *
   * @param that The other bytes writable
   * @return Positive if left is bigger than right, 0 if they are equal, and
   *         negative if left is smaller than right.
   */
  @Override
  public int compareTo(Bytes that) {
    return bytesRawcomparator.compare(
      this.bytes, this.offset, this.length,
      that.bytes, that.offset, that.length);
  }

  @Override
  public boolean equals(Object that) {
    if (that == this) {
      return true;
    }
    if (that instanceof Bytes) {
      return compareTo((Bytes) that) == 0;
    }
    return false;
  }

  @Override
  public String toString() {
    return toString(bytes, offset, length);
  }

  /**
   * Byte array comparator class.
   */
  public static class ByteArrayComparator implements Comparator<byte []> {
    /**
     * Constructor
     */
    ByteArrayComparator() {
      super();
    }

    @Override
    public int compare(byte [] left, byte [] right) {
      return compareTo(left, right);
    }

    /**
     * Compares two byte arrays with a specified offset and length.
     *
     * @param b1 left byte array
     * @param s1 offset of left byte array
     * @param l1 length of left byte array
     * @param b2 right byte array
     * @param s2 offset of right byte array
     * @param l2 length of right byte array
     * @return 0 if equal, &lt; 0 if left is less than right, etc.
     */
    public int compare(byte [] b1, int s1, int l1, byte [] b2, int s2, int l2) {
      return LexicographicalComparerHolder.BEST_COMPARER.
        compareTo(b1, s1, l1, b2, s2, l2);
    }
  }

  /**
   * Put bytes at the specified byte array position.
   *
   * @param tgtBytes the byte array
   * @param tgtOffset position in the array
   * @param srcBytes array to write out
   * @param srcOffset source offset
   * @param srcLength source length
   * @return incremented offset
   */
  public static int putBytes(byte[] tgtBytes, int tgtOffset, byte[] srcBytes,
                             int srcOffset, int srcLength) {
    System.arraycopy(srcBytes, srcOffset, tgtBytes, tgtOffset, srcLength);
    return tgtOffset + srcLength;
  }

  /**
   * Write a single byte out to the specified byte array position.
   *
   * @param bytes the byte array
   * @param offset position in the array
   * @param b byte to write out
   * @return incremented offset
   */
  public static int putByte(byte[] bytes, int offset, byte b) {
    bytes[offset] = b;
    return offset + 1;
  }

  /**
   * This method will convert utf8 encoded bytes into a string. If the given byte array is null,
   * this method will return null.
   *
   * @param b Presumed UTF-8 encoded byte array.
   * @return String made from <code>b</code>
   */
  public static String toString(final byte [] b) {
    if (b == null) {
      return null;
    }
    return toString(b, 0, b.length);
  }

  /**
   * Joins two byte arrays together using a separator.
   *
   * @param b1 The first byte array.
   * @param sep The separator to use.
   * @param b2 The second byte array.
   * @return input byte arrays as string with a separator between
   */
  public static String toString(final byte [] b1,
                                String sep,
                                final byte [] b2) {
    return toString(b1, 0, b1.length) + sep + toString(b2, 0, b2.length);
  }

  /**
   * This method will convert utf8 encoded bytes into a string. If the given byte array is null,
   * this method will return null.
   *
   * @param b Presumed UTF-8 encoded byte array.
   * @param off offset into array
   * @return String made from <code>b</code> or null
   */
  public static String toString(final byte[] b, int off) {
    if (b == null) {
      return null;
    }
    int len = b.length - off;
    if (len <= 0) {
      return "";
    }
    return new String(b, off, len, StandardCharsets.UTF_8);
  }

  /**
   * This method will convert utf8 encoded bytes into a string. If the given byte array is null,
   * this method will return null.
   *
   * @param b Presumed UTF-8 encoded byte array.
   * @param off offset into array
   * @param len length of utf-8 sequence
   * @return String made from <code>b</code> or null
   */
  public static String toString(final byte [] b, int off, int len) {
    if (b == null) {
      return null;
    }
    if (len == 0) {
      return "";
    }
    return new String(b, off, len, StandardCharsets.UTF_8);
  }

  /**
   * Converts a string to a UTF-8 byte array.
   *
   * @param s string
   * @return the byte array
   */
  public static byte[] toBytes(String s) {
    return s.getBytes(StandardCharsets.UTF_8);
  }

  /**
   * Converts a byte array to a long value.
   *
   * @param bytes array
   * @return the long value
   */
  public static long toLong(byte[] bytes) {
    return toLong(bytes, 0, SIZEOF_LONG);
  }

  /**
   * Converts a byte array to a long value. Assumes there will be {@link #SIZEOF_LONG} bytes available.
   *
   * @param bytes bytes
   * @param offset offset
   * @return the long value
   */
  public static long toLong(byte[] bytes, int offset) {
    return toLong(bytes, offset, SIZEOF_LONG);
  }

  /**
   * Converts a byte array to a long value.
   *
   * @param bytes array of bytes
   * @param offset offset into array
   * @param length length of data (must be {@link #SIZEOF_LONG})
   * @return the long value
   * @throws IllegalArgumentException if length is not {@link #SIZEOF_LONG} or
   * if there's not enough room in the array at the offset indicated.
   */
  public static long toLong(byte[] bytes, int offset, final int length) {
    if (length != SIZEOF_LONG || offset + length > bytes.length) {
      throw explainWrongLengthOrOffset(bytes, offset, length, SIZEOF_LONG);
    }
    if (UNSAFE_UNALIGNED) {
      return toLongUnsafe(bytes, offset);
    } else {
      long l = 0;
      for (int i = offset; i < offset + length; i++) {
        l <<= 8;
        l ^= bytes[i] & 0xFF;
      }
      return l;
    }
  }

  /**
   * Return Exception with fancy error message.
   *
   * @param bytes used byte array
   * @param offset used offset
   * @param length used length
   * @param expectedLength expected length
   * @return IllegalArgumentException with fancy error message
   */
  private static IllegalArgumentException explainWrongLengthOrOffset(final byte[] bytes,
                                                                     final int offset,
                                                                     final int length,
                                                                     final int expectedLength) {
    String reason;
    if (length != expectedLength) {
      reason = "Wrong length: " + length + ", expected " + expectedLength;
    } else {
      reason = "offset (" + offset + ") + length (" + length + ") exceed the capacity of the array: " +
        bytes.length;
    }
    return new IllegalArgumentException(reason);
  }

  /**
   * Put a long value out to the specified byte array position.
   *
   * @param bytes the byte array
   * @param offset position in the array
   * @param val long to write out
   * @return incremented offset
   * @throws IllegalArgumentException if the byte array given doesn't have
   * enough room at the offset specified.
   */
  public static int putLong(byte[] bytes, int offset, long val) {
    if (bytes.length - offset < SIZEOF_LONG) {
      throw new IllegalArgumentException("Not enough room to put a long at" + " offset " + offset +
        " in a " + bytes.length + " byte array");
    }
    if (UNSAFE_UNALIGNED) {
      return putLongUnsafe(bytes, offset, val);
    } else {
      for (int i = offset + 7; i > offset; i--) {
        bytes[i] = (byte) val;
        val >>>= 8;
      }
      bytes[offset] = (byte) val;
      return offset + SIZEOF_LONG;
    }
  }

  /**
   * Put a long value out to the specified byte array position (Unsafe).
   *
   * @param bytes the byte array
   * @param offset position in the array
   * @param val long to write out
   * @return incremented offset
   */
  private static int putLongUnsafe(byte[] bytes, int offset, long val) {
    if (LexicographicalComparerHolder.UnsafeComparer.LITTLE_ENDIAN) {
      val = Long.reverseBytes(val);
    }
    LexicographicalComparerHolder.UnsafeComparer.UNSAFE.putLong(bytes, (long) offset +
      LexicographicalComparerHolder.UnsafeComparer.BYTE_ARRAY_BASE_OFFSET, val);
    return offset + SIZEOF_LONG;
  }

  /**
   * Presumes float encoded as IEEE 754 floating-point "single format"
   *
   * @param bytes byte array
   * @return Float made from passed byte array.
   */
  public static float toFloat(byte [] bytes) {
    return toFloat(bytes, 0);
  }

  /**
   * Presumes float encoded as IEEE 754 floating-point "single format"
   *
   * @param bytes array to convert
   * @param offset offset into array
   * @return Float made from passed byte array.
   */
  public static float toFloat(byte [] bytes, int offset) {
    return Float.intBitsToFloat(toInt(bytes, offset, SIZEOF_INT));
  }

  /**
   * Put a float value out to the specified byte position.
   *
   * @param bytes byte array
   * @param offset offset to write to
   * @param f float value
   * @return New offset in <code>bytes</code>
   */
  public static int putFloat(byte [] bytes, int offset, float f) {
    return putInt(bytes, offset, Float.floatToRawIntBits(f));
  }

  /**
   * Converts a byte array to a double value.
   *
   * @param bytes byte array
   * @return Return double made from passed bytes.
   */
  public static double toDouble(final byte [] bytes) {
    return toDouble(bytes, 0);
  }

  /**
   * Converts a byte array to a double value.
   *
   * @param bytes byte array
   * @param offset offset where double is
   * @return Return double made from passed bytes.
   */
  public static double toDouble(final byte [] bytes, final int offset) {
    return Double.longBitsToDouble(toLong(bytes, offset, SIZEOF_LONG));
  }

  /**
   * Put a double value out to the specified byte position.
   *
   * @param bytes byte array
   * @param offset offset to write to
   * @param d value
   * @return New offset into array <code>bytes</code>
   */
  public static int putDouble(byte [] bytes, int offset, double d) {
    return putLong(bytes, offset, Double.doubleToLongBits(d));
  }

  /**
   * Converts a byte array to an int value
   *
   * @param bytes byte array
   * @return the int value
   */
  public static int toInt(byte[] bytes) {
    return toInt(bytes, 0, SIZEOF_INT);
  }

  /**
   * Converts a byte array to an int value
   *
   * @param bytes byte array
   * @param offset offset into array
   * @return the int value
   */
  public static int toInt(byte[] bytes, int offset) {
    return toInt(bytes, offset, SIZEOF_INT);
  }

  /**
   * Converts a byte array to an int value
   *
   * @param bytes byte array
   * @param offset offset into array
   * @param length length of int (has to be {@link #SIZEOF_INT})
   * @return the int value
   * @throws IllegalArgumentException if length is not {@link #SIZEOF_INT} or
   * if there's not enough room in the array at the offset indicated.
   */
  public static int toInt(byte[] bytes, int offset, final int length) {
    if (length != SIZEOF_INT || offset + length > bytes.length) {
      throw explainWrongLengthOrOffset(bytes, offset, length, SIZEOF_INT);
    }
    if (UNSAFE_UNALIGNED) {
      return toIntUnsafe(bytes, offset);
    } else {
      int n = 0;
      for (int i = offset; i < (offset + length); i++) {
        n <<= 8;
        n ^= bytes[i] & 0xFF;
      }
      return n;
    }
  }

  /**
   * Converts a byte array to an int value (Unsafe version)
   *
   * @param bytes byte array
   * @param offset offset into array
   * @return the int value
   */
  public static int toIntUnsafe(byte[] bytes, int offset) {
    if (LexicographicalComparerHolder.UnsafeComparer.LITTLE_ENDIAN) {
      return Integer.reverseBytes(LexicographicalComparerHolder.UnsafeComparer.UNSAFE.getInt(bytes,
        (long) offset + LexicographicalComparerHolder.UnsafeComparer.BYTE_ARRAY_BASE_OFFSET));
    } else {
      return LexicographicalComparerHolder.UnsafeComparer.UNSAFE.getInt(bytes,
        (long) offset + LexicographicalComparerHolder.UnsafeComparer.BYTE_ARRAY_BASE_OFFSET);
    }
  }

  /**
   * Converts a byte array to an short value (Unsafe version)
   *
   * @param bytes byte array
   * @param offset offset into array
   * @return the short value
   */
  private static short toShortUnsafe(byte[] bytes, int offset) {
    if (LexicographicalComparerHolder.UnsafeComparer.LITTLE_ENDIAN) {
      return Short.reverseBytes(LexicographicalComparerHolder.UnsafeComparer.UNSAFE.getShort(bytes,
        (long) offset + LexicographicalComparerHolder.UnsafeComparer.BYTE_ARRAY_BASE_OFFSET));
    } else {
      return LexicographicalComparerHolder.UnsafeComparer.UNSAFE.getShort(bytes,
        (long) offset + LexicographicalComparerHolder.UnsafeComparer.BYTE_ARRAY_BASE_OFFSET);
    }
  }

  /**
   * Converts a byte array to an long value (Unsafe version)
   *
   * @param bytes byte array
   * @param offset offset into array
   * @return the long value
   */
  private static long toLongUnsafe(byte[] bytes, int offset) {
    if (LexicographicalComparerHolder.UnsafeComparer.LITTLE_ENDIAN) {
      return Long.reverseBytes(LexicographicalComparerHolder.UnsafeComparer.UNSAFE.getLong(bytes,
        (long) offset + LexicographicalComparerHolder.UnsafeComparer.BYTE_ARRAY_BASE_OFFSET));
    } else {
      return LexicographicalComparerHolder.UnsafeComparer.UNSAFE.getLong(bytes,
        (long) offset + LexicographicalComparerHolder.UnsafeComparer.BYTE_ARRAY_BASE_OFFSET);
    }
  }

  /**
   * Put an int value out to the specified byte array position.
   *
   * @param bytes the byte array
   * @param offset position in the array
   * @param val int to write out
   * @return incremented offset
   * @throws IllegalArgumentException if the byte array given doesn't have enough room at the
   * offset specified.
   */
  public static int putInt(byte[] bytes, int offset, int val) {
    if (bytes.length - offset < SIZEOF_INT) {
      throw new IllegalArgumentException("Not enough room to put an int at" + " offset " + offset +
        " in a " + bytes.length + " byte array");
    }
    if (UNSAFE_UNALIGNED) {
      return putIntUnsafe(bytes, offset, val);
    } else {
      for (int i = offset + 3; i > offset; i--) {
        bytes[i] = (byte) val;
        val >>>= 8;
      }
      bytes[offset] = (byte) val;
      return offset + SIZEOF_INT;
    }
  }

  /**
   * Put an int value out to the specified byte array position (Unsafe).
   *
   * @param bytes the byte array
   * @param offset position in the array
   * @param val int to write out
   * @return incremented offset
   */
  private static int putIntUnsafe(byte[] bytes, int offset, int val) {
    if (LexicographicalComparerHolder.UnsafeComparer.LITTLE_ENDIAN) {
      val = Integer.reverseBytes(val);
    }
    LexicographicalComparerHolder.UnsafeComparer.UNSAFE.putInt(bytes, (long) offset +
      LexicographicalComparerHolder.UnsafeComparer.BYTE_ARRAY_BASE_OFFSET, val);
    return offset + SIZEOF_INT;
  }

  /**
   * Converts a byte array to a short value
   *
   * @param bytes byte array
   * @return the short value
   */
  public static short toShort(byte[] bytes) {
    return toShort(bytes, 0, SIZEOF_SHORT);
  }

  /**
   * Converts a byte array to a short value
   *
   * @param bytes byte array
   * @param offset offset into array
   * @return the short value
   */
  public static short toShort(byte[] bytes, int offset) {
    return toShort(bytes, offset, SIZEOF_SHORT);
  }

  /**
   * Converts a byte array to a short value
   *
   * @param bytes byte array
   * @param offset offset into array
   * @param length length, has to be {@link #SIZEOF_SHORT}
   * @return the short value
   * @throws IllegalArgumentException if length is not {@link #SIZEOF_SHORT}
   * or if there's not enough room in the array at the offset indicated.
   */
  public static short toShort(byte[] bytes, int offset, final int length) {
    if (length != SIZEOF_SHORT || offset + length > bytes.length) {
      throw explainWrongLengthOrOffset(bytes, offset, length, SIZEOF_SHORT);
    }
    if (UNSAFE_UNALIGNED) {
      return toShortUnsafe(bytes, offset);
    } else {
      short n = 0;
      n = (short) ((n ^ bytes[offset]) & 0xFF);
      n = (short) (n << 8);
      n = (short) ((n ^ bytes[offset + 1]) & 0xFF);
      return n;
    }
  }

  /**
   * Put a short value out to the specified byte array position.
   *
   * @param bytes the byte array
   * @param offset position in the array
   * @param val short to write out
   * @return incremented offset
   * @throws IllegalArgumentException if the byte array given doesn't have enough room at the
   * offset specified.
   */
  public static int putShort(byte[] bytes, int offset, short val) {
    if (bytes.length - offset < SIZEOF_SHORT) {
      throw new IllegalArgumentException("Not enough room to put a short at" + " offset " + offset +
        " in a " + bytes.length + " byte array");
    }
    if (UNSAFE_UNALIGNED) {
      return putShortUnsafe(bytes, offset, val);
    } else {
      bytes[offset + 1] = (byte) val;
      val >>= 8;
      bytes[offset] = (byte) val;
      return offset + SIZEOF_SHORT;
    }
  }

  /**
   * Put a short value out to the specified byte array position (Unsafe).
   *
   * @param bytes the byte array
   * @param offset position in the array
   * @param val short to write out
   * @return incremented offset
   */
  private static int putShortUnsafe(byte[] bytes, int offset, short val) {
    if (LexicographicalComparerHolder.UnsafeComparer.LITTLE_ENDIAN) {
      val = Short.reverseBytes(val);
    }
    LexicographicalComparerHolder.UnsafeComparer.UNSAFE.putShort(bytes, (long) offset +
      LexicographicalComparerHolder.UnsafeComparer.BYTE_ARRAY_BASE_OFFSET, val);
    return offset + SIZEOF_SHORT;
  }

  /**
   * Convert a BigDecimal value to a byte array
   *
   * @param val input value
   * @return the byte array
   */
  public static byte[] toBytes(BigDecimal val) {
    byte[] valueBytes = val.unscaledValue().toByteArray();
    byte[] result = new byte[valueBytes.length + SIZEOF_INT];
    int offset = putInt(result, 0, val.scale());
    putBytes(result, offset, valueBytes, 0, valueBytes.length);
    return result;
  }


  /**
   * Converts a byte array to a BigDecimal
   *
   * @param bytes bytes
   * @return the char value
   */
  public static BigDecimal toBigDecimal(byte[] bytes) {
    return toBigDecimal(bytes, 0, bytes.length);
  }

  /**
   * Converts a byte array to a BigDecimal value
   *
   * @param bytes bytes
   * @param offset offset
   * @param length length
   * @return the char value
   */
  public static BigDecimal toBigDecimal(byte[] bytes, int offset, final int length) {
    if (bytes == null || length < SIZEOF_INT + 1 ||
      (offset + length > bytes.length)) {
      return null;
    }

    int scale = toInt(bytes, offset);
    byte[] tcBytes = new byte[length - SIZEOF_INT];
    System.arraycopy(bytes, offset + SIZEOF_INT, tcBytes, 0, length - SIZEOF_INT);
    return new BigDecimal(new BigInteger(tcBytes), scale);
  }

  /**
   * Compares two byte arrays
   *
   * @param left left operand
   * @param right right operand
   * @return 0 if equal, &lt; 0 if left is less than right, etc.
   */
  public static int compareTo(final byte [] left, final byte [] right) {
    return LexicographicalComparerHolder.BEST_COMPARER.
      compareTo(left, 0, left.length, right, 0, right.length);
  }

  /**
   * 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, &lt; 0 if left is less than right, etc.
   */
  public static int compareTo(byte[] buffer1, int offset1, int length1,
                              byte[] buffer2, int offset2, int length2) {
    return LexicographicalComparerHolder.BEST_COMPARER.
      compareTo(buffer1, offset1, length1, buffer2, offset2, length2);
  }

  /**
   * Comparer interface
   *
   * @param <T> type to compare
   */
  interface Comparer<T> {
    /**
     * Compares two buffers of type {@code T} with a given offset and length.
     *
     * @param buffer1 left operand
     * @param offset1 offset of left operand
     * @param length1 length of left operand
     * @param buffer2 left operand
     * @param offset2 offset of right operand
     * @param length2 length of right operand
     * @return 0 if equal, < 0 if left is less than right, etc.
     */
    int compareTo(
      T buffer1, int offset1, int length1, T buffer2, int offset2, int length2
    );
  }

  /**
   * Returns the pure java comparer
   *
   * @return java comparer
   */
  private static Comparer<byte[]> lexicographicalComparerJavaImpl() {
    return LexicographicalComparerHolder.PureJavaComparer.INSTANCE;
  }

  /**
   * Provides a lexicographical comparer implementation; either a Java
   * implementation or a faster implementation based on {@link Unsafe}.
   * <p>
   * Uses reflection to gracefully fall back to the Java implementation if {@code Unsafe} isn't available.
   */
  static class LexicographicalComparerHolder {
    /**
     * Class name of the unsafe comparer
     */
    static final String UNSAFE_COMPARER_NAME =
      LexicographicalComparerHolder.class.getName() + "$UnsafeComparer";

    /**
     * The best comparer
     */
    static final Comparer<byte[]> BEST_COMPARER = getBestComparer();
    /**
     * Returns the Unsafe-using Comparer, or falls back to the pure-Java implementation if unable to do so.
     *
     * @return Unsafe comparer if available, else a pure java comparer
     */
    static Comparer<byte[]> getBestComparer() {
      try {
        Class<?> theClass = Class.forName(UNSAFE_COMPARER_NAME);

        // yes, UnsafeComparer does implement Comparer<byte[]>
        @SuppressWarnings("unchecked")
        Comparer<byte[]> comparer =
          (Comparer<byte[]>) theClass.getEnumConstants()[0];
        return comparer;
      } catch (ClassNotFoundException t) {
        return lexicographicalComparerJavaImpl();
      }
    }

    /**
     * Java comparer (without unsafe)
     */
    enum PureJavaComparer implements Comparer<byte[]> {
      /**
       * Instance
       */
      INSTANCE;

      @Override
      public 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;
        }
        // Bring WritableComparator code local
        int end1 = offset1 + length1;
        int end2 = offset2 + length2;
        for (int i = offset1, j = offset2; i < end1 && j < end2; i++, j++) {
          int a = buffer1[i] & 0xff;
          int b = buffer2[j] & 0xff;
          if (a != b) {
            return a - b;
          }
        }
        return length1 - length2;
      }
    }

    /**
     * Unsafe comparer
     */
    enum UnsafeComparer implements Comparer<byte[]> {
      /**
       * Instance
       */
      INSTANCE;

      /**
       * Unsafe
       */
      static final Unsafe UNSAFE;

      /** The offset to the first element in a byte array. */
      static final int BYTE_ARRAY_BASE_OFFSET;

      static {
        if (UNSAFE_UNALIGNED) {
          UNSAFE = (Unsafe) AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
            try {
              Field f = Unsafe.class.getDeclaredField("theUnsafe");
              f.setAccessible(true);
              return f.get(null);
            } catch (ReflectiveOperationException e) {
              LOG.warn("sun.misc.Unsafe is not accessible");
              return null;
            }
          });
        } else {
          // It doesn't matter what we throw;
          // it's swallowed in getBestComparer().
          throw new Error();
        }

        BYTE_ARRAY_BASE_OFFSET = UNSAFE.arrayBaseOffset(byte[].class);

        // sanity check - this should never fail
        if (UNSAFE.arrayIndexScale(byte[].class) != 1) {
          throw new AssertionError();
        }
      }

      /**
       * true, if byte order is little endian
       */
      static final boolean LITTLE_ENDIAN = ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN);

      /**
       * 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.
       */
      @Override
      public 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;
        }
        final int stride = 8;
        final int minLength = Math.min(length1, length2);
        int strideLimit = minLength & -stride;
        final long offset1Adj = (long) offset1 + BYTE_ARRAY_BASE_OFFSET;
        final long offset2Adj = (long) offset2 + BYTE_ARRAY_BASE_OFFSET;
        int i;

        /*
         * Compare 8 bytes at a time. Benchmarking on x86 shows a stride of 8 bytes is no slower
         * than 4 bytes even on 32-bit. On the other hand, it is substantially faster on 64-bit.
         */
        for (i = 0; i < strideLimit; i += stride) {
          long lw = UNSAFE.getLong(buffer1, offset1Adj + (long) i);
          long rw = UNSAFE.getLong(buffer2, offset2Adj + (long) i);
          if (lw != rw) {
            if (!LITTLE_ENDIAN) {
              return ((lw + Long.MIN_VALUE) < (rw + Long.MIN_VALUE)) ? -1 : 1;
            }

            /*
             * We want to compare only the first index where left[index] != right[index]. This
             * corresponds to the least significant nonzero byte in lw ^ rw, since lw and rw are
             * little-endian. Long.numberOfTrailingZeros(diff) tells us the least significant
             * nonzero bit, and zeroing out the first three bits of L.nTZ gives us the shift to get
             * that least significant nonzero byte. This comparison logic is based on UnsignedBytes
             * comparator from guava v21
             */
            int n = Long.numberOfTrailingZeros(lw ^ rw) & ~0x7;
            return ((int) ((lw >>> n) & 0xFF)) - ((int) ((rw >>> n) & 0xFF));
          }
        }

        // The epilogue to cover the last (minLength % stride) elements.
        for (; i < minLength; i++) {
          int a = buffer1[offset1 + i] & 0xFF;
          int b = buffer2[offset2 + i] & 0xFF;
          if (a != b) {
            return a - b;
          }
        }
        return length1 - length2;
      }
    }
  }

  /**
   * Hashes a specified byte array with offset and length.
   *
   * @param bytes array to hash
   * @param offset offset to start from
   * @param length length to hash
   * @return hash
   */
  public static int hashCode(byte[] bytes, int offset, int length) {
    int hash = 1;
    for (int i = offset; i < offset + length; i++) {
      hash = (31 * hash) + (int) bytes[i];
    }
    return hash;
  }
}