content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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.apache.cassandra.utils;
  
  /*
   * BE ADVISED: New imports added here might introduce new dependencies for
   * the clientutil jar.  If in doubt, run the `ant test-clientutil-jar' target
   * afterward, and ensure the tests still pass.
   */
  
  import java.io.DataInput;
  import java.io.EOFException;
  import java.io.IOException;
  import java.io.InputStream;
  import java.math.BigDecimal;
  import java.math.BigInteger;
  import java.net.InetAddress;
  import java.nio.ByteBuffer;
  import java.nio.channels.FileChannel;
  import java.nio.charset.CharacterCodingException;
  import java.nio.charset.Charset;
  import java.nio.charset.StandardCharsets;
  import java.util.Arrays;
  import java.util.Date;
  import java.util.LinkedHashMap;
  import java.util.LinkedHashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.UUID;
  import java.util.stream.Collectors;
  
  import net.nicoulaj.compilecommand.annotations.DontInline;
  import net.nicoulaj.compilecommand.annotations.Inline;
  import org.apache.cassandra.db.TypeSizes;
  import org.apache.cassandra.db.marshal.BooleanType;
  import org.apache.cassandra.db.marshal.BytesType;
  import org.apache.cassandra.db.marshal.ListType;
  import org.apache.cassandra.db.marshal.MapType;
  import org.apache.cassandra.db.marshal.SetType;
  import org.apache.cassandra.db.marshal.TimestampType;
  import org.apache.cassandra.io.UnversionedSerializer;
  import org.apache.cassandra.io.compress.BufferType;
  import org.apache.cassandra.io.util.DataInputPlus;
  import org.apache.cassandra.io.util.DataOutputPlus;
  import org.apache.cassandra.utils.memory.MemoryUtil;
  
  /**
   * Utility methods to make ByteBuffers less painful
   * The following should illustrate the different ways byte buffers can be used
   *
   *        public void testArrayOffet()
   *        {
   *
   *            byte[] b = "test_slice_array".getBytes();
   *            ByteBuffer bb = ByteBuffer.allocate(1024);
   *
   *            assert bb.position() == 0;
   *            assert bb.limit()    == 1024;
   *            assert bb.capacity() == 1024;
   *
   *            bb.put(b);
   *
   *            assert bb.position()  == b.length;
   *            assert bb.remaining() == bb.limit() - bb.position();
   *
   *            ByteBuffer bb2 = bb.slice();
   *
   *            assert bb2.position()    == 0;
   *
   *            //slice should begin at other buffers current position
   *            assert bb2.arrayOffset() == bb.position();
   *
   *            //to match the position in the underlying array one needs to
   *            //track arrayOffset
   *            assert bb2.limit()+bb2.arrayOffset() == bb.limit();
   *
   *
   *            assert bb2.remaining() == bb.remaining();
   *
   *        }
   *
   * }
   *
   */
  public class ByteBufferUtil
  {
      public static final ByteBuffer EMPTY_BYTE_BUFFER = ByteBuffer.wrap(new byte[0]);
      /** Represents an unset value in bound variables */
      public static final ByteBuffer UNSET_BYTE_BUFFER = ByteBuffer.wrap(new byte[]{});
  
      public static final long EMPTY_SIZE_ON_HEAP = ObjectSizes.measureDeep(ByteBufferUtil.EMPTY_BYTE_BUFFER);
  
      public static final ByteBuffer[] EMPTY_ARRAY = new ByteBuffer[0];
  
      @Inline
      public static int compareUnsigned(ByteBuffer o1, ByteBuffer o2)
      {
          return FastByteOperations.compareUnsigned(o1, o2);
      }
  
      @Inline
      public static int compare(byte[] o1, ByteBuffer o2)
      {
          return FastByteOperations.compareUnsigned(o1, 0, o1.length, o2);
      }
  
      @Inline
      public static int compare(ByteBuffer o1, byte[] o2)
      {
          return FastByteOperations.compareUnsigned(o1, o2, 0, o2.length);
      }
  
      public static int compare(ByteBuffer o1, int s1, int l1, byte[] o2)
      {
          return FastByteOperations.compareUnsigned(o1, s1, l1, o2, 0, o2.length);
      }
  
      public static int compare(byte[] o1, ByteBuffer o2, int s2, int l2)
      {
          return FastByteOperations.compareUnsigned(o1, 0, o1.length, o2, s2, l2);
      }
  
      /**
       * Decode a String representation.
       * This method assumes that the encoding charset is UTF_8.
       *
       * @param buffer a byte buffer holding the string representation
       * @return the decoded string
       */
      public static String string(ByteBuffer buffer) throws CharacterCodingException
      {
          return string(buffer, StandardCharsets.UTF_8);
      }
  
      /**
       * Decode a String representation.
       * This method assumes that the encoding charset is UTF_8.
       *
       * @param buffer a byte buffer holding the string representation
       * @param position the starting position in {@code buffer} to start decoding from
       * @param length the number of bytes from {@code buffer} to use
       * @return the decoded string
       */
      public static String string(ByteBuffer buffer, int position, int length) throws CharacterCodingException
      {
          return string(buffer, position, length, StandardCharsets.UTF_8);
      }
  
      /**
       * Decode a String representation.
       *
       * @param buffer a byte buffer holding the string representation
       * @param position the starting position in {@code buffer} to start decoding from
       * @param length the number of bytes from {@code buffer} to use
       * @param charset the String encoding charset
       * @return the decoded string
       */
      public static String string(ByteBuffer buffer, int position, int length, Charset charset) throws CharacterCodingException
      {
          ByteBuffer copy = buffer.duplicate();
          copy.position(position);
          copy.limit(copy.position() + length);
          return string(copy, charset);
      }
  
      /**
       * Decode a String representation.
       *
       * @param buffer a byte buffer holding the string representation
       * @param charset the String encoding charset
       * @return the decoded string
       */
      public static String string(ByteBuffer buffer, Charset charset) throws CharacterCodingException
      {
          return charset.newDecoder().decode(buffer.duplicate()).toString();
      }
  
      /**
       * You should almost never use this.  Instead, use the write* methods to avoid copies.
       */
      public static byte[] getArray(ByteBuffer buffer)
      {
          return getArray(buffer, buffer.position(), buffer.remaining());
      }
  
      /**
       * You should almost never use this.  Instead, use the write* methods to avoid copies.
       */
      public static byte[] getArray(ByteBuffer buffer, int position, int length)
      {
          if (buffer.hasArray())
          {
              int boff = buffer.arrayOffset() + position;
              return Arrays.copyOfRange(buffer.array(), boff, boff + length);
          }
  
          // else, DirectByteBuffer.get() is the fastest route
          byte[] bytes = new byte[length];
          ByteBuffer dup = buffer.duplicate();
          dup.position(position).limit(position + length);
          dup.get(bytes);
          return bytes;
      }
  
      /**
       * You should almost never use this.  Instead, use the write* methods to avoid copies.
       */
      public static byte[] getArrayUnsafe(ByteBuffer buffer)
      {
          return getArrayUnsafe(buffer, buffer.position(), buffer.remaining());
      }
  
      /**
       * You should almost never use this.  Instead, use the write* methods to avoid copies.
       */
      public static byte[] getArrayUnsafe(ByteBuffer buffer, int position, int length)
      {
          if (buffer.hasArray() && position == 0 && buffer.arrayOffset() == 0 && length == buffer.capacity())
              return buffer.array();
  
          return getArray(buffer, position, length);
      }
  
      /**
       * ByteBuffer adaptation of org.apache.commons.lang3.ArrayUtils.lastIndexOf method
       *
       * @param buffer the array to traverse for looking for the object, may be <code>null</code>
       * @param valueToFind the value to find
       * @param startIndex the start index (i.e. BB position) to travers backwards from
       * @return the last index (i.e. BB position) of the value within the array
       * [between buffer.position() and buffer.limit()]; <code>-1</code> if not found.
       */
      public static int lastIndexOf(ByteBuffer buffer, byte valueToFind, int startIndex)
      {
          assert buffer != null;
  
          if (startIndex < buffer.position())
          {
              return -1;
          }
          else if (startIndex >= buffer.limit())
          {
              startIndex = buffer.limit() - 1;
          }
  
          for (int i = startIndex; i >= buffer.position(); i--)
          {
              if (valueToFind == buffer.get(i))
                  return i;
          }
  
          return -1;
      }
  
      /**
       * Encode a String in a ByteBuffer using UTF_8.
       *
       * @param s the string to encode
       * @return the encoded string
       */
      public static ByteBuffer bytes(String s)
      {
          return ByteBuffer.wrap(s.getBytes(StandardCharsets.UTF_8));
      }
  
      /**
       * Encode a String in a ByteBuffer using the provided charset.
       *
       * @param s the string to encode
       * @param charset the String encoding charset to use
       * @return the encoded string
       */
      public static ByteBuffer bytes(String s, Charset charset)
      {
          return ByteBuffer.wrap(s.getBytes(charset));
      }
  
      /**
       * @return a new copy of the data in @param buffer
       * USUALLY YOU SHOULD USE ByteBuffer.duplicate() INSTEAD, which creates a new Buffer
       * (so you can mutate its position without affecting the original) without copying the underlying array.
       */
      public static ByteBuffer clone(ByteBuffer buffer)
      {
          assert buffer != null;
  
          if (buffer.remaining() == 0)
              return EMPTY_BYTE_BUFFER;
  
          ByteBuffer clone = ByteBuffer.allocate(buffer.remaining());
  
          if (buffer.hasArray())
          {
              System.arraycopy(buffer.array(), buffer.arrayOffset() + buffer.position(), clone.array(), 0, buffer.remaining());
          }
          else
          {
              clone.put(buffer.duplicate());
              clone.flip();
          }
  
          return clone;
      }
  
      /**
       * Transfer bytes from a ByteBuffer to byte array.
       *
       * @param src the source ByteBuffer
       * @param srcPos starting position in the source ByteBuffer
       * @param dst the destination byte array
       * @param dstPos starting position in the destination byte array
       * @param length the number of bytes to copy
       */
      public static void copyBytes(ByteBuffer src, int srcPos, byte[] dst, int dstPos, int length)
      {
          FastByteOperations.copy(src, srcPos, dst, dstPos, length);
      }
  
      /**
       * Transfer bytes from one ByteBuffer to another.
       * This function acts as System.arrayCopy() but for ByteBuffers, and operates safely on direct memory.
       *
       * @param src the source ByteBuffer
       * @param srcPos starting position in the source ByteBuffer
       * @param dst the destination ByteBuffer
       * @param dstPos starting position in the destination ByteBuffer
       * @param length the number of bytes to copy
       */
      public static void copyBytes(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int length)
      {
          FastByteOperations.copy(src, srcPos, dst, dstPos, length);
      }
  
      public static int put(ByteBuffer src, ByteBuffer trg)
      {
          int length = Math.min(src.remaining(), trg.remaining());
          copyBytes(src, src.position(), trg, trg.position(), length);
          trg.position(trg.position() + length);
          src.position(src.position() + length);
          return length;
      }
  
      public static void writeZeroes(ByteBuffer dest, int count)
      {
          if (count >= 8)
          {
              // align
              while ((dest.position() & 0x7) != 0)
              {
                  dest.put((byte) 0);
                  --count;
              }
          }
          // write aligned longs
          while (count >= 8)
          {
              dest.putLong(0L);
              count -= 8;
          }
          // finish up
          while (count > 0)
          {
              dest.put((byte) 0);
              --count;
          }
      }
  
      public static void writeWithLength(ByteBuffer bytes, DataOutputPlus out) throws IOException
      {
          out.writeInt(bytes.remaining());
          out.write(bytes);
      }
  
      public static void writeWithVIntLength(ByteBuffer bytes, DataOutputPlus out) throws IOException
      {
          out.writeUnsignedVInt32(bytes.remaining());
          out.write(bytes);
      }
      public static void writeWithVIntLengthAndNull(ByteBuffer bytes, DataOutputPlus out) throws IOException
      {
          if (bytes == null)
          {
              out.writeVInt32(-1);
              return;
          }
          
          out.writeVInt32(bytes.remaining());
          out.write(bytes);
      }
  
      public static void writeWithShortLength(ByteBuffer buffer, DataOutputPlus out) throws IOException
      {
          int length = buffer.remaining();
          assert 0 <= length && length <= FBUtilities.MAX_UNSIGNED_SHORT
              : String.format("Attempted serializing to buffer exceeded maximum of %s bytes: %s", FBUtilities.MAX_UNSIGNED_SHORT, length);
          out.writeShort(length);
          out.write(buffer);
      }
  
      public static ByteBuffer readWithLength(DataInput in) throws IOException
      {
          int length = in.readInt();
          if (length < 0)
          {
              throw new IOException("Corrupt (negative) value length encountered");
          }
  
          return ByteBufferUtil.read(in, length);
      }
  
      public static ByteBuffer readWithVIntLength(DataInputPlus in) throws IOException
      {
          int length = in.readUnsignedVInt32();
          if (length < 0)
              throw new IOException("Corrupt (negative) value length encountered");
  
          return ByteBufferUtil.read(in, length);
      }
  
      public static int serializedSizeWithVIntLength(ByteBuffer buffer)
      {
          int size = buffer.remaining();
          return TypeSizes.sizeofUnsignedVInt(size) + size;
      }
  
      public static long estimatedSizeOnHeap(ByteBuffer buffer)
      {
          return EMPTY_SIZE_ON_HEAP + buffer.remaining();
      }
  
      public static void skipWithVIntLength(DataInputPlus in) throws IOException
      {
          int length = in.readUnsignedVInt32();
          if (length < 0)
              throw new IOException("Corrupt (negative) value length encountered");
  
          in.skipBytesFully(length);
      }
  
      /* @return An unsigned short in an integer. */
      public static int readShortLength(DataInput in) throws IOException
      {
          return in.readUnsignedShort();
      }
  
      /**
       * @param in data input
       * @return An unsigned short in an integer.
       * @throws IOException if an I/O error occurs.
       */
      public static ByteBuffer readWithShortLength(DataInput in) throws IOException
      {
          return ByteBufferUtil.read(in, readShortLength(in));
      }
  
      public static int serializedSizeWithShortLength(ByteBuffer buffer)
      {
          int size = buffer.remaining();
          return TypeSizes.sizeof((short)size) + size;
      }
  
      /**
       * @param in data input
       * @throws IOException if an I/O error occurs.
       */
      public static void skipShortLength(DataInputPlus in) throws IOException
      {
          int skip = readShortLength(in);
          in.skipBytesFully(skip);
      }
  
      public static ByteBuffer read(DataInput in, int length) throws IOException
      {
          if (length == 0)
              return EMPTY_BYTE_BUFFER;
  
          byte[] buff = new byte[length];
          in.readFully(buff);
          return ByteBuffer.wrap(buff);
      }
  
      public static byte[] readBytes(DataInput in, int length) throws IOException
      {
          assert length > 0 : "length is not > 0: " + length;
          byte[] bytes = new byte[length];
          in.readFully(bytes);
          return bytes;
      }
  
      public static byte[] readBytesWithLength(DataInput in) throws IOException
      {
          int length = in.readInt();
          if (length < 0)
              throw new IOException("Corrupt (negative) value length encountered");
  
          return readBytes(in, length);
      }
  
      /**
       * Convert a byte buffer to an integer.
       * Does not change the byte buffer position.
       *
       * @param bytes byte buffer to convert to integer
       * @return int representation of the byte buffer
       */
      public static int toInt(ByteBuffer bytes)
      {
          return bytes.getInt(bytes.position());
      }
  
      /**
       * Convert a byte buffer to a short.
       * Does not change the byte buffer position.
       *
       * @param bytes byte buffer to convert to short
       * @return short representation of the byte buffer
       */
      public static short toShort(ByteBuffer bytes)
      {
          return bytes.getShort(bytes.position());
      }
  
      /**
       * Convert a byte buffer to a short.
       * Does not change the byte buffer position.
       *
       * @param bytes byte buffer to convert to byte
       * @return byte representation of the byte buffer
       */
      public static byte toByte(ByteBuffer bytes)
      {
          return bytes.get(bytes.position());
      }
  
      public static long toLong(ByteBuffer bytes)
      {
          return bytes.getLong(bytes.position());
      }
  
      public static float toFloat(ByteBuffer bytes)
      {
          return bytes.getFloat(bytes.position());
      }
  
      public static float getFloat(ByteBuffer bytes, int offset)
      {
          return bytes.getFloat(offset);
      }
  
      public static double toDouble(ByteBuffer bytes)
      {
          return bytes.getDouble(bytes.position());
      }
  
      public static ByteBuffer objectToBytes(Object obj)
      {
          if (obj == null) return null;
          if (obj instanceof Integer)
              return ByteBufferUtil.bytes((int) obj);
          else if (obj instanceof Byte)
              return ByteBufferUtil.bytes((byte) obj);
          else if (obj instanceof Short)
              return ByteBufferUtil.bytes((short) obj);
          else if (obj instanceof Long)
              return ByteBufferUtil.bytes((long) obj);
          else if (obj instanceof Float)
              return ByteBufferUtil.bytes((float) obj);
          else if (obj instanceof Double)
              return ByteBufferUtil.bytes((double) obj);
          else if (obj instanceof Boolean)
              return BooleanType.instance.decompose((Boolean) obj);
          else if (obj instanceof UUID)
              return ByteBufferUtil.bytes((UUID) obj);
          else if (obj instanceof InetAddress)
              return ByteBufferUtil.bytes((InetAddress) obj);
          else if (obj instanceof String)
              return ByteBufferUtil.bytes((String) obj);
          else if (obj instanceof byte[])
              return ByteBuffer.wrap((byte[]) obj);
          else if (obj instanceof ByteBuffer)
              return (ByteBuffer) obj;
          else if (obj instanceof BigInteger)
              return ByteBuffer.wrap(((BigInteger)obj).toByteArray());
          else if (obj instanceof BigDecimal)
          {
              BigDecimal cast = (BigDecimal) obj;
              BigInteger bi = cast.unscaledValue();
              int scale = cast.scale();
              byte[] bibytes = bi.toByteArray();
  
              ByteBuffer bytes = ByteBuffer.allocate(4 + bibytes.length);
              bytes.putInt(scale);
              bytes.put(bibytes);
              bytes.rewind();
              return bytes;
          }
          else if (obj instanceof List)
          {
              List<?> list = (List<?>) obj;
              // convert subtypes to BB
              List<ByteBuffer> bbs = list.stream().map(ByteBufferUtil::objectToBytes).collect(Collectors.toList());
              // decompose/serializer doesn't use the isMultiCell, so safe to do this
              return ListType.getInstance(BytesType.instance, false).decompose(bbs);
          }
          else if (obj instanceof Map)
          {
              Map<?, ?> map = (Map<?, ?>) obj;
              // convert subtypes to BB
              Map<ByteBuffer, ByteBuffer> bbs = new LinkedHashMap<>();
              for (Map.Entry<?, ?> e : map.entrySet())
              {
                  Object key = e.getKey();
                  ByteBuffer previousValue = bbs.put(objectToBytes(key), objectToBytes(e.getValue()));
                  if (previousValue != null)
                      throw new IllegalStateException("Key " + key + " already maps to value " + previousValue);
              }
              // decompose/serializer doesn't use the isMultiCell, so safe to do this
              return MapType.getInstance(BytesType.instance, BytesType.instance, false).decompose(bbs);
          }
          else if (obj instanceof Set)
          {
              Set<?> set = (Set<?>) obj;
              // convert subtypes to BB
              Set<ByteBuffer> bbs = new LinkedHashSet<>();
              for (Object o : set)
                  if (!bbs.add(objectToBytes(o)))
                      throw new IllegalStateException("Object " + o + " maps to a buffer that already exists in the set");
              // decompose/serializer doesn't use the isMultiCell, so safe to do this
              return SetType.getInstance(BytesType.instance, false).decompose(bbs);
          }
          else if (obj instanceof Date)
              return TimestampType.instance.decompose((Date) obj);
          else
              throw new IllegalArgumentException(String.format("Cannot convert value %s of type %s",
                                                               obj,
                                                               obj.getClass()));
      }
  
      public static ByteBuffer bytes(byte b)
      {
          return ByteBuffer.allocate(1).put(0, b);
      }
  
      public static ByteBuffer bytes(short s)
      {
          return ByteBuffer.allocate(2).putShort(0, s);
      }
  
      public static ByteBuffer bytes(int i)
      {
          return ByteBuffer.allocate(4).putInt(0, i);
      }
  
      public static ByteBuffer bytes(long n)
      {
          return ByteBuffer.allocate(8).putLong(0, n);
      }
  
      public static ByteBuffer bytes(float f)
      {
          return ByteBuffer.allocate(4).putFloat(0, f);
      }
  
      public static ByteBuffer bytes(double d)
      {
          return ByteBuffer.allocate(8).putDouble(0, d);
      }
  
      public static InputStream inputStream(ByteBuffer bytes)
      {
          final ByteBuffer copy = bytes.duplicate();
  
          return new InputStream()
          {
              public int read()
              {
                  if (!copy.hasRemaining())
                      return -1;
  
                  return copy.get() & 0xFF;
              }
  
              @Override
              public int read(byte[] bytes, int off, int len)
              {
                  if (!copy.hasRemaining())
                      return -1;
  
                  len = Math.min(len, copy.remaining());
                  copy.get(bytes, off, len);
                  return len;
              }
  
              @Override
              public int available()
              {
                  return copy.remaining();
              }
          };
      }
  
      /*
       * Does not modify position or limit of buffer even temporarily
       * so this is safe even without duplication.
       */
      public static String bytesToHex(ByteBuffer bytes)
      {
          if (bytes.hasArray())
          {
              return Hex.bytesToHex(bytes.array(), bytes.arrayOffset() + bytes.position(), bytes.remaining());
          }
  
          final int offset = bytes.position();
          final int size = bytes.remaining();
          final char[] c = new char[size * 2];
          for (int i = 0; i < size; i++)
          {
              final int bint = bytes.get(i+offset);
              c[i * 2] = Hex.byteToChar[(bint & 0xf0) >> 4];
              c[1 + i * 2] = Hex.byteToChar[bint & 0x0f];
          }
          return Hex.wrapCharArray(c);
      }
  
      public static ByteBuffer hexToBytes(String str)
      {
          return ByteBuffer.wrap(Hex.hexToBytes(str));
      }
  
      /**
       * Compare two ByteBuffer at specified offsets for length.
       * Compares the non equal bytes as unsigned.
       * @param bytes1 First byte buffer to compare.
       * @param offset1 Position to start the comparison at in the first array.
       * @param bytes2 Second byte buffer to compare.
       * @param offset2 Position to start the comparison at in the second array.
       * @param length How many bytes to compare?
       * @return -1 if byte1 is less than byte2, 1 if byte2 is less than byte1 or 0 if equal.
       */
      public static int compareSubArrays(ByteBuffer bytes1, int offset1, ByteBuffer bytes2, int offset2, int length)
      {
          if (bytes1 == null)
              return bytes2 == null ? 0 : -1;
          if (bytes2 == null) return 1;
  
          assert bytes1.limit() >= offset1 + length : "The first byte array isn't long enough for the specified offset and length.";
          assert bytes2.limit() >= offset2 + length : "The second byte array isn't long enough for the specified offset and length.";
  
          for (int i = 0; i < length; i++)
          {
              byte byte1 = bytes1.get(offset1 + i);
              byte byte2 = bytes2.get(offset2 + i);
              if (byte1 == byte2)
                  continue;
              // compare non-equal bytes as unsigned
              return (byte1 & 0xFF) < (byte2 & 0xFF) ? -1 : 1;
          }
          return 0;
      }
  
      public static ByteBuffer bytes(InetAddress address)
      {
          return ByteBuffer.wrap(address.getAddress());
      }
  
      public static ByteBuffer bytes(UUID uuid)
      {
          return ByteBuffer.wrap(UUIDGen.decompose(uuid));
      }
  
      public static ByteBuffer bytes(TimeUUID uuid)
      {
          return ByteBuffer.wrap(UUIDGen.decompose(uuid.msb(), uuid.lsb()));
      }
  
      // Returns whether {@code prefix} is a prefix of {@code value}.
      public static boolean isPrefix(ByteBuffer prefix, ByteBuffer value)
      {
          if (prefix.remaining() > value.remaining())
              return false;
  
          int diff = value.remaining() - prefix.remaining();
          return prefix.equals(value.duplicate().limit(value.remaining() - diff));
      }
  
      public static boolean canMinimize(ByteBuffer buf)
      {
          return buf != null && (!buf.hasArray() || buf.array().length > buf.remaining());
          // Note: buf.array().length is different from buf.capacity() for sliced buffers.
      }
  
      /** trims size of bytebuffer to exactly number of bytes in it, to do not hold too much memory */
      public static ByteBuffer minimalBufferFor(ByteBuffer buf)
      {
          return !buf.hasArray() || buf.array().length > buf.remaining() ? ByteBuffer.wrap(getArray(buf)) : buf;
      }
  
      public static ByteBuffer[] minimizeBuffers(ByteBuffer[] src)
      {
          ByteBuffer[] dst = new ByteBuffer[src.length];
          for (int i=0; i<src.length; i++)
              dst[i] = src[i] != null ? minimalBufferFor(src[i]) : null;
          return dst;
      }
  
      public static boolean canMinimize(ByteBuffer[] src)
      {
          for (ByteBuffer buffer : src)
          {
              if (canMinimize(buffer))
                  return true;
          }
          return false;
      }
  
      public static int getUnsignedShort(ByteBuffer bb, int position)
      {
          return ((bb.get(position) & 0xFF) << 8) | (bb.get(position + 1) & 0xFF);
      }
  
      // Doesn't change bb position
      public static int getShortLength(ByteBuffer bb, int position)
      {
          return getUnsignedShort(bb, position);
      }
  
      // changes bb position
      public static int readShortLength(ByteBuffer bb)
      {
          int length = (bb.get() & 0xFF) << 8;
          return length | (bb.get() & 0xFF);
      }
  
      // changes bb position
      public static void writeShortLength(ByteBuffer bb, int length)
      {
          if (length > FBUtilities.MAX_UNSIGNED_SHORT)
              throw new IllegalArgumentException(String.format("Length %d > max length %d", length, FBUtilities.MAX_UNSIGNED_SHORT));
          bb.put((byte) ((length >> 8) & 0xFF));
          bb.put((byte) (length & 0xFF));
      }
  
      // changes bb position
      public static ByteBuffer readBytes(ByteBuffer bb, int length)
      {
          ByteBuffer copy = bb.duplicate();
          copy.limit(copy.position() + length);
          bb.position(bb.position() + length);
          return copy;
      }
  
      // changes bb position
      public static ByteBuffer readBytesWithShortLength(ByteBuffer bb)
      {
          int length = readShortLength(bb);
          return readBytes(bb, length);
      }
  
      /**
       * Ensure {@code buf} is large enough for {@code outputLength}. If not, it is cleaned up and a new buffer is allocated;
       * else; buffer has it's position/limit set appropriately.
       *
       * @param buf buffer to test the size of; may be null, in which case, a new buffer is allocated.
       * @param outputLength the minimum target size of the buffer
       * @param allowBufferResize true if resizing (reallocating) the buffer is allowed
       * @return {@code buf} if it was large enough, else a newly allocated buffer.
       */
      public static ByteBuffer ensureCapacity(ByteBuffer buf, int outputLength, boolean allowBufferResize)
      {
          BufferType bufferType = buf != null ? BufferType.typeOf(buf) : BufferType.ON_HEAP;
          return ensureCapacity(buf, outputLength, allowBufferResize, bufferType);
      }
  
      /**
       * Ensure {@code buf} is large enough for {@code outputLength}. If not, it is cleaned up and a new buffer is allocated;
       * else; buffer has it's position/limit set appropriately.
       *
       * @param buf buffer to test the size of; may be null, in which case, a new buffer is allocated.
       * @param outputLength the minimum target size of the buffer
       * @param allowBufferResize true if resizing (reallocating) the buffer is allowed
       * @param bufferType on- or off- heap byte buffer
       * @return {@code buf} if it was large enough, else a newly allocated buffer.
       */
      public static ByteBuffer ensureCapacity(ByteBuffer buf, int outputLength, boolean allowBufferResize, BufferType bufferType)
      {
          if (0 > outputLength)
              throw new IllegalArgumentException("invalid size for output buffer: " + outputLength);
          if (buf == null || buf.capacity() < outputLength)
          {
              if (!allowBufferResize)
                  throw new IllegalStateException(String.format("output buffer is not large enough for data: current capacity %d, required %d", buf.capacity(), outputLength));
              MemoryUtil.clean(buf);
              buf = bufferType.allocate(outputLength);
          }
          else
          {
              buf.position(0).limit(outputLength);
          }
          return buf;
      }
  
      /**
       * Check is the given buffer contains a given sub-buffer.
       *
       * @param buffer The buffer to search for sequence of bytes in.
       * @param subBuffer The buffer to match.
       *
       * @return true if buffer contains sub-buffer, false otherwise.
       */
      public static boolean contains(ByteBuffer buffer, ByteBuffer subBuffer)
      {
          int len = subBuffer.remaining();
          if (buffer.remaining() - len < 0)
              return false;
  
          // adapted form the JDK's String.indexOf()
          byte first = subBuffer.get(subBuffer.position());
          int max = buffer.position() + (buffer.remaining() - len);
  
          for (int i = buffer.position(); i <= max; i++)
          {
              /* Look for first character. */
              if (buffer.get(i) != first)
              {
                  while (++i <= max && buffer.get(i) != first)
                  {}
              }
  
              /* (maybe) Found first character, now look at the rest of v2 */
              if (i <= max)
              {
                  int j = i + 1;
                  int end = j + len - 1;
                  for (int k = 1 + subBuffer.position(); j < end && buffer.get(j) == subBuffer.get(k); j++, k++)
                  {}
  
                  if (j == end)
                      return true;
              }
          }
          return false;
      }
  
      public static boolean startsWith(ByteBuffer src, ByteBuffer prefix)
      {
          return startsWith(src, prefix, 0);
      }
  
      public static boolean endsWith(ByteBuffer src, ByteBuffer suffix)
      {
          return startsWith(src, suffix, src.remaining() - suffix.remaining());
      }
  
      private static boolean startsWith(ByteBuffer src, ByteBuffer prefix, int offset)
      {
          if (offset < 0)
              return false;
  
          int sPos = src.position() + offset;
          int pPos = prefix.position();
  
          if (src.remaining() - offset < prefix.remaining())
              return false;
  
          int len = Math.min(src.remaining() - offset, prefix.remaining());
  
          while (len-- > 0)
          {
              if (src.get(sPos++) != prefix.get(pPos++))
                  return false;
          }
  
          return true;
      }
  
      /**
       * Returns true if the buffer at the current position in the input matches given buffer.
       * If true, the input is positioned at the end of the consumed buffer.
       * If false, the position of the input is undefined.
       * <p>
       * The matched buffer is unchanged
       */
      public static boolean equalsWithShortLength(DataInput in, ByteBuffer toMatch) throws IOException
      {
          int length = readShortLength(in);
          if (length != toMatch.remaining())
              return false;
          int limit = toMatch.limit();
          for (int i = toMatch.position(); i < limit; ++i)
              if (toMatch.get(i) != in.readByte())
                  return false;
  
          return true;
      }
  
      public static void readFully(FileChannel channel, ByteBuffer dst, long position) throws IOException
      {
          while (dst.hasRemaining())
          {
              int read = channel.read(dst, position);
              if (read == -1)
                  throw new EOFException();
              position += read;
          }
      }
  
      public static void writeLeastSignificantBytes(long register, int bytes, ByteBuffer out)
      {
          writeMostSignificantBytes(register << ((8 - bytes)*8), bytes, out);
      }
  
      public static void writeMostSignificantBytes(long register, int bytes, ByteBuffer out)
      {
          int position = out.position();
          int limit = out.limit();
          if (limit - position < Long.BYTES)
          {
              writeMostSignificantBytesSlow(register, bytes, out);
          }
          else
          {
              out.putLong(position, register);
              out.position(position + bytes);
          }
      }
  
      @DontInline
      private static void writeMostSignificantBytesSlow(long register, int bytes, ByteBuffer out)
      {
          switch (bytes)
          {
              case 0:
                  break;
              case 1:
                  out.put((byte)(register >>> 56));
                  break;
              case 2:
                  out.putShort((short)(register >> 48));
                  break;
              case 3:
                  out.putShort((short)(register >> 48));
                  out.put((byte)(register >> 40));
                  break;
              case 4:
                  out.putInt((int)(register >> 32));
                  break;
              case 5:
                  out.putInt((int)(register >> 32));
                  out.put((byte)(register >> 24));
                  break;
              case 6:
                  out.putInt((int)(register >> 32));
                  out.putShort((short)(register >> 16));
                  break;
              case 7:
                  out.putInt((int)(register >> 32));
                  out.putShort((short)(register >> 16));
                  out.put((byte)(register >> 8));
                  break;
              case 8:
                  out.putLong(register);
                  break;
              default:
                  throw new IllegalArgumentException();
          }
      }
  
      public static long readLeastSignificantBytes(int bytes, ByteBuffer in)
      {
          if (bytes == 0)
              return 0L;
  
          int position = in.position();
          int limit = in.limit();
          if (limit - position < Long.BYTES)
          {
              return readLeastSignificantBytesSlow(bytes, in);
          }
          else
          {
              long result = in.getLong(position);
              in.position(position + bytes);
              return result >>> (64 - 8*bytes);
          }
      }
  
      @DontInline
      private static long readLeastSignificantBytesSlow(int bytes, ByteBuffer out)
      {
          switch (bytes)
          {
              case 0: return 0;
              case 1: return out.get() & 0xffL;
              case 2: return out.getShort() & 0xffffL;
              case 3: return ((out.getShort() & 0xffffL) << 8) | (out.get() & 0xffL);
              case 4: return out.getInt() & 0xffffffffL;
              case 5: return ((out.getInt() & 0xffffffffL) << 8) | (out.get() & 0xffL);
              case 6: return ((out.getInt() & 0xffffffffL) << 16) | (out.getShort() & 0xffffL);
              case 7: return ((out.getInt() & 0xffffffffL) << 24) | ((out.getShort() & 0xffffL) << 8) | (out.get() & 0xffL);
              case 8: return out.getLong();
              default: throw new IllegalArgumentException();
          }
      }
  
      public static final UnversionedSerializer<ByteBuffer> byteBufferSerializer = new UnversionedSerializer<ByteBuffer>()
      {
          @Override
          public void serialize(ByteBuffer bytes, DataOutputPlus out) throws IOException
          {
              writeWithVIntLength(bytes, out);
          }
  
          @Override
          public ByteBuffer deserialize(DataInputPlus in) throws IOException
          {
              return readWithVIntLength(in);
          }
  
          @Override
          public long serializedSize(ByteBuffer bytes)
          {
              return serializedSizeWithVIntLength(bytes);
          }
      };
  
      public static final UnversionedSerializer<ByteBuffer> nullableByteBufferSerializer = NullableSerializer.wrap(byteBufferSerializer);
  }