/*
* 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;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.util.Arrays;
import static com.google.common.base.Charsets.UTF_8;
import org.apache.cassandra.io.util.FileDataInput;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.commons.lang.ArrayUtils;
/**
* 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(ArrayUtils.EMPTY_BYTE_ARRAY);
public static int compareUnsigned(ByteBuffer o1, ByteBuffer o2)
{
assert o1 != null;
assert o2 != null;
int minLength = Math.min(o1.remaining(), o2.remaining());
for (int x = 0, i = o1.position(), j = o2.position(); x < minLength; x++, i++, j++)
{
if (o1.get(i) == o2.get(j))
continue;
// compare non-equal bytes as unsigned
return (o1.get(i) & 0xFF) < (o2.get(j) & 0xFF) ? -1 : 1;
}
return (o1.remaining() == o2.remaining()) ? 0 : ((o1.remaining() < o2.remaining()) ? -1 : 1);
}
public static int compare(byte[] o1, ByteBuffer o2)
{
return compareUnsigned(ByteBuffer.wrap(o1), o2);
}
public static int compare(ByteBuffer o1, byte[] o2)
{
return compareUnsigned(o1, ByteBuffer.wrap(o2));
}
/**
* 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, 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, 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)
{
int length = buffer.remaining();
if (buffer.hasArray())
{
int start = buffer.position();
if (buffer.arrayOffset() == 0 && start == 0 && length == buffer.array().length)
return buffer.array();
else
return Arrays.copyOfRange(buffer.array(), start + buffer.arrayOffset(), start + length + buffer.arrayOffset());
}
// else, DirectByteBuffer.get() is the fastest route
byte[] bytes = new byte[length];
buffer.duplicate().get(bytes);
return bytes;
}
/**
* ByteBuffer adaptation of org.apache.commons.lang.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(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;
}
public static void arrayCopy(ByteBuffer buffer, int position, byte[] bytes, int offset, int length)
{
if (buffer.hasArray())
System.arraycopy(buffer.array(), buffer.arrayOffset() + position, bytes, offset, length);
else
((ByteBuffer) buffer.duplicate().position(position)).get(bytes, offset, length);
}
public static void writeWithLength(ByteBuffer bytes, DataOutput out) throws IOException
{
out.writeInt(bytes.remaining());
write(bytes, out); // writing data bytes to output source
}
public static void write(ByteBuffer buffer, DataOutput out) throws IOException
{
if (buffer.hasArray())
{
out.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
}
else
{
for (int i = buffer.position(); i < buffer.limit(); i++)
{
out.writeByte(buffer.get(i));
}
}
}
public static void writeWithShortLength(ByteBuffer buffer, DataOutput out)
{
int length = buffer.remaining();
assert 0 <= length && length <= FBUtilities.MAX_UNSIGNED_SHORT : length;
try
{
out.writeByte((length >> 8) & 0xFF);
out.writeByte(length & 0xFF);
write(buffer, out); // writing data bytes to output source
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
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);
}
/* @return An unsigned short in an integer. */
private static int readShortLength(DataInput in) throws IOException
{
int length = (in.readByte() & 0xFF) << 8;
return length | (in.readByte() & 0xFF);
}
/**
* @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));
}
/**
* @param in data input
* @return null
* @throws IOException if an I/O error occurs.
*/
public static ByteBuffer skipShortLength(DataInput in) throws IOException
{
int skip = readShortLength(in);
FileUtils.skipBytesFully(in, skip);
return null;
}
private static ByteBuffer read(DataInput in, int length) throws IOException
{
ByteBuffer array;
if (in instanceof FileDataInput)
{
array = ((FileDataInput) in).readBytes(length);
}
else
{
byte[] buff = new byte[length];
in.readFully(buff);
array = ByteBuffer.wrap(buff);
}
return array;
}
/**
* 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());
}
public static long toLong(ByteBuffer bytes)
{
return bytes.getLong(bytes.position());
}
public static float toFloat(ByteBuffer bytes)
{
return bytes.getFloat(bytes.position());
}
public static double toDouble(ByteBuffer bytes)
{
return bytes.getDouble(bytes.position());
}
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() throws IOException
{
if (!copy.hasRemaining())
return -1;
return copy.get() & 0xFF;
}
@Override
public int read(byte[] bytes, int off, int len) throws IOException
{
if (!copy.hasRemaining())
return -1;
len = Math.min(len, copy.remaining());
copy.get(bytes, off, len);
return len;
}
@Override
public int available() throws IOException
{
return copy.remaining();
}
};
}
public static String bytesToHex(ByteBuffer bytes)
{
StringBuilder sb = new StringBuilder();
for (int i = bytes.position(); i < bytes.limit(); i++)
{
int bint = bytes.get(i) & 0xff;
if (bint <= 0xF)
// toHexString does not 0 pad its results.
sb.append("0");
sb.append(Integer.toHexString(bint));
}
return sb.toString();
}
public static ByteBuffer hexToBytes(String str)
{
return ByteBuffer.wrap(FBUtilities.hexToBytes(str));
}
}