blob: 32b5c04eefcbcf5577fbbb07658664f9eb00ad34 [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 org.apache.hugegraph.backend.serializer;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.UUID;
import org.apache.hugegraph.backend.id.EdgeId;
import org.apache.hugegraph.backend.id.Id;
import org.apache.hugegraph.backend.id.Id.IdType;
import org.apache.hugegraph.backend.id.IdGenerator;
import org.apache.hugegraph.backend.serializer.BinaryBackendEntry.BinaryId;
import org.apache.hugegraph.schema.PropertyKey;
import org.apache.hugegraph.type.HugeType;
import org.apache.hugegraph.type.define.Cardinality;
import org.apache.hugegraph.type.define.DataType;
import org.apache.hugegraph.util.Blob;
import org.apache.hugegraph.util.Bytes;
import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.KryoUtil;
import org.apache.hugegraph.util.StringEncoding;
/**
* class BytesBuffer is a util for read/write binary
*/
public final class BytesBuffer extends OutputStream {
public static final int BYTE_LEN = Byte.BYTES;
public static final int SHORT_LEN = Short.BYTES;
public static final int INT_LEN = Integer.BYTES;
public static final int LONG_LEN = Long.BYTES;
public static final int CHAR_LEN = Character.BYTES;
public static final int FLOAT_LEN = Float.BYTES;
public static final int DOUBLE_LEN = Double.BYTES;
public static final int BLOB_LEN = 4;
public static final int UINT8_MAX = ((byte) -1) & 0xff;
public static final int UINT16_MAX = ((short) -1) & 0xffff;
public static final long UINT32_MAX = (-1) & 0xffffffffL;
// NOTE: +1 to let code 0 represent length 1
public static final int ID_LEN_MASK = 0x7f;
public static final int ID_LEN_MAX = 0x7f + 1; // 128
public static final int BIG_ID_LEN_MAX = 0x7fff + 1; // 32768
public static final byte STRING_ENDING_BYTE = (byte) 0x00;
public static final byte STRING_ENDING_BYTE_FF = (byte) 0xff;
public static final int STRING_LEN_MAX = UINT16_MAX;
public static final long BLOB_LEN_MAX = 1 * Bytes.GB;
// The value must be in range [8, ID_LEN_MAX]
public static final int INDEX_HASH_ID_THRESHOLD = 32;
public static final int DEFAULT_CAPACITY = 64;
public static final int MAX_BUFFER_CAPACITY = 128 * 1024 * 1024; // 128M
public static final int BUF_EDGE_ID = 128;
public static final int BUF_PROPERTY = 64;
public static final byte[] BYTES_EMPTY = new byte[0];
private ByteBuffer buffer;
private final boolean resize;
public BytesBuffer() {
this(DEFAULT_CAPACITY);
}
public BytesBuffer(int capacity) {
E.checkArgument(capacity <= MAX_BUFFER_CAPACITY,
"Capacity exceeds max buffer capacity: %s",
MAX_BUFFER_CAPACITY);
this.buffer = ByteBuffer.allocate(capacity);
this.resize = true;
}
public BytesBuffer(ByteBuffer buffer) {
E.checkNotNull(buffer, "buffer");
this.buffer = buffer;
this.resize = false;
}
public static BytesBuffer allocate(int capacity) {
return new BytesBuffer(capacity);
}
public static BytesBuffer wrap(ByteBuffer buffer) {
return new BytesBuffer(buffer);
}
public static BytesBuffer wrap(byte[] array) {
return new BytesBuffer(ByteBuffer.wrap(array));
}
public static BytesBuffer wrap(byte[] array, int offset, int length) {
return new BytesBuffer(ByteBuffer.wrap(array, offset, length));
}
public ByteBuffer asByteBuffer() {
return this.buffer;
}
public BytesBuffer forReadWritten() {
this.buffer.flip();
return this;
}
public BytesBuffer forReadAll() {
this.buffer.position(this.buffer.limit());
return this;
}
public byte[] array() {
return this.buffer.array();
}
public byte[] bytes() {
byte[] bytes = this.buffer.array();
int position = this.buffer.position();
if (position == bytes.length) {
return bytes;
} else {
return Arrays.copyOf(bytes, position);
}
}
public int position() {
return this.buffer.position();
}
public BytesBuffer copyFrom(BytesBuffer other) {
this.write(other.bytes());
return this;
}
public int remaining() {
return this.buffer.remaining();
}
private void require(int size) {
// Does need to resize?
if (this.buffer.limit() - this.buffer.position() >= size) {
return;
}
// Can't resize for wrapped buffer since will change the origin ref
E.checkState(this.resize, "Can't resize for wrapped buffer");
// Extra capacity as buffer
int newCapacity = size + this.buffer.limit() + DEFAULT_CAPACITY;
E.checkArgument(newCapacity <= MAX_BUFFER_CAPACITY,
"Capacity exceeds max buffer capacity: %s",
MAX_BUFFER_CAPACITY);
ByteBuffer newBuffer = ByteBuffer.allocate(newCapacity);
this.buffer.flip();
newBuffer.put(this.buffer);
this.buffer = newBuffer;
}
public BytesBuffer write(byte val) {
require(BYTE_LEN);
this.buffer.put(val);
return this;
}
@Override
public void write(int val) {
assert val <= UINT8_MAX;
require(BYTE_LEN);
this.buffer.put((byte) val);
}
@Override
public void write(byte[] val) {
require(BYTE_LEN * val.length);
this.buffer.put(val);
}
@Override
public void write(byte[] val, int offset, int length) {
require(BYTE_LEN * length);
this.buffer.put(val, offset, length);
}
public BytesBuffer writeBoolean(boolean val) {
this.write(val ? 1 : 0);
return this;
}
public BytesBuffer writeChar(char val) {
require(CHAR_LEN);
this.buffer.putChar(val);
return this;
}
public BytesBuffer writeShort(short val) {
require(SHORT_LEN);
this.buffer.putShort(val);
return this;
}
public BytesBuffer writeInt(int val) {
require(INT_LEN);
this.buffer.putInt(val);
return this;
}
public BytesBuffer writeLong(long val) {
require(LONG_LEN);
this.buffer.putLong(val);
return this;
}
public BytesBuffer writeFloat(float val) {
require(FLOAT_LEN);
this.buffer.putFloat(val);
return this;
}
public BytesBuffer writeDouble(double val) {
require(DOUBLE_LEN);
this.buffer.putDouble(val);
return this;
}
public byte peek() {
return this.buffer.get(this.buffer.position());
}
public byte peekLast() {
return this.buffer.get(this.buffer.capacity() - 1);
}
public byte read() {
return this.buffer.get();
}
public byte[] read(int length) {
byte[] bytes = new byte[length];
this.buffer.get(bytes);
return bytes;
}
public boolean readBoolean() {
return this.buffer.get() != 0;
}
public char readChar() {
return this.buffer.getChar();
}
public short readShort() {
return this.buffer.getShort();
}
public int readInt() {
return this.buffer.getInt();
}
public long readLong() {
return this.buffer.getLong();
}
public float readFloat() {
return this.buffer.getFloat();
}
public double readDouble() {
return this.buffer.getDouble();
}
public BytesBuffer writeBytes(byte[] bytes) {
E.checkArgument(bytes.length <= UINT16_MAX,
"The max length of bytes is %s, but got %s",
UINT16_MAX, bytes.length);
require(SHORT_LEN + bytes.length);
this.writeVInt(bytes.length);
this.write(bytes);
return this;
}
public byte[] readBytes() {
int length = this.readVInt();
assert length >= 0;
return this.read(length);
}
public BytesBuffer writeBigBytes(byte[] bytes) {
E.checkArgument(bytes.length <= BLOB_LEN_MAX,
"The max length of bytes is %s, but got %s",
BLOB_LEN_MAX, bytes.length);
require(BLOB_LEN + bytes.length);
this.writeVInt(bytes.length);
this.write(bytes);
return this;
}
public byte[] readBigBytes() {
int length = this.readVInt();
assert length >= 0;
return this.read(length);
}
public BytesBuffer writeStringRaw(String val) {
this.write(StringEncoding.encode(val));
return this;
}
public BytesBuffer writeString(String val) {
byte[] bytes = StringEncoding.encode(val);
this.writeBytes(bytes);
return this;
}
public String readString() {
return StringEncoding.decode(this.readBytes());
}
public BytesBuffer writeStringWithEnding(String value) {
if (!value.isEmpty()) {
byte[] bytes = StringEncoding.encode(value);
/*
* assert '0x00'/'0xFF' not exist in string index id
* NOTE:
* 0x00 is NULL in UTF8(or ASCII) bytes
* 0xFF is not a valid byte in UTF8 bytes
*/
assert !Bytes.contains(bytes, STRING_ENDING_BYTE_FF) :
"Invalid UTF8 bytes: " + value;
if (Bytes.contains(bytes, STRING_ENDING_BYTE)) {
E.checkArgument(false,
"Can't contains byte '0x00' in string: '%s'",
value);
}
this.write(bytes);
}
/*
* Choose 0x00 as ending symbol (see #1057)
* The following is out of date:
* A reasonable ending symbol should be 0x00(to ensure order), but
* considering that some backends like PG do not support 0x00 string,
* so choose 0xFF currently.
*/
this.write(STRING_ENDING_BYTE);
return this;
}
public String readStringWithEnding() {
return StringEncoding.decode(this.readBytesWithEnding());
}
public BytesBuffer writeStringToRemaining(String value) {
byte[] bytes = StringEncoding.encode(value);
this.write(bytes);
return this;
}
public String readStringFromRemaining() {
byte[] bytes = new byte[this.buffer.remaining()];
this.buffer.get(bytes);
return StringEncoding.decode(bytes);
}
public BytesBuffer writeUInt8(int val) {
assert val <= UINT8_MAX;
this.write(val);
return this;
}
public int readUInt8() {
return this.read() & 0x000000ff;
}
public BytesBuffer writeUInt16(int val) {
assert val <= UINT16_MAX;
this.writeShort((short) val);
return this;
}
public int readUInt16() {
return this.readShort() & 0x0000ffff;
}
public BytesBuffer writeUInt32(long val) {
assert val <= UINT32_MAX;
this.writeInt((int) val);
return this;
}
public long readUInt32() {
return this.readInt() & 0xffffffffL;
}
public BytesBuffer writeVInt(int value) {
// NOTE: negative numbers are not compressed
if (value > 0x0fffffff || value < 0) {
this.write(0x80 | ((value >>> 28) & 0x7f));
}
if (value > 0x1fffff || value < 0) {
this.write(0x80 | ((value >>> 21) & 0x7f));
}
if (value > 0x3fff || value < 0) {
this.write(0x80 | ((value >>> 14) & 0x7f));
}
if (value > 0x7f || value < 0) {
this.write(0x80 | ((value >>> 7) & 0x7f));
}
this.write(value & 0x7f);
return this;
}
public int readVInt() {
byte leading = this.read();
E.checkArgument(leading != 0x80,
"Unexpected varint with leading byte '0x%s'",
Bytes.toHex(leading));
int value = leading & 0x7f;
if (leading >= 0) {
assert (leading & 0x80) == 0;
return value;
}
int i = 1;
for (; i < 5; i++) {
byte b = this.read();
if (b >= 0) {
value = b | (value << 7);
break;
} else {
value = (b & 0x7f) | (value << 7);
}
}
E.checkArgument(i < 5,
"Unexpected varint %s with too many bytes(%s)",
value, i + 1);
E.checkArgument(i < 4 || (leading & 0x70) == 0,
"Unexpected varint %s with leading byte '0x%s'",
value, Bytes.toHex(leading));
return value;
}
public BytesBuffer writeVLong(long value) {
if (value < 0) {
this.write((byte) 0x81);
}
if (value > 0xffffffffffffffL || value < 0L) {
this.write(0x80 | ((int) (value >>> 56) & 0x7f));
}
if (value > 0x1ffffffffffffL || value < 0L) {
this.write(0x80 | ((int) (value >>> 49) & 0x7f));
}
if (value > 0x3ffffffffffL || value < 0L) {
this.write(0x80 | ((int) (value >>> 42) & 0x7f));
}
if (value > 0x7ffffffffL || value < 0L) {
this.write(0x80 | ((int) (value >>> 35) & 0x7f));
}
if (value > 0xfffffffL || value < 0L) {
this.write(0x80 | ((int) (value >>> 28) & 0x7f));
}
if (value > 0x1fffffL || value < 0L) {
this.write(0x80 | ((int) (value >>> 21) & 0x7f));
}
if (value > 0x3fffL || value < 0L) {
this.write(0x80 | ((int) (value >>> 14) & 0x7f));
}
if (value > 0x7fL || value < 0L) {
this.write(0x80 | ((int) (value >>> 7) & 0x7f));
}
this.write((int) value & 0x7f);
return this;
}
public long readVLong() {
byte leading = this.read();
E.checkArgument(leading != 0x80,
"Unexpected varlong with leading byte '0x%s'",
Bytes.toHex(leading));
long value = leading & 0x7fL;
if (leading >= 0) {
assert (leading & 0x80) == 0;
return value;
}
int i = 1;
for (; i < 10; i++) {
byte b = this.read();
if (b >= 0) {
value = b | (value << 7);
break;
} else {
value = (b & 0x7f) | (value << 7);
}
}
E.checkArgument(i < 10,
"Unexpected varlong %s with too many bytes(%s)",
value, i + 1);
E.checkArgument(i < 9 || (leading & 0x7e) == 0,
"Unexpected varlong %s with leading byte '0x%s'",
value, Bytes.toHex(leading));
return value;
}
public BytesBuffer writeProperty(PropertyKey pkey, Object value) {
if (pkey.cardinality() == Cardinality.SINGLE) {
this.writeProperty(pkey.dataType(), value);
return this;
}
assert pkey.cardinality() == Cardinality.LIST ||
pkey.cardinality() == Cardinality.SET;
Collection<?> values = (Collection<?>) value;
this.writeVInt(values.size());
for (Object o : values) {
this.writeProperty(pkey.dataType(), o);
}
return this;
}
public Object readProperty(PropertyKey pkey) {
if (pkey.cardinality() == Cardinality.SINGLE) {
return this.readProperty(pkey.dataType());
}
assert pkey.cardinality() == Cardinality.LIST ||
pkey.cardinality() == Cardinality.SET;
int size = this.readVInt();
Collection<Object> values = pkey.newValue();
for (int i = 0; i < size; i++) {
values.add(this.readProperty(pkey.dataType()));
}
return values;
}
public void writeProperty(DataType dataType, Object value) {
switch (dataType) {
case BOOLEAN:
this.writeVInt(((Boolean) value) ? 1 : 0);
break;
case BYTE:
this.writeVInt((Byte) value);
break;
case INT:
this.writeVInt((Integer) value);
break;
case FLOAT:
this.writeFloat((Float) value);
break;
case LONG:
this.writeVLong((Long) value);
break;
case DATE:
this.writeVLong(((Date) value).getTime());
break;
case DOUBLE:
this.writeDouble((Double) value);
break;
case TEXT:
this.writeString((String) value);
break;
case BLOB:
byte[] bytes = value instanceof byte[] ?
(byte[]) value : ((Blob) value).bytes();
this.writeBigBytes(bytes);
break;
case UUID:
UUID uuid = (UUID) value;
// Generally writeVLong(uuid) can't save space
this.writeLong(uuid.getMostSignificantBits());
this.writeLong(uuid.getLeastSignificantBits());
break;
default:
this.writeBytes(KryoUtil.toKryoWithType(value));
break;
}
}
public Object readProperty(DataType dataType) {
switch (dataType) {
case BOOLEAN:
return this.readVInt() == 1;
case BYTE:
return (byte) this.readVInt();
case INT:
return this.readVInt();
case FLOAT:
return this.readFloat();
case LONG:
return this.readVLong();
case DATE:
return new Date(this.readVLong());
case DOUBLE:
return this.readDouble();
case TEXT:
return this.readString();
case BLOB:
return Blob.wrap(this.readBigBytes());
case UUID:
return new UUID(this.readLong(), this.readLong());
default:
return KryoUtil.fromKryoWithType(this.readBytes());
}
}
public BytesBuffer writeId(Id id) {
return this.writeId(id, false);
}
public BytesBuffer writeId(Id id, boolean big) {
switch (id.type()) {
case LONG:
// Number Id
long value = id.asLong();
this.writeNumber(value);
break;
case UUID:
// UUID Id
byte[] bytes = id.asBytes();
assert bytes.length == Id.UUID_LENGTH;
this.writeUInt8(0x7f); // 0b01111111 means UUID
this.write(bytes);
break;
case EDGE:
// Edge Id
this.writeUInt8(0x7e); // 0b01111110 means EdgeId
this.writeEdgeId(id);
break;
default:
// String Id
bytes = id.asBytes();
int len = bytes.length;
E.checkArgument(len > 0, "Can't write empty id");
if (!big) {
E.checkArgument(len <= ID_LEN_MAX,
"Id max length is %s, but got %s {%s}",
ID_LEN_MAX, len, id);
len -= 1; // mapping [1, 128] to [0, 127]
this.writeUInt8(len | 0x80);
} else {
E.checkArgument(len <= BIG_ID_LEN_MAX,
"Big id max length is %s, but got %s {%s}",
BIG_ID_LEN_MAX, len, id);
len -= 1;
int high = len >> 8;
int low = len & 0xff;
this.writeUInt8(high | 0x80);
this.writeUInt8(low);
}
this.write(bytes);
break;
}
return this;
}
public Id readId() {
return this.readId(false);
}
public Id readId(boolean big) {
byte b = this.read();
boolean number = (b & 0x80) == 0;
if (number) {
if (b == 0x7f) {
// UUID Id
return IdGenerator.of(this.read(Id.UUID_LENGTH), IdType.UUID);
} else if (b == 0x7e) {
// Edge Id
return this.readEdgeId();
} else {
// Number Id
return IdGenerator.of(this.readNumber(b));
}
} else {
// String Id
int len = b & ID_LEN_MASK;
if (big) {
int high = len << 8;
int low = this.readUInt8();
len = high + low;
}
len += 1; // restore [0, 127] to [1, 128]
byte[] id = this.read(len);
return IdGenerator.of(id, IdType.STRING);
}
}
public BytesBuffer writeEdgeId(Id id) {
// owner-vertex + dir + edge-label + sort-values + other-vertex
EdgeId edge = (EdgeId) id;
this.writeId(edge.ownerVertexId());
this.write(edge.directionCode());
this.writeId(edge.edgeLabelId());
this.writeStringWithEnding(edge.sortValues());
this.writeId(edge.otherVertexId());
return this;
}
public Id readEdgeId() {
return new EdgeId(this.readId(), EdgeId.directionFromCode(this.read()),
this.readId(), this.readStringWithEnding(),
this.readId());
}
public BytesBuffer writeIndexId(Id id, HugeType type) {
return this.writeIndexId(id, type, true);
}
public BytesBuffer writeIndexId(Id id, HugeType type, boolean withEnding) {
byte[] bytes = id.asBytes();
int len = bytes.length;
E.checkArgument(len > 0, "Can't write empty id");
this.write(bytes);
if (type.isStringIndex()) {
if (Bytes.contains(bytes, STRING_ENDING_BYTE)) {
// Not allow STRING_ENDING_BYTE exist in string index id
E.checkArgument(false,
"The %s type index id can't contains " +
"byte '0x%s', but got: 0x%s", type,
Bytes.toHex(STRING_ENDING_BYTE),
Bytes.toHex(bytes));
}
if (withEnding) {
this.writeStringWithEnding("");
}
}
return this;
}
public BinaryId readIndexId(HugeType type) {
byte[] id;
if (type.isRange4Index()) {
// IndexLabel 4 bytes + fieldValue 4 bytes
id = this.read(8);
} else if (type.isRange8Index()) {
// IndexLabel 4 bytes + fieldValue 8 bytes
id = this.read(12);
} else {
assert type.isStringIndex();
id = this.readBytesWithEnding();
}
return new BinaryId(id, IdGenerator.of(id, IdType.STRING));
}
public BinaryId asId() {
return new BinaryId(this.bytes(), null);
}
public BinaryId parseId(HugeType type, boolean enablePartition) {
if (type.isIndex()) {
return this.readIndexId(type);
}
// Parse id from bytes
if ((type.isVertex() || type.isEdge()) && enablePartition) {
this.readShort();
}
int start = this.buffer.position();
/*
* Since edge id in edges table doesn't prefix with leading 0x7e,
* so readId() will return the source vertex id instead of edge id,
* can't call: type.isEdge() ? this.readEdgeId() : this.readId();
*/
Id id = this.readId();
int end = this.buffer.position();
int len = end - start;
byte[] bytes = new byte[len];
System.arraycopy(this.array(), start, bytes, 0, len);
return new BinaryId(bytes, id);
}
/**
* 解析 olap id
* @param type
* @param isOlap
* @return
*/
public BinaryId parseOlapId(HugeType type, boolean isOlap) {
if (type.isIndex()) {
return this.readIndexId(type);
}
// Parse id from bytes
int start = this.buffer.position();
/**
* OLAP
* {PropertyKey}{VertexId}
*/
if (isOlap) {
// 先 read olap property id
Id pkId = this.readId();
}
Id id = this.readId();
int end = this.buffer.position();
int len = end - start;
byte[] bytes = new byte[len];
System.arraycopy(this.array(), start, bytes, 0, len);
return new BinaryId(bytes, id);
}
private void writeNumber(long val) {
/*
* 8 kinds of number, 2 ~ 9 bytes number:
* 0b 0kkksxxx X...
* 0(1 bit) + kind(3 bits) + signed(1 bit) + number(n bits)
*
* 2 byte : 0b 0000 1xxx X(8 bits) [0, 2047]
* 0b 0000 0xxx X(8 bits) [-2048, -1]
* 3 bytes: 0b 0001 1xxx X X [0, 524287]
* 0b 0001 0xxx X X [-524288, -1]
* 4 bytes: 0b 0010 1xxx X X X [0, 134217727]
* 0b 0010 0xxx X X X [-134217728, -1]
* 5 bytes: 0b 0011 1xxx X X X X [0, 2^35 - 1]
* 0b 0011 0xxx X X X X [-2^35, -1]
* 6 bytes: 0b 0100 1xxx X X X X X [0, 2^43 - 1]
* 0b 0100 0xxx X X X X X [-2^43, -1]
* 7 bytes: 0b 0101 1xxx X X X X X X [0, 2^51 - 1]
* 0b 0101 0xxx X X X X X X [-2^51, -1]
* 8 bytes: 0b 0110 1xxx X X X X X X X [0, 2^59 - 1]
* 0b 0110 0xxx X X X X X X X [-2^59, -1]
* 9 bytes: 0b 0111 1000 X X X X X X X X [0, 2^64 - 1]
* 0b 0111 0000 X X X X X X X X [-2^64, -1]
*
* NOTE: 0b 0111 1111 is used by 128 bits UUID
* 0b 0111 1110 is used by EdgeId
*/
int positive = val >= 0 ? 0x08 : 0x00;
if (~0x7ffL <= val && val <= 0x7ffL) {
int high3bits = (int) (val >> 8) & 0x07;
this.writeUInt8(0x00 | positive | high3bits);
this.writeUInt8((byte) val);
} else if (~0x7ffffL <= val && val <= 0x7ffffL) {
int high3bits = (int) (val >> 16) & 0x07;
this.writeUInt8(0x10 | positive | high3bits);
this.writeShort((short) val);
} else if (~0x7ffffffL <= val && val <= 0x7ffffffL) {
int high3bits = (int) (val >> 24 & 0x07);
this.writeUInt8(0x20 | positive | high3bits);
this.write((byte) (val >> 16));
this.writeShort((short) val);
} else if (~0x7ffffffffL <= val && val <= 0x7ffffffffL) {
int high3bits = (int) (val >> 32) & 0x07;
this.writeUInt8(0x30 | positive | high3bits);
this.writeInt((int) val);
} else if (~0x7ffffffffffL <= val && val <= 0x7ffffffffffL) {
int high3bits = (int) (val >> 40) & 0x07;
this.writeUInt8(0x40 | positive | high3bits);
this.write((byte) (val >> 32));
this.writeInt((int) val);
} else if (~0x7ffffffffffffL <= val && val <= 0x7ffffffffffffL) {
int high3bits = (int) (val >> 48) & 0x07;
this.writeUInt8(0x50 | positive | high3bits);
this.writeShort((short) (val >> 32));
this.writeInt((int) val);
} else if (~0x7ffffffffffffffL <= val && val <= 0x7ffffffffffffffL) {
int high3bits = (int) (val >> 56) & 0x07;
this.writeUInt8(0x60 | positive | high3bits);
this.write((byte) (val >> 48));
this.writeShort((short) (val >> 32));
this.writeInt((int) val);
} else {
// high3bits is always 0b000 for 9 bytes number
this.writeUInt8(0x70 | positive);
this.writeLong(val);
}
}
private long readNumber(byte b) {
E.checkArgument((b & 0x80) == 0,
"Not a number type with prefix byte '0x%s'",
Bytes.toHex(b));
// Parse the kind from byte 0kkksxxx
int kind = b >>> 4;
boolean positive = (b & 0x08) > 0;
long high3bits = b & 0x07;
long value = high3bits << ((kind + 1) * 8);
switch (kind) {
case 0:
value |= this.readUInt8();
break;
case 1:
value |= this.readUInt16();
break;
case 2:
value |= (long) this.readUInt8() << 16 | this.readUInt16();
break;
case 3:
value |= this.readUInt32();
break;
case 4:
value |= (long) this.readUInt8() << 32 | this.readUInt32();
break;
case 5:
value |= (long) this.readUInt16() << 32 | this.readUInt32();
break;
case 6:
value |= (long) this.readUInt8() << 48 |
(long) this.readUInt16() << 32 |
this.readUInt32();
break;
case 7:
assert high3bits == 0L;
value |= this.readLong();
break;
default:
throw new AssertionError("Invalid length of number: " + kind);
}
if (!positive && kind < 7) {
// Restore the bits of the original negative number
long mask = Long.MIN_VALUE >> (52 - kind * 8);
value |= mask;
}
return value;
}
private byte[] readBytesWithEnding() {
int start = this.buffer.position();
boolean foundEnding = false;
int remaining = this.remaining();
for (int i = 0; i < remaining; i++) {
byte current = this.read();
if (current == STRING_ENDING_BYTE) {
foundEnding = true;
break;
}
}
E.checkArgument(foundEnding, "Not found ending '0x%s'",
Bytes.toHex(STRING_ENDING_BYTE));
int end = this.buffer.position() - 1;
int len = end - start;
if (len <= 0) {
return BYTES_EMPTY;
}
byte[] bytes = new byte[len];
System.arraycopy(this.array(), start, bytes, 0, len);
return bytes;
}
}