blob: 46f0ee95870998a21a9ede35c6b12560e38ebaed [file] [log] [blame]
// 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 streamer;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* This class represents a slice in a buffer.
*/
public class ByteBuffer {
public static final String SEQUENCE_NUMBER = "seq";
public byte data[];
public int offset = 0;
public int length = 0;
public int cursor = 0;
private int refCount = 1;
private Order order;
/**
* Create buffer of size no less than length. Buffer can be a bit larger than
* length. Offset also can be set to non-zero value to leave some place for
* future headers.
*/
public ByteBuffer(int minLength) {
// Get buffer of acceptable size from buffer pool
data = BufferPool.allocateNewBuffer(minLength);
offset = 0;
length = minLength;
}
public ByteBuffer(byte data[]) {
if (data == null)
throw new NullPointerException("Data must be non-null.");
this.data = data;
offset = 0;
length = data.length;
}
public ByteBuffer(byte[] data, int offset, int length) {
if (data == null)
throw new NullPointerException("Data must be non-null.");
this.data = data;
this.offset = offset;
this.length = length;
}
/**
* Create byte buffer of requested size with some space reserved for future
* headers.
*/
public ByteBuffer(int minLength, boolean reserveSpaceForHeader) {
// Get buffer of acceptable size from buffer pool
data = BufferPool.allocateNewBuffer(128 + minLength);
offset = 128; // 100 bytes should be enough for headers
length = minLength;
}
/**
* Create empty buffer with given order only.
*/
public ByteBuffer(Order order) {
this.order = order;
}
public void setOrder(Order order) {
this.order = order;
}
public Order getOrder() {
return order;
}
@Override
public String toString() {
return toString(length);
}
/**
* Return string representation of this byte buffer.
*
* @param maxLength
* number of bytes to show in string
*/
public String toString(int maxLength) {
return "ByteRange(){offset=" + offset + ", length=" + length + ", cursor=" + cursor + ", data=" + ((data == null) ? "null" : toHexString(maxLength))
+ ((metadata == null || metadata.size() == 0) ? "" : ", metadata=" + metadata) + "}";
}
/**
* Return string representation of this byte buffer as hexadecimal numbers,
* e.g. "[0x01, 0x02]".
*
* @param maxLength
* number of bytes to show in string
*/
public String toHexString(int maxLength) {
StringBuilder builder = new StringBuilder(maxLength * 6);
builder.append('[');
for (int i = 0; i < maxLength && i < length; i++) {
if (i > 0)
builder.append(", ");
int b = data[offset + i] & 0xff;
builder.append("0x" + ((b < 16) ? "0" : "") + Integer.toString(b, 16));
}
builder.append(']');
return builder.toString();
}
/**
* Return string representation of this byte buffer as hexadecimal numbers,
* e.g. "01 02".
*
* @param maxLength
* number of bytes to show in string
*/
public String toPlainHexString(int maxLength) {
StringBuilder builder = new StringBuilder(maxLength * 3);
for (int i = 0; i < maxLength && i < length; i++) {
if (i > 0)
builder.append(" ");
int b = data[offset + i] & 0xff;
builder.append(String.format("%02x", b));
}
return builder.toString();
}
/**
* Return string representation of this byte buffer as dump, e.g.
* "0000 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 .................".
*
* @param maxLength
* number of bytes to show in string
*/
public String dump() {
StringBuilder builder = new StringBuilder(length * 4);
int i = addBytesToBuilder(builder);
int end = i - 1;
if (end % 16 != 15) {
int begin = end & ~0xf;
for (int j = 0; j < (15 - (end % 16)); j++) {
builder.append(" ");
}
builder.append(' ');
builder.append(toASCIIString(begin, end));
builder.append('\n');
}
return builder.toString();
}
protected int addBytesToBuilder(StringBuilder builder) {
int i = 0;
for (; i < length; i++) {
if (i % 16 == 0) {
builder.append(String.format("%04x", i));
}
builder.append(' ');
int b = data[offset + i] & 0xff;
builder.append(String.format("%02x", b));
if (i % 16 == 15) {
builder.append(' ');
builder.append(toASCIIString(i - 15, i));
builder.append('\n');
}
}
return i;
}
private String toASCIIString(int start, int finish) {
StringBuffer sb = new StringBuffer(16);
for (int i = start; i <= finish; i++) {
char ch = (char)data[offset + i];
if (ch < ' ' || ch >= 0x7f) {
sb.append('.');
} else {
sb.append(ch);
}
}
return sb.toString();
}
/**
* Return string representation of this byte buffer as hexadecimal numbers,
* e.g. "01 02".
*/
public String toPlainHexString() {
return toPlainHexString(length);
}
public void extend(int newLength) {
if (data.length < newLength)
Arrays.copyOf(data, newLength);
}
public void ref() {
refCount++;
}
public void unref() {
refCount--;
if (refCount == 0) {
// Return buffer to buffer pool
BufferPool.recycleBuffer(data);
data = null;
}
}
public boolean isSoleOwner() {
return refCount == 1;
}
/**
* Create shared lightweight copy of part of this buffer.
*/
public ByteBuffer slice(int offset, int length, boolean copyMetadata) {
ref();
if (this.length < (offset + length))
throw new RuntimeException("Length of region is larger that length of this buffer. Buffer length: " + this.length + ", offset: " + offset + ", new region length: "
+ length + ".");
ByteBuffer slice = new ByteBuffer(data, this.offset + offset, length);
if (copyMetadata && metadata != null)
slice.metadata = new HashMap<String, Object>(metadata);
return slice;
}
private Map<String, Object> metadata = null;
public Object putMetadata(String key, Object value) {
if (metadata == null)
metadata = new HashMap<String, Object>();
return metadata.put(key, value);
}
public Object getMetadata(String key) {
return (metadata != null) ? metadata.get(key) : null;
}
/**
* Create new buffer, which holds data from both buffers. Expensive operation.
*
* @TODO if only one reference to this ByteBuffer exists, then extend this
* buffer instead of creating new buffer
* @TODO support list of buffers to avoid expensive joins until absolute
* necessary
*/
public ByteBuffer join(ByteBuffer buf) {
// Extend byte array for new data
int newLength = length + buf.length;
byte newData[] = new byte[newLength];
// Copy data from our buffer
System.arraycopy(data, offset, newData, 0, length);
// Copy data from other buffer
System.arraycopy(buf.data, buf.offset, newData, length, buf.length);
ByteBuffer newBuf = new ByteBuffer(newData);
// Copy our (older) metadata to new buffer, because handler might store some
// metadata in buffer, which is pushed back.
if (metadata != null)
newBuf.metadata = new HashMap<String, Object>(metadata);
return newBuf;
}
/**
* Copy used portion of buffer to new byte array. Expensive operation.
*/
public byte[] toByteArray() {
return Arrays.copyOfRange(data, offset, offset + length);
}
public short[] toShortArray() {
if (length % 2 != 0)
throw new ArrayIndexOutOfBoundsException("Length of byte array must be dividable by 2 without remainder. Array length: " + length + ", remainder: " + (length % 2)
+ ".");
short[] buf = new short[length / 2];
for (int i = 0, j = offset; i < buf.length; i++, j += 2) {
buf[i] = (short)((data[j + 0] & 0xFF) | ((data[j + 1] & 0xFF) << 8));
}
return buf;
}
/**
* Return array of int's in little endian order.
*/
public int[] toIntLEArray() {
if (length % 4 != 0)
throw new ArrayIndexOutOfBoundsException("Length of byte array must be dividable by 4 without remainder. Array length: " + length + ", remainder: " + (length % 4)
+ ".");
int[] buf = new int[length / 4];
for (int i = 0, j = offset; i < buf.length; i++, j += 4) {
buf[i] = (data[j + 0] & 0xFF) | ((data[j + 1] & 0xFF) << 8) | ((data[j + 2] & 0xFF) << 16) | ((data[j + 3] & 0xFF) << 24);
}
return buf;
}
/**
* Return array of int's in little endian order, but use only 3 bytes per int
* (3RGB).
*/
public int[] toInt3LEArray() {
if (length % 3 != 0)
throw new ArrayIndexOutOfBoundsException("Length of byte array must be dividable by 3 without remainder. Array length: " + length + ", remainder: " + (length % 3)
+ ".");
int[] buf = new int[length / 3];
for (int i = 0, j = offset; i < buf.length; i++, j += 3) {
buf[i] = (data[j + 0] & 0xFF) | ((data[j + 1] & 0xFF) << 8) | ((data[j + 2] & 0xFF) << 16);
}
return buf;
}
/**
* Helper method for test cases to convert array of byte arrays to array of
* byte buffers.
*/
public static ByteBuffer[] convertByteArraysToByteBuffers(byte[]... bas) {
ByteBuffer bufs[] = new ByteBuffer[bas.length];
int i = 0;
for (byte[] ba : bas) {
bufs[i++] = new ByteBuffer(ba);
}
return bufs;
}
/**
* Read signed int in network order. Cursor is advanced by 4.
*/
public int readSignedInt() {
if (cursor + 4 > length)
throw new ArrayIndexOutOfBoundsException("Cannot read 4 bytes from this buffer: " + this + ".");
int result = (((data[offset + cursor] & 0xff) << 24) + ((data[offset + cursor + 1] & 0xff) << 16) + ((data[offset + cursor + 2] & 0xff) << 8) + (data[offset + cursor + 3] & 0xff));
cursor += 4;
return result;
}
/**
* Read signed int in little endian order. Cursor is advanced by 4.
*/
public int readSignedIntLE() {
if (cursor + 4 > length)
throw new ArrayIndexOutOfBoundsException("Cannot read 4 bytes from this buffer: " + this + ".");
int result = (((data[offset + cursor + 3] & 0xff) << 24) + ((data[offset + cursor + 2] & 0xff) << 16) + ((data[offset + cursor + 1] & 0xff) << 8) + (data[offset + cursor] & 0xff));
cursor += 4;
return result;
}
/**
* Read unsigned int in little endian order. Cursor is advanced by 4.
*/
public long readUnsignedIntLE() {
if (cursor + 4 > length)
throw new ArrayIndexOutOfBoundsException("Cannot read 4 bytes from this buffer: " + this + ".");
long result = (((long)(data[offset + cursor + 3] & 0xff) << 24) + ((long)(data[offset + cursor + 2] & 0xff) << 16) + ((long)(data[offset + cursor + 1] & 0xff) << 8) + (data[offset
+ cursor + 0] & 0xff));
cursor += 4;
return result;
}
/**
* Read unsigned int in network order. Cursor is advanced by 4.
*/
public long readUnsignedInt() {
if (cursor + 4 > length)
throw new ArrayIndexOutOfBoundsException("Cannot read 4 bytes from this buffer: " + this + ".");
byte value1 = data[offset + cursor + 0];
byte value2 = data[offset + cursor + 1];
byte value3 = data[offset + cursor + 2];
byte value4 = data[offset + cursor + 3];
long result = calculateUnsignedInt(value1, value2, value3, value4);
cursor += 4;
return result;
}
protected static long calculateUnsignedInt(byte value1, byte value2, byte value3, byte value4) {
return (((long)calculateUnsignedByte(value1)) << 24)
+ (((long)calculateUnsignedByte(value2)) << 16)
+ (((long)calculateUnsignedByte(value3)) << 8)
+ calculateUnsignedByte(value4);
}
/**
* Read signed int in variable length format. Top most bit of each byte
* indicates that next byte contains additional bits. Cursor is advanced by
* 1-5 bytes.
*/
public int readVariableSignedIntLE() {
int result = 0;
for (int shift = 0; shift < 32; shift += 7) {
int b = readUnsignedByte();
result |= (b & 0x7f) << shift;
if ((b & 0x80) == 0)
break;
}
return result;
}
/**
* Read unsigned int in network order in variable length format. Cursor is
* advanced by 1 to 4 bytes.
*
* Two most significant bits of first byte indicates length of field: 0x00 - 1
* byte, 0x40 - 2 bytes, 0x80 - 3 bytes, 0xc0 - 4 bytes.
*
* @see http://msdn.microsoft.com/en-us/library/cc241614.aspx
*/
public int readEncodedUnsignedInt() {
int firstByte = readUnsignedByte();
int result;
switch (firstByte & 0xc0) {
default:
case 0x00:
result = firstByte & 0x3f;
break;
case 0x40:
result = (firstByte & 0x3f << 8) | readUnsignedByte();
break;
case 0x80:
result = (((firstByte & 0x3f << 8) | readUnsignedByte()) << 8) | readUnsignedByte();
break;
case 0xc0:
result = ((((firstByte & 0x3f << 8) | readUnsignedByte()) << 8) | readUnsignedByte() << 8) | readUnsignedByte();
break;
}
return result;
}
/**
* Read unsigned byte. Cursor is advanced by 1.
*/
public int readUnsignedByte() {
if (cursor + 1 > length)
throw new ArrayIndexOutOfBoundsException("Cannot read 1 byte from this buffer: " + this + ".");
byte value = data[offset + cursor];
int b = calculateUnsignedByte(value);
cursor += 1;
return b;
}
protected static int calculateUnsignedByte(byte value) {
return value & 0xff;
}
/**
* Read signed byte. Cursor is advanced by 1.
*/
public byte readSignedByte() {
if (cursor + 1 > length)
throw new ArrayIndexOutOfBoundsException("Cannot read 1 byte from this buffer: " + this + ".");
byte b = data[offset + cursor];
cursor += 1;
return b;
}
/**
* Read unsigned short in network order. Cursor is advanced by 2.
*/
public int readUnsignedShort() {
if (cursor + 2 > length)
throw new ArrayIndexOutOfBoundsException("Cannot read 2 bytes from this buffer: " + this + ".");
byte value1 = data[offset + cursor];
byte value2 = data[offset + cursor + 1];
int result = calculateUnsignedShort(value1, value2);
cursor += 2;
return result;
}
protected static int calculateUnsignedShort(byte value1, byte value2) {
return (calculateUnsignedByte(value1) << 8) | calculateUnsignedByte(value2);
}
/**
* Read signed short in little endian order. Cursor is advanced by 2.
*/
public short readSignedShortLE() {
if (cursor + 2 > length)
throw new ArrayIndexOutOfBoundsException("Cannot read 2 bytes from this buffer: " + this + ".");
short result = (short)(((data[offset + cursor + 1] & 0xff) << 8) | (data[offset + cursor] & 0xff));
cursor += 2;
return result;
}
/**
* Read signed short in network order. Cursor is advanced by 2.
*/
public short readSignedShort() {
if (cursor + 2 > length)
throw new ArrayIndexOutOfBoundsException("Cannot read 2 bytes from this buffer: " + this + ".");
byte value1 = data[offset + cursor + 0];
byte value2 = data[offset + cursor + 1];
short result = calculateSignedShort(value1, value2);
cursor += 2;
return result;
}
protected static short calculateSignedShort(byte value1, byte value2) {
return (short)calculateUnsignedShort(value1, value2);
}
/**
* Read unsigned short in network order in variable length format. Cursor is
* advanced by 1 or 2 bytes.
*
* Most significant bit of first byte indicates length of field: 0 - 1 byte, 1
* - 2 bytes.
*/
public int readVariableUnsignedShort() {
int firstByte = readUnsignedByte();
int result;
if ((firstByte & 0x80) == 0) {
result = firstByte & 0x7f;
} else {
int secondByte = readUnsignedByte();
result = (((firstByte & 0x7f) << 8) | secondByte);
}
return result;
}
/**
* Read integer in BER format.
*
* Most significant bit of first byte indicates type of date in first byte: if
* 0, then byte contains length (up to 7f), if 1, then byte contains number of
* following bytes with value in network order. Value 0x80 means unlimited
* length, which ends with two zero bytes (0x00 0x00) sequence.
*
* If -1 is returned by this method, then caller must seek two consecutive
* zeroes in buffer and consume all that data from buffer, including these two
* zeroes, but caller should not parse these two zeroes.
*
* @return length or -1, for unlimited length
*/
public long readBerLength() {
int firstByte = readUnsignedByte();
long result;
if ((firstByte & 0x80) == 0) {
result = firstByte & 0x7f;
} else {
int intLength = firstByte & 0x7f;
if (intLength != 0)
result = readUnsignedVarInt(intLength);
else
return -1;
}
return result;
}
/**
* Read integer in BER format.
*
* Most significant bit of first byte indicates type of date in first byte: if
* 0, then byte contains length (up to 7f), if 1, then byte contains number of
* following bytes with value in network order.
*/
public void writeBerLength(long length) {
if (length < 0)
throw new RuntimeException("Length cannot be less than zero: " + length + ". Data: " + this + ".");
if (length < 0x80) {
writeByte((int)length);
} else {
if (length < 0xff) {
writeByte(0x81);
writeByte((int)length);
} else if (length <= 0xffFF) {
writeByte(0x82);
writeShort((int)length);
} else if (length <= 0xffFFff) {
writeByte(0x83);
writeByte((int)(length >> 16));
writeShort((int)length);
} else if (length <= 0xffFFffFFL) {
writeByte(0x84);
writeInt((int)length);
} else if (length <= 0xffFFffFFffL) {
writeByte(0x85);
writeByte((int)(length >> 32));
writeInt((int)length);
} else if (length <= 0xffFFffFFffFFL) {
writeByte(0x86);
writeShort((int)(length >> 32));
writeInt((int)length);
} else if (length <= 0xffFFffFFffFFffL) {
writeByte(0x87);
writeByte((int)(length >> (32 + 16)));
writeShort((int)(length >> 32));
writeInt((int)length);
} else {
writeByte(0x88);
writeInt((int)(length >> 32));
writeInt((int)length);
}
}
}
/**
* Read signed variable length integers in network order.
*
* @param len
* length of integer
*/
public long readSignedVarInt(int len) {
long value = 0;
switch (len) {
case 0:
value = 0;
break;
case 1:
value = readSignedByte();
break;
case 2:
value = readSignedShort();
break;
case 3:
value = (readSignedByte() << 16) | readUnsignedShort();
break;
case 4:
value = readSignedInt();
break;
case 5:
value = readSignedByte() | readUnsignedInt();
break;
case 6:
value = readSignedShort() | readUnsignedInt();
break;
case 7:
value = (readSignedByte() << 24) | readUnsignedShort() | readUnsignedInt();
break;
case 8:
value = readSignedLong();
break;
default:
throw new RuntimeException("Cannot read integers which are more than 8 bytes long. Length: " + len + ". Data: " + this + ".");
}
return value;
}
/**
* Read unsigned variable length integers in network order. Values, which are
* larger than 0x7FffFFffFFffFFff cannot be parsed by this method.
*/
public long readUnsignedVarInt(int len) {
long value = 0;
switch (len) {
case 0:
value = 0;
break;
case 1:
value = readUnsignedByte();
break;
case 2:
value = readUnsignedShort();
break;
case 3:
value = (readUnsignedByte() << 16) | readUnsignedShort();
break;
case 4:
value = readUnsignedInt();
break;
case 5:
value = readUnsignedByte() | readUnsignedInt();
break;
case 6:
value = readUnsignedShort() | readUnsignedInt();
break;
case 7:
value = (readUnsignedByte() << 16) | readUnsignedShort() | readUnsignedInt();
break;
case 8:
value = readSignedLong();
if (value < 0)
throw new RuntimeException("Cannot read 64 bit integers which are larger than 0x7FffFFffFFffFFff, because of lack of unsinged long type in Java. Value: " + value
+ ". Data: " + this + ".");
break;
default:
throw new RuntimeException("Cannot read integers which are more than 8 bytes long. Length: " + len + ". Data: " + this + ".");
}
return value;
}
/**
* Read unsigned short in little endian order. Cursor is advanced by 2.
*/
public int readUnsignedShortLE() {
if (cursor + 2 > length)
throw new ArrayIndexOutOfBoundsException("Cannot read 2 bytes from this buffer: " + this + ".");
int result = (((data[offset + cursor + 1] & 0xff) << 8) | (data[offset + cursor] & 0xff));
cursor += 2;
return result;
}
/**
* Read unsigned short in network order in variable length format. Cursor is
* advanced by 1 or 2 bytes.
*
* Most significant bit of first byte indicates length of field: 0x00 - 1
* byte, 0x80 - 2 bytes.
*
* @see http://msdn.microsoft.com/en-us/library/cc241612.aspx
*/
public int readEncodedUnsignedShort() {
int firstByte = readUnsignedByte();
int result;
if ((firstByte & 0x80) == 0)
result = firstByte & 0x7f;
else {
int secondByte = readUnsignedByte();
result = (((firstByte & 0x7f) << 8) | secondByte);
}
return result;
}
/**
* Read signed short in network order in variable length format. Cursor is
* advanced by 1 or 2 bytes.
*
* Most significant bit of first byte indicates length of field: 0x00 - 1
* byte, 0x80 - 2 bytes. Second most significant bit indicates is value
* positive or negative.
*
* @see http://msdn.microsoft.com/en-us/library/cc241613.aspx
*/
public int readEncodedSignedShort() {
int firstByte = readUnsignedByte();
int result;
if ((firstByte & 0x80) == 0)
result = firstByte & 0x3f;
else {
int secondByte = readUnsignedByte();
result = (((firstByte & 0x3f) << 8) | secondByte);
}
if ((firstByte & 0x40) > 0)
return -result;
else
return result;
}
/**
* Read signed long in little endian order. Cursor is advanced by 8 bytes.
*/
public long readSignedLongLE() {
return ((readSignedIntLE()) & 0xffFFffFFL) | (((long)readSignedIntLE()) << 32);
}
/**
* Read signed long in network order. Cursor is advanced by 8 bytes.
*/
public long readSignedLong() {
return (((long)readSignedInt()) << 32) | ((readSignedInt()) & 0xffFFffFFL);
}
/**
* Read string from buffer. Cursor is advanced by string length.
*/
public String readString(int length, Charset charset) {
if (cursor + length > this.length)
throw new ArrayIndexOutOfBoundsException("Cannot read " + length + " bytes from this buffer: " + this + ".");
String string = new String(data, offset + cursor, length, charset);
cursor += length;
return string;
}
/**
* Read string with '\0' character at end.
*/
public String readVariableString(Charset charset) {
int start = cursor;
// Find end of string
while (readUnsignedByte() != 0) {
}
String string = new String(data, offset + start, cursor - start - 1, charset);
return string;
}
/**
* Read wide string with wide '\0' character at end.
*/
public String readVariableWideString(Charset charset) {
int start = cursor;
// Find end of string
while (readUnsignedShortLE() != 0) {
}
String string = new String(data, offset + start, cursor - start - 2, charset);
return string;
}
/**
* Get bytes as lightweight slice. Cursor is advanced by data length.
*/
public ByteBuffer readBytes(int dataLength) {
if (cursor + dataLength > length)
throw new ArrayIndexOutOfBoundsException("Cannot read " + dataLength + " bytes from this buffer: " + this + ".");
ByteBuffer slice = slice(cursor, dataLength, false);
cursor += dataLength;
return slice;
}
/**
* Cursor is advanced by given number of bytes.
*/
public void skipBytes(int numOfBytes) {
if (cursor + numOfBytes > length)
throw new ArrayIndexOutOfBoundsException("Cannot read " + numOfBytes + " bytes from this buffer: " + this + ".");
cursor += numOfBytes;
}
/**
* Write byte. Cursor is advanced by 1.
*/
public void writeByte(int b) {
if (cursor + 1 > length)
throw new ArrayIndexOutOfBoundsException("Cannot write 1 byte to this buffer: " + this + ".");
data[offset + cursor] = (byte)b;
cursor += 1;
}
/**
* Write short in network order. Cursor is advanced by 2.
*/
public void writeShort(int x) {
if (cursor + 2 > length)
throw new ArrayIndexOutOfBoundsException("Cannot write 2 bytes to this buffer: " + this + ".");
data[offset + cursor] = (byte)(x >> 8);
data[offset + cursor + 1] = (byte)x;
cursor += 2;
}
/**
* Write short in little endian order. Cursor is advanced by 2.
*/
public void writeShortLE(int x) {
if (cursor + 2 > length)
throw new ArrayIndexOutOfBoundsException("Cannot write 2 bytes to this buffer: " + this + ".");
data[offset + cursor + 1] = (byte)(x >> 8);
data[offset + cursor] = (byte)x;
cursor += 2;
}
/**
* Write int in network order. Cursor is advanced by 4.
*/
public void writeInt(int i) {
if (cursor + 4 > length)
throw new ArrayIndexOutOfBoundsException("Cannot write 4 bytes to this buffer: " + this + ".");
data[offset + cursor] = (byte)(i >> 24);
data[offset + cursor + 1] = (byte)(i >> 16);
data[offset + cursor + 2] = (byte)(i >> 8);
data[offset + cursor + 3] = (byte)i;
cursor += 4;
}
public void writeIntLE(int i) {
if (cursor + 4 > length)
throw new ArrayIndexOutOfBoundsException("Cannot write 4 bytes to this buffer: " + this + ".");
data[offset + cursor] = (byte)i;
data[offset + cursor + 1] = (byte)(i >> 8);
data[offset + cursor + 2] = (byte)(i >> 16);
data[offset + cursor + 3] = (byte)(i >> 24);
cursor += 4;
}
/**
* Write int in variable length format. Cursor is advanced by number of bytes
* written (1-5).
*
* Topmost bit of each byte is set to 1 to indicate that next byte has data.
*/
public void writeVariableIntLE(int i) {
while (i != 0) {
// Get lower bits of number
int b = i & 0x7f;
i >>= 7;
if (i > 0)
// Set topmost bit of byte to indicate that next byte(s) contains
// remainder bits
b |= 0x80;
writeByte(b);
}
}
/**
* Write short in variable length format. Cursor is advanced by number of
* bytes written (1-2).
*
* Topmost bit of first byte is set to 1 to indicate that next byte has data.
*/
public void writeVariableShort(int length) {
if (length > 0x7f | length < 0)
writeShort(length | 0x8000);
else
writeByte(length);
}
/**
* Prepend given data to this byte buffer.
*/
public void prepend(ByteBuffer buf) {
prepend(buf.data, buf.offset, buf.length);
}
/**
* Prepend given data to this byte buffer.
*/
public void prepend(byte[] data) {
prepend(data, 0, data.length);
}
/**
* Prepend given data to this byte buffer.
*/
public void prepend(byte[] data, int offset, int length) {
if (!isSoleOwner()) {
throw new RuntimeException("Create full copy of this byte buffer data for modification. refCount: " + refCount + ".");
}
// If there is no enough space for header to prepend
if (!(this.offset >= length)) {
throw new RuntimeException("Reserve data to have enough space for header.");
}
// Copy header
System.arraycopy(data, offset, this.data, this.offset - length, length);
// Extend byte range to include header
this.offset -= length;
this.length += length;
cursor += length;
}
/**
* Write byte representation of given string, without string terminators (zero
* or zeroes at end of string).
*/
public void writeString(String str, Charset charset) {
writeBytes(str.getBytes(charset));
}
/**
* Write string of fixed size. When string is shorted, empty space is filled
* with zeros. When string is larger, it is truncated.
*/
public void writeFixedString(int length, String str, Charset charset) {
byte[] bytes = str.getBytes(charset);
writeBytes(bytes, 0, Math.min(bytes.length, length));
for (int i = bytes.length; i < length; i++)
writeByte(0);
}
public void writeBytes(ByteBuffer buf) {
writeBytes(buf.data, buf.offset, buf.length);
}
public void writeBytes(byte[] bytes) {
writeBytes(bytes, 0, bytes.length);
}
public void writeBytes(byte[] bytes, int offset, int length) {
System.arraycopy(bytes, offset, data, this.offset + cursor, length);
cursor += length;
}
/**
* Reduce length of buffer to cursor position.
*/
public void trimAtCursor() {
length = cursor;
}
/**
* Rewind cursor to beginning of the buffer.
*/
public void rewindCursor() {
cursor = 0;
}
/**
* Read RGB color in LE order. Cursor is advanced by 3.
*
* @return color as int, with red in lowest octet.
*/
public int readRGBColor() {
return readUnsignedByte() | (readUnsignedByte() << 8) | (readUnsignedByte() << 16);
}
public void assertThatBufferIsFullyRead() {
if (cursor != length)
throw new RuntimeException("Data in buffer is not read fully. Buf: " + this + ".");
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
int end = offset + length;
for (int i = offset; i < end; i++)
result = 31 * result + data[i];
result = prime * result + length;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
// Does not work in case of anonymous type(s)
if (getClass() != obj.getClass())
return false;
ByteBuffer other = (ByteBuffer)obj;
if (length != other.length)
return false;
for (int i = 0; i < length; i++)
if (data[offset + i] != other.data[other.offset + i])
return false;
return true;
}
/**
* Return length of data left after cursor.
*/
public int remainderLength() {
if (length >= cursor)
return length - cursor;
else
throw new RuntimeException("Inconsistent state of buffer: cursor is after end of buffer: " + this + ".");
}
public Set<String> getMetadataKeys() {
if (metadata != null)
return metadata.keySet();
else
return new HashSet<String>(0);
}
/**
* Return unsigned value of byte at given position relative to cursor. Cursor
* is not advanced.
*/
public int peekUnsignedByte(int i) {
return data[offset + cursor + i] & 0xff;
}
/**
* Trim few first bytes.
*/
public void trimHeader(int length) {
offset += length;
this.length -= length;
rewindCursor();
}
}