public static <T extends Throwable> ByteBuf applyByteBufferFunctionToByteBuf(ByteBuf buf, ByteBufferFunction<T> fn)
      throws T {
    if (buf.nioBufferCount() == 1) {
      ByteBuffer buffer = buf.nioBuffer();
      buf.skipBytes(buffer.remaining());
      return Unpooled.wrappedBuffer(fn.apply(buffer));
    } else {
      ByteBuf temp = ByteBufAllocator.DEFAULT.heapBuffer(buf.readableBytes());
      try {
        temp.writeBytes(buf);
        ByteBuffer buffer = temp.nioBuffer();
        return Unpooled.wrappedBuffer(fn.apply(buffer));
      } finally {
        temp.release();
      }
    }
  }
Apply a {@link ByteBufferFunction} to a {@link ByteBuf} and return a {@link ByteBuf}. All the bytes in the input
{@link ByteBuf} will be consumed.
@param buf The input {@link ByteBuf}.
@param fn The {@link ByteBufferFunction}.
@param <T> The exception to throw in the {@code fn}.
@return A {@link ByteBuf}.
@throws T Exception thrown from {@code fn}.
ambry-utils/src/main/java/com/github/ambry/utils/Utils.java