| // 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(); |
| } |
| |
| } |