/* * Copyright 2014 NAVER Corp. * * Licensed 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 com.navercorp.pinpoint.common.buffer; import com.navercorp.pinpoint.common.util.BytesUtils; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; /** * @author emeroad */ public class FixedBuffer implements Buffer { protected static final int NULL = -1; protected byte[] buffer; protected int offset; public FixedBuffer() { this(32); } public FixedBuffer(final int bufferSize) { if (bufferSize < 0) { throw new IndexOutOfBoundsException("negative bufferSize:" + bufferSize); } this.buffer = new byte[bufferSize]; this.offset = 0; } public FixedBuffer(final byte[] buffer) { if (buffer == null) { throw new NullPointerException("buffer must not be null"); } this.buffer = buffer; this.offset = 0; } @Override public void putPadBytes(byte[] bytes, int totalLength) { if (bytes == null) { bytes = EMPTY; } if (bytes.length > totalLength) { throw new IndexOutOfBoundsException("bytes too big:" + bytes.length + " totalLength:" + totalLength); } putBytes(bytes); final int padSize = totalLength - bytes.length; if (padSize > 0) { putPad(padSize); } } private void putPad(int padSize) { for (int i = 0; i < padSize; i++) { putByte((byte)0); } } @Override public void putPrefixedBytes(final byte[] bytes) { if (bytes == null) { putSVInt(NULL); } else { putSVInt(bytes.length); putBytes(bytes); } } @Override public void put2PrefixedBytes(final byte[] bytes) { if (bytes == null) { putShort((short)NULL); } else { if (bytes.length > Short.MAX_VALUE) { throw new IndexOutOfBoundsException("too large bytes length:" + bytes.length); } putShort((short)bytes.length); putBytes(bytes); } } @Override public void put4PrefixedBytes(final byte[] bytes) { if (bytes == null) { putInt(NULL); } else { putInt(bytes.length); putBytes(bytes); } } @Override public void putPadString(String string, int totalLength) { final byte[] bytes = BytesUtils.toBytes(string); putPadBytes(bytes, totalLength); } @Override public void putPrefixedString(final String string) { final byte[] bytes = BytesUtils.toBytes(string); putPrefixedBytes(bytes); } @Override public void put2PrefixedString(final String string) { final byte[] bytes = BytesUtils.toBytes(string); if (bytes == null) { putShort((short)NULL); return; } if (bytes.length > Short.MAX_VALUE) { throw new IndexOutOfBoundsException("too large String size:" + bytes.length); } put2PrefixedBytes(bytes); } @Override public void put4PrefixedString(final String string) { final byte[] bytes = BytesUtils.toBytes(string); if (bytes == null) { putInt(NULL); return; } put4PrefixedBytes(bytes); } @Override public void putByte(final byte v) { this.buffer[offset++] = v; } @Override public void putBoolean(final boolean v) { if (v) { this.buffer[offset++] = BOOLEAN_TRUE; } else { this.buffer[offset++] = BOOLEAN_FALSE; } } @Override public void putInt(final int v) { this.offset = BytesUtils.writeInt(v, buffer, offset); } @Override public void putVInt(int v) { if (v >= 0) { putVar32(v); } else { putVar64((long) v); } } @Override public void putSVInt(int v) { this.offset = BytesUtils.writeSVar32(v, buffer, offset); } private void putVar32(int v) { this.offset = BytesUtils.writeVar32(v, buffer, offset); } @Override public void putShort(final short v) { this.offset = BytesUtils.writeShort(v, buffer, offset); } @Override public void putLong(final long v) { this.offset = BytesUtils.writeLong(v, buffer, offset); } @Override public void putVLong(long v) { putVar64(v); } @Override public void putSVLong(long v) { putVar64(BytesUtils.longToZigZag(v)); } private void putVar64(long v) { this.offset = BytesUtils.writeVar64(v, buffer, offset); } @Override public void putDouble(double v) { putLong(Double.doubleToRawLongBits(v)); } @Override public void putVDouble(double v) { putVLong(Double.doubleToRawLongBits(v)); } @Override public void putSVDouble(double v) { putSVLong(Double.doubleToRawLongBits(v)); } @Override public void putBytes(final byte[] v) { if (v == null) { throw new NullPointerException("v must not be null"); } System.arraycopy(v, 0, buffer, offset, v.length); this.offset = offset + v.length; } @Override public byte getByte(int index) { return this.buffer[offset]; } @Override public byte readByte() { return this.buffer[offset++]; } @Override public int readUnsignedByte() { return readByte() & 0xff; } @Override public boolean readBoolean() { final byte b = readByte(); return b == BOOLEAN_TRUE; } @Override public int readInt() { final int i = BytesUtils.bytesToInt(buffer, offset); this.offset = this.offset + 4; return i; } @Override public int readVInt() { // borrowing the protocol buffer's concept of variable-length encoding // copy https://github.com/google/protobuf 2.6.1 // CodedInputStream.java -> int readRawVarint32() // See implementation notes for readRawVarint64 fastpath: { int pos = this.offset; final int bufferSize = this.buffer.length; if (bufferSize == pos) { break fastpath; } final byte[] buffer = this.buffer; int x; if ((x = buffer[pos++]) >= 0) { this.offset = pos; return x; } else if (bufferSize - pos < 9) { break fastpath; } else if ((x ^= (buffer[pos++] << 7)) < 0) { x ^= (~0 << 7); } else if ((x ^= (buffer[pos++] << 14)) >= 0) { x ^= (~0 << 7) ^ (~0 << 14); } else if ((x ^= (buffer[pos++] << 21)) < 0) { x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21); } else { int y = buffer[pos++]; x ^= y << 28; x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21) ^ (~0 << 28); if (y < 0 && buffer[pos++] < 0 && buffer[pos++] < 0 && buffer[pos++] < 0 && buffer[pos++] < 0 && buffer[pos++] < 0) { break fastpath; // Will throw malformedVarint() } } this.offset = pos; return x; } return (int) readVar64SlowPath(); } /** Variant of readRawVarint64 for when uncomfortably close to the limit. */ /* Visible for testing */ long readVar64SlowPath() { int copyOffset = this.offset; long result = 0; for (int shift = 0; shift < 64; shift += 7) { final byte b = this.buffer[copyOffset++]; result |= (long) (b & 0x7F) << shift; if ((b & 0x80) == 0) { this.offset = copyOffset; return result; } } throw new IllegalArgumentException("invalid varLong. start offset:" + this.offset + " readOffset:" + offset); } @Override public int readSVInt() { return BytesUtils.zigzagToInt(readVInt()); } @Deprecated public int readSVarInt() { return readSVInt(); } @Override public short readShort() { final short i = BytesUtils.bytesToShort(buffer, offset); this.offset = this.offset + 2; return i; } public int readUnsignedShort() { return readShort() & 0xFFFF; } @Override public long readLong() { final long l = BytesUtils.bytesToLong(buffer, offset); this.offset = this.offset + 8; return l; } @Override public long readVLong() { // borrowing the protocol buffer's concept of variable-length encoding // copy https://github.com/google/protobuf 2.6.1 // CodedInputStream.java -> long readRawVarint64() throws IOException // Implementation notes: // // Optimized for one-byte values, expected to be common. // The particular code below was selected from various candidates // empirically, by winning VarintBenchmark. // // Sign extension of (signed) Java bytes is usually a nuisance, but // we exploit it here to more easily obtain the sign of bytes read. // Instead of cleaning up the sign extension bits by masking eagerly, // we delay until we find the final (positive) byte, when we clear all // accumulated bits with one xor. We depend on javac to constant fold. fastpath: { int pos = offset; int bufferSize = this.buffer.length; if (bufferSize == pos) { break fastpath; } final byte[] buffer = this.buffer; long x; int y; if ((y = buffer[pos++]) >= 0) { this.offset = pos; return y; } else if (bufferSize - pos < 9) { break fastpath; } else if ((x = y ^ (buffer[pos++] << 7)) < 0L) { x ^= (~0L << 7); } else if ((x ^= (buffer[pos++] << 14)) >= 0L) { x ^= (~0L << 7) ^ (~0L << 14); } else if ((x ^= (buffer[pos++] << 21)) < 0L) { x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21); } else if ((x ^= ((long) buffer[pos++] << 28)) >= 0L) { x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28); } else if ((x ^= ((long) buffer[pos++] << 35)) < 0L) { x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35); } else if ((x ^= ((long) buffer[pos++] << 42)) >= 0L) { x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42); } else if ((x ^= ((long) buffer[pos++] << 49)) < 0L) { x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42) ^ (~0L << 49); } else { x ^= ((long) buffer[pos++] << 56); x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42) ^ (~0L << 49) ^ (~0L << 56); if (x < 0L) { if (buffer[pos++] < 0L) { break fastpath; // Will throw malformedVarint() } } } this.offset = pos; return x; } return readVar64SlowPath(); } @Override public long readSVLong() { return BytesUtils.zigzagToLong(readVLong()); } @Override public double readDouble() { return Double.longBitsToDouble(this.readLong()); } @Override public double readVDouble() { return Double.longBitsToDouble(this.readVLong()); } @Override public double readSVDouble() { return Double.longBitsToDouble(this.readSVLong()); } @Override public byte[] readPadBytes(int totalLength) { return readBytes(totalLength); } @Override public String readPadString(int totalLength) { return readString(totalLength); } @Override public String readPadStringAndRightTrim(int totalLength) { String string = BytesUtils.toStringAndRightTrim(buffer, offset, totalLength); this.offset = offset + totalLength; return string ; } @Override public byte[] readPrefixedBytes() { final int size = readSVInt(); if (size == NULL) { return null; } if (size == 0) { return EMPTY; } return readBytes(size); } @Override public byte[] read2PrefixedBytes() { final int size = readShort(); if (size == NULL) { return null; } if (size == 0) { return EMPTY; } return readBytes(size); } @Override public byte[] read4PrefixedBytes() { final int size = readInt(); if (size == NULL) { return null; } if (size == 0) { return EMPTY; } return readBytes(size); } private byte[] readBytes(int size) { final byte[] b = new byte[size]; System.arraycopy(buffer, offset, b, 0, size); this.offset = offset + size; return b; } @Override public String readPrefixedString() { final int size = readSVInt(); if (size == NULL) { return null; } if (size == 0) { return ""; } return readString(size); } @Override public String read2PrefixedString() { final int size = readShort(); if (size == NULL) { return null; } if (size == 0) { return ""; } return readString(size); } @Override public String read4PrefixedString() { final int size = readInt(); if (size == NULL) { return null; } if (size == 0) { return ""; } return readString(size); } private String readString(final int size) { final String s = newString(size); this.offset = offset + size; return s; } private String newString(final int size) { try { return new String(buffer, offset, size, UTF8); } catch (UnsupportedEncodingException ue) { return new String(buffer, offset, size, UTF8_CHARSET); } } /** * Be careful that if internal buffer's length is as same as offset, * then just return internal buffer without copying memory for improving performance. * @return */ @Override public byte[] getBuffer() { if (offset == buffer.length) { return this.buffer; } else { return copyBuffer(); } } @Override public byte[] copyBuffer() { final byte[] copy = new byte[offset]; System.arraycopy(buffer, 0, copy, 0, offset); return copy; } @Override public ByteBuffer wrapByteBuffer() { return ByteBuffer.wrap(this.buffer, 0, offset); } /** * return internal buffer * @return */ @Override public byte[] getInternalBuffer() { return this.buffer; } @Override public void setOffset(int offset) { this.offset = offset; } @Override public int getOffset() { return offset; } @Override public int remaining() { return buffer.length - offset; } @Override public boolean hasRemaining() { return offset < buffer.length; } }