io.objectbox.flatbuffers.FlexBuffers Maven / Gradle / Ivy
Show all versions of objectbox-java Show documentation
/*
* Copyright 2014 Google Inc. All rights reserved.
*
* 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 io.objectbox.flatbuffers;
import static io.objectbox.flatbuffers.FlexBuffers.Unsigned.byteToUnsignedInt;
import static io.objectbox.flatbuffers.FlexBuffers.Unsigned.intToUnsignedLong;
import static io.objectbox.flatbuffers.FlexBuffers.Unsigned.shortToUnsignedInt;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
/// @file
/// @addtogroup flatbuffers_java_api
/// @{
/**
* This class can be used to parse FlexBuffer messages.
*
* For generating FlexBuffer messages, use {@link FlexBuffersBuilder}.
*
* Example of usage:
*
* ReadBuf bb = ... // load message from file or network
* FlexBuffers.Reference r = FlexBuffers.getRoot(bb); // Reads the root element
* FlexBuffers.Map map = r.asMap(); // We assumed root object is a map
* System.out.println(map.get("name").asString()); // prints element with key "name"
*
*/
public class FlexBuffers {
// These are used as the upper 6 bits of a type field to indicate the actual
// type.
/** Represent a null type */
public static final int FBT_NULL = 0;
/** Represent a signed integer type */
public static final int FBT_INT = 1;
/** Represent a unsigned type */
public static final int FBT_UINT = 2;
/** Represent a float type */
public static final int FBT_FLOAT = 3; // Types above stored inline, types below store an offset.
/** Represent a key to a map type */
public static final int FBT_KEY = 4;
/** Represent a string type */
public static final int FBT_STRING = 5;
/** Represent a indirect signed integer type */
public static final int FBT_INDIRECT_INT = 6;
/** Represent a indirect unsigned integer type */
public static final int FBT_INDIRECT_UINT = 7;
/** Represent a indirect float type */
public static final int FBT_INDIRECT_FLOAT = 8;
/** Represent a map type */
public static final int FBT_MAP = 9;
/** Represent a vector type */
public static final int FBT_VECTOR = 10; // Untyped.
/** Represent a vector of signed integers type */
public static final int FBT_VECTOR_INT = 11; // Typed any size = stores no type table).
/** Represent a vector of unsigned integers type */
public static final int FBT_VECTOR_UINT = 12;
/** Represent a vector of floats type */
public static final int FBT_VECTOR_FLOAT = 13;
/** Represent a vector of keys type */
public static final int FBT_VECTOR_KEY = 14;
/** Represent a vector of strings type */
// DEPRECATED, use FBT_VECTOR or FBT_VECTOR_KEY instead.
// more info on thttps://github.com/google/flatbuffers/issues/5627.
public static final int FBT_VECTOR_STRING_DEPRECATED = 15;
/// @cond FLATBUFFERS_INTERNAL
public static final int FBT_VECTOR_INT2 = 16; // Typed tuple = no type table; no size field).
public static final int FBT_VECTOR_UINT2 = 17;
public static final int FBT_VECTOR_FLOAT2 = 18;
public static final int FBT_VECTOR_INT3 = 19; // Typed triple = no type table; no size field).
public static final int FBT_VECTOR_UINT3 = 20;
public static final int FBT_VECTOR_FLOAT3 = 21;
public static final int FBT_VECTOR_INT4 = 22; // Typed quad = no type table; no size field).
public static final int FBT_VECTOR_UINT4 = 23;
public static final int FBT_VECTOR_FLOAT4 = 24;
/// @endcond FLATBUFFERS_INTERNAL
/** Represent a blob type */
public static final int FBT_BLOB = 25;
/** Represent a boolean type */
public static final int FBT_BOOL = 26;
/** Represent a vector of booleans type */
public static final int FBT_VECTOR_BOOL = 36; // To Allow the same type of conversion of type to vector type
private static final ReadBuf EMPTY_BB = new ArrayReadWriteBuf(new byte[] {0}, 1);
/**
* Checks where a type is a typed vector
*
* @param type type to be checked
* @return true if typed vector
*/
static boolean isTypedVector(int type) {
return (type >= FBT_VECTOR_INT && type <= FBT_VECTOR_STRING_DEPRECATED) || type == FBT_VECTOR_BOOL;
}
/**
* Check whether you can access type directly (no indirection) or not.
*
* @param type type to be checked
* @return true if inline type
*/
static boolean isTypeInline(int type) {
return type <= FBT_FLOAT || type == FBT_BOOL;
}
static int toTypedVectorElementType(int original_type) {
return original_type - FBT_VECTOR_INT + FBT_INT;
}
/**
* Return a vector type our of a original element type
*
* @param type element type
* @param fixedLength size of element
* @return typed vector type
*/
static int toTypedVector(int type, int fixedLength) {
assert (isTypedVectorElementType(type));
switch (fixedLength) {
case 0: return type - FBT_INT + FBT_VECTOR_INT;
case 2: return type - FBT_INT + FBT_VECTOR_INT2;
case 3: return type - FBT_INT + FBT_VECTOR_INT3;
case 4: return type - FBT_INT + FBT_VECTOR_INT4;
default:
assert (false);
return FBT_NULL;
}
}
static boolean isTypedVectorElementType(int type) {
return (type >= FBT_INT && type <= FBT_KEY) || type == FBT_BOOL;
}
// return position of the element that the offset is pointing to
private static int indirect(ReadBuf bb, int offset, int byteWidth) {
// we assume all offset fits on a int, since ReadBuf operates with that assumption
return (int) (offset - readUInt(bb, offset, byteWidth));
}
// read unsigned int with size byteWidth and return as a 64-bit integer
private static long readUInt(ReadBuf buff, int end, int byteWidth) {
switch (byteWidth) {
case 1: return byteToUnsignedInt(buff.get(end));
case 2: return shortToUnsignedInt(buff.getShort(end));
case 4: return intToUnsignedLong(buff.getInt(end));
case 8: return buff.getLong(end); // We are passing signed long here. Losing information (user should know)
default: return -1; // we should never reach here
}
}
// read signed int of size byteWidth and return as 32-bit int
private static int readInt(ReadBuf buff, int end, int byteWidth) {
return (int) readLong(buff, end, byteWidth);
}
// read signed int of size byteWidth and return as 64-bit int
private static long readLong(ReadBuf buff, int end, int byteWidth) {
switch (byteWidth) {
case 1: return buff.get(end);
case 2: return buff.getShort(end);
case 4: return buff.getInt(end);
case 8: return buff.getLong(end);
default: return -1; // we should never reach here
}
}
private static double readDouble(ReadBuf buff, int end, int byteWidth) {
switch (byteWidth) {
case 4: return buff.getFloat(end);
case 8: return buff.getDouble(end);
default: return -1; // we should never reach here
}
}
/**
* Reads a FlexBuffer message in ReadBuf and returns {@link Reference} to
* the root element.
* @param buffer ReadBuf containing FlexBuffer message
* @return {@link Reference} to the root object
*/
@Deprecated
public static Reference getRoot(ByteBuffer buffer) {
return getRoot( buffer.hasArray() ? new ArrayReadWriteBuf(buffer.array(), buffer.limit()) : new ByteBufferReadWriteBuf(buffer));
}
/**
* Reads a FlexBuffer message in ReadBuf and returns {@link Reference} to
* the root element.
* @param buffer ReadBuf containing FlexBuffer message
* @return {@link Reference} to the root object
*/
public static Reference getRoot(ReadBuf buffer) {
// See Finish() below for the serialization counterpart of this.
// The root ends at the end of the buffer, so we parse backwards from there.
int end = buffer.limit();
int byteWidth = buffer.get(--end);
int packetType = byteToUnsignedInt(buffer.get(--end));
end -= byteWidth; // The root data item.
return new Reference(buffer, end, byteWidth, packetType);
}
/**
* Represents an generic element in the buffer.
*/
public static class Reference {
private static final Reference NULL_REFERENCE = new Reference(EMPTY_BB, 0, 1, 0);
private ReadBuf bb;
private int end;
private int parentWidth;
private int byteWidth;
private int type;
Reference(ReadBuf bb, int end, int parentWidth, int packedType) {
this(bb, end, parentWidth, (1 << (packedType & 3)), packedType >> 2);
}
Reference(ReadBuf bb, int end, int parentWidth, int byteWidth, int type) {
this.bb = bb;
this.end = end;
this.parentWidth = parentWidth;
this.byteWidth = byteWidth;
this.type = type;
}
/**
* Return element type
* @return element type as integer
*/
public int getType() {
return type;
}
/**
* Checks whether the element is null type
* @return true if null type
*/
public boolean isNull() {
return type == FBT_NULL;
}
/**
* Checks whether the element is boolean type
* @return true if boolean type
*/
public boolean isBoolean() {
return type == FBT_BOOL;
}
/**
* Checks whether the element type is numeric (signed/unsigned integers and floats)
* @return true if numeric type
*/
public boolean isNumeric() {
return isIntOrUInt() || isFloat();
}
/**
* Checks whether the element type is signed or unsigned integers
* @return true if an integer type
*/
public boolean isIntOrUInt() {
return isInt() || isUInt();
}
/**
* Checks whether the element type is float
* @return true if a float type
*/
public boolean isFloat() {
return type == FBT_FLOAT || type == FBT_INDIRECT_FLOAT;
}
/**
* Checks whether the element type is signed integer
* @return true if a signed integer type
*/
public boolean isInt() {
return type == FBT_INT || type == FBT_INDIRECT_INT;
}
/**
* Checks whether the element type is signed integer
* @return true if a signed integer type
*/
public boolean isUInt() {
return type == FBT_UINT || type == FBT_INDIRECT_UINT;
}
/**
* Checks whether the element type is string
* @return true if a string type
*/
public boolean isString() {
return type == FBT_STRING;
}
/**
* Checks whether the element type is key
* @return true if a key type
*/
public boolean isKey() {
return type == FBT_KEY;
}
/**
* Checks whether the element type is vector
* @return true if a vector type
*/
public boolean isVector() {
return type == FBT_VECTOR || type == FBT_MAP;
}
/**
* Checks whether the element type is typed vector
* @return true if a typed vector type
*/
public boolean isTypedVector() {
return FlexBuffers.isTypedVector(type);
}
/**
* Checks whether the element type is a map
* @return true if a map type
*/
public boolean isMap() {
return type == FBT_MAP;
}
/**
* Checks whether the element type is a blob
* @return true if a blob type
*/
public boolean isBlob() {
return type == FBT_BLOB;
}
/**
* Returns element as 32-bit integer.
* For vector element, it will return size of the vector
* For String element, it will type to be parsed as integer
* Unsigned elements will become negative
* Float elements will be casted to integer
* @return 32-bit integer or 0 if fail to convert element to integer.
*/
public int asInt() {
if (type == FBT_INT) {
// A fast path for the common case.
return readInt(bb, end, parentWidth);
} else
switch (type) {
case FBT_INDIRECT_INT: return readInt(bb, indirect(bb, end, parentWidth), byteWidth);
case FBT_UINT: return (int) readUInt(bb, end, parentWidth);
case FBT_INDIRECT_UINT: return (int) readUInt(bb, indirect(bb, end, parentWidth), parentWidth);
case FBT_FLOAT: return (int) readDouble(bb, end, parentWidth);
case FBT_INDIRECT_FLOAT: return (int) readDouble(bb, indirect(bb, end, parentWidth), byteWidth);
case FBT_NULL: return 0;
case FBT_STRING: return Integer.parseInt(asString());
case FBT_VECTOR: return asVector().size();
case FBT_BOOL: return readInt(bb, end, parentWidth);
default:
// Convert other things to int.
return 0;
}
}
/**
* Returns element as unsigned 64-bit integer.
* For vector element, it will return size of the vector
* For String element, it will type to be parsed as integer
* Negative signed elements will become unsigned counterpart
* Float elements will be casted to integer
* @return 64-bit integer or 0 if fail to convert element to integer.
*/
public long asUInt() {
if (type == FBT_UINT) {
// A fast path for the common case.
return readUInt(bb, end, parentWidth);
} else
switch (type) {
case FBT_INDIRECT_UINT: return readUInt(bb, indirect(bb, end, parentWidth), byteWidth);
case FBT_INT: return readLong(bb, end, parentWidth);
case FBT_INDIRECT_INT: return readLong(bb, indirect(bb, end, parentWidth), byteWidth);
case FBT_FLOAT: return (long) readDouble(bb, end, parentWidth);
case FBT_INDIRECT_FLOAT: return (long) readDouble(bb, indirect(bb, end, parentWidth), parentWidth);
case FBT_NULL: return 0;
case FBT_STRING: return Long.parseLong(asString());
case FBT_VECTOR: return asVector().size();
case FBT_BOOL: return readInt(bb, end, parentWidth);
default:
// Convert other things to uint.
return 0;
}
}
/**
* Returns element as 64-bit integer.
* For vector element, it will return size of the vector
* For String element, it will type to be parsed as integer
* Unsigned elements will become negative
* Float elements will be casted to integer
* @return 64-bit integer or 0 if fail to convert element to long.
*/
public long asLong() {
if (type == FBT_INT) {
// A fast path for the common case.
return readLong(bb, end, parentWidth);
} else
switch (type) {
case FBT_INDIRECT_INT: return readLong(bb, indirect(bb, end, parentWidth), byteWidth);
case FBT_UINT: return readUInt(bb, end, parentWidth);
case FBT_INDIRECT_UINT: return readUInt(bb, indirect(bb, end, parentWidth), parentWidth);
case FBT_FLOAT: return (long) readDouble(bb, end, parentWidth);
case FBT_INDIRECT_FLOAT: return (long) readDouble(bb, indirect(bb, end, parentWidth), byteWidth);
case FBT_NULL: return 0;
case FBT_STRING: {
try {
return Long.parseLong(asString());
} catch (NumberFormatException nfe) {
return 0; //same as C++ implementation
}
}
case FBT_VECTOR: return asVector().size();
case FBT_BOOL: return readInt(bb, end, parentWidth);
default:
// Convert other things to int.
return 0;
}
}
/**
* Returns element as 64-bit integer.
* For vector element, it will return size of the vector
* For String element, it will type to be parsed as integer
* @return 64-bit integer or 0 if fail to convert element to long.
*/
public double asFloat() {
if (type == FBT_FLOAT) {
// A fast path for the common case.
return readDouble(bb, end, parentWidth);
} else
switch (type) {
case FBT_INDIRECT_FLOAT: return readDouble(bb, indirect(bb, end, parentWidth), byteWidth);
case FBT_INT: return readInt(bb, end, parentWidth);
case FBT_UINT:
case FBT_BOOL:
return readUInt(bb, end, parentWidth);
case FBT_INDIRECT_INT: return readInt(bb, indirect(bb, end, parentWidth), byteWidth);
case FBT_INDIRECT_UINT: return readUInt(bb, indirect(bb, end, parentWidth), byteWidth);
case FBT_NULL: return 0.0;
case FBT_STRING: return Double.parseDouble(asString());
case FBT_VECTOR: return asVector().size();
default:
// Convert strings and other things to float.
return 0;
}
}
/**
* Returns element as a {@link Key}
* @return key or {@link Key#empty()} if element is not a key
*/
public Key asKey() {
if (isKey()) {
return new Key(bb, indirect(bb, end, parentWidth), byteWidth);
} else {
return Key.empty();
}
}
/**
* Returns element as a `String`
* @return element as `String` or empty `String` if fail
*/
public String asString() {
if (isString()) {
int start = indirect(bb, end, parentWidth);
int size = (int) readUInt(bb, start - byteWidth, byteWidth);
return bb.getString(start, size);
}
else if (isKey()){
int start = indirect(bb, end, byteWidth);
for (int i = start; ; i++) {
if (bb.get(i) == 0) {
return bb.getString(start, i - start);
}
}
} else {
return "";
}
}
/**
* Returns element as a {@link Map}
* @return element as {@link Map} or empty {@link Map} if fail
*/
public Map asMap() {
if (isMap()) {
return new Map(bb, indirect(bb, end, parentWidth), byteWidth);
} else {
return Map.empty();
}
}
/**
* Returns element as a {@link Vector}
* @return element as {@link Vector} or empty {@link Vector} if fail
*/
public Vector asVector() {
if (isVector()) {
return new Vector(bb, indirect(bb, end, parentWidth), byteWidth);
} else if(type == FlexBuffers.FBT_VECTOR_STRING_DEPRECATED) {
// deprecated. Should be treated as key vector
return new TypedVector(bb, indirect(bb, end, parentWidth), byteWidth, FlexBuffers.FBT_KEY);
} else if (FlexBuffers.isTypedVector(type)) {
return new TypedVector(bb, indirect(bb, end, parentWidth), byteWidth, FlexBuffers.toTypedVectorElementType(type));
} else {
return Vector.empty();
}
}
/**
* Returns element as a {@link Blob}
* @return element as {@link Blob} or empty {@link Blob} if fail
*/
public Blob asBlob() {
if (isBlob() || isString()) {
return new Blob(bb, indirect(bb, end, parentWidth), byteWidth);
} else {
return Blob.empty();
}
}
/**
* Returns element as a boolean
* If element type is not boolean, it will be casted to integer and compared against 0
* @return element as boolean
*/
public boolean asBoolean() {
if (isBoolean()) {
return bb.get(end) != 0;
}
return asUInt() != 0;
}
/**
* Returns text representation of the element (JSON)
* @return String containing text representation of the element
*/
@Override
public String toString() {
return toString(new StringBuilder(128)).toString();
}
/**
* Appends a text(JSON) representation to a `StringBuilder`
*/
StringBuilder toString(StringBuilder sb) {
//TODO: Original C++ implementation escape strings.
// probably we should do it as well.
switch (type) {
case FBT_NULL:
return sb.append("null");
case FBT_INT:
case FBT_INDIRECT_INT:
return sb.append(asLong());
case FBT_UINT:
case FBT_INDIRECT_UINT:
return sb.append(asUInt());
case FBT_INDIRECT_FLOAT:
case FBT_FLOAT:
return sb.append(asFloat());
case FBT_KEY:
return asKey().toString(sb.append('"')).append('"');
case FBT_STRING:
return sb.append('"').append(asString()).append('"');
case FBT_MAP:
return asMap().toString(sb);
case FBT_VECTOR:
return asVector().toString(sb);
case FBT_BLOB:
return asBlob().toString(sb);
case FBT_BOOL:
return sb.append(asBoolean());
case FBT_VECTOR_INT:
case FBT_VECTOR_UINT:
case FBT_VECTOR_FLOAT:
case FBT_VECTOR_KEY:
case FBT_VECTOR_STRING_DEPRECATED:
case FBT_VECTOR_BOOL:
return sb.append(asVector());
case FBT_VECTOR_INT2:
case FBT_VECTOR_UINT2:
case FBT_VECTOR_FLOAT2:
case FBT_VECTOR_INT3:
case FBT_VECTOR_UINT3:
case FBT_VECTOR_FLOAT3:
case FBT_VECTOR_INT4:
case FBT_VECTOR_UINT4:
case FBT_VECTOR_FLOAT4:
throw new FlexBufferException("not_implemented:" + type);
default:
return sb;
}
}
}
/**
* Base class of all types below.
* Points into the data buffer and allows access to one type.
*/
private static abstract class Object {
ReadBuf bb;
int end;
int byteWidth;
Object(ReadBuf buff, int end, int byteWidth) {
this.bb = buff;
this.end = end;
this.byteWidth = byteWidth;
}
@Override
public String toString() {
return toString(new StringBuilder(128)).toString();
}
public abstract StringBuilder toString(StringBuilder sb);
}
// Stores size in `byte_width_` bytes before end position.
private static abstract class Sized extends Object {
protected final int size;
Sized(ReadBuf buff, int end, int byteWidth) {
super(buff, end, byteWidth);
size = (int) readUInt(bb, end - byteWidth, byteWidth);
}
public int size() {
return size;
}
}
/**
* Represents a array of bytes element in the buffer
*
* It can be converted to `ReadBuf` using {@link data()},
* copied into a byte[] using {@link getBytes()} or
* have individual bytes accessed individually using {@link get(int)}
*/
public static class Blob extends Sized {
static final Blob EMPTY = new Blob(EMPTY_BB, 1, 1);
Blob(ReadBuf buff, int end, int byteWidth) {
super(buff, end, byteWidth);
}
/** Return an empty {@link Blob} */
public static Blob empty() {
return EMPTY;
}
/**
* Return {@link Blob} as `ReadBuf`
* @return blob as `ReadBuf`
*/
public ByteBuffer data() {
ByteBuffer dup = ByteBuffer.wrap(bb.data());
dup.position(end);
dup.limit(end + size());
return dup.asReadOnlyBuffer().slice();
}
/**
* Copy blob into a byte[]
* @return blob as a byte[]
*/
public byte[] getBytes() {
int size = size();
byte[] result = new byte[size];
for (int i = 0; i < size; i++) {
result[i] = bb.get(end + i);
}
return result;
}
/**
* Return individual byte at a given position
* @param pos position of the byte to be read
*/
public byte get(int pos) {
assert pos >=0 && pos <= size();
return bb.get(end + pos);
}
/**
* Returns a text(JSON) representation of the {@link Blob}
*/
@Override
public String toString() {
return bb.getString(end, size());
}
/**
* Append a text(JSON) representation of the {@link Blob} into a `StringBuilder`
*/
@Override
public StringBuilder toString(StringBuilder sb) {
sb.append('"');
sb.append(bb.getString(end, size()));
return sb.append('"');
}
}
/**
* Represents a key element in the buffer. Keys are
* used to reference objects in a {@link Map}
*/
public static class Key extends Object {
private static final Key EMPTY = new Key(EMPTY_BB, 0, 0);
Key(ReadBuf buff, int end, int byteWidth) {
super(buff, end, byteWidth);
}
/**
* Return an empty {@link Key}
* @return empty {@link Key}
* */
public static Key empty() {
return Key.EMPTY;
}
/**
* Appends a text(JSON) representation to a `StringBuilder`
*/
@Override
public StringBuilder toString(StringBuilder sb) {
return sb.append(toString());
}
@Override
public String toString() {
int size;
for (int i = end; ; i++) {
if (bb.get(i) == 0) {
size = i - end;
break;
}
}
return bb.getString(end, size);
}
int compareTo(byte[] other) {
int ia = end;
int io = 0;
byte c1, c2;
do {
c1 = bb.get(ia);
c2 = other[io];
if (c1 == '\0')
return c1 - c2;
ia++;
io++;
if (io == other.length) {
// in our buffer we have an additional \0 byte
// but this does not exist in regular Java strings, so we return now
int cmp = c1 - c2;
if (cmp != 0 || bb.get(ia) == '\0') {
return cmp;
} else {
return 1;
}
}
}
while (c1 == c2);
return c1 - c2;
}
/**
* Compare keys
* @param obj other key to compare
* @return true if keys are the same
*/
@Override
public boolean equals(java.lang.Object obj) {
if (!(obj instanceof Key))
return false;
return ((Key) obj).end == end && ((Key) obj).byteWidth == byteWidth;
}
public int hashCode() {
return end ^ byteWidth;
}
}
/**
* Map object representing a set of key-value pairs.
*/
public static class Map extends Vector {
private static final Map EMPTY_MAP = new Map(EMPTY_BB, 1, 1);
// cache for converting UTF-8 codepoints into
// Java chars. Used to speed up String comparison
private final byte[] comparisonBuffer = new byte[4];
Map(ReadBuf bb, int end, int byteWidth) {
super(bb, end, byteWidth);
}
/**
* Returns an empty {@link Map}
* @return an empty {@link Map}
*/
public static Map empty() {
return EMPTY_MAP;
}
/**
* @param key access key to element on map
* @return reference to value in map
*/
public Reference get(String key) {
int index = binarySearch(key);
if (index >= 0 && index < size) {
return get(index);
}
return Reference.NULL_REFERENCE;
}
/**
* @param key access key to element on map. Keys are assumed to be encoded in UTF-8
* @return reference to value in map
*/
public Reference get(byte[] key) {
int index = binarySearch(key);
if (index >= 0 && index < size) {
return get(index);
}
return Reference.NULL_REFERENCE;
}
/**
* Get a vector or keys in the map
*
* @return vector of keys
*/
public KeyVector keys() {
final int num_prefixed_fields = 3;
int keysOffset = end - (byteWidth * num_prefixed_fields);
return new KeyVector(new TypedVector(bb,
indirect(bb, keysOffset, byteWidth),
readInt(bb, keysOffset + byteWidth, byteWidth),
FBT_KEY));
}
/**
* @return {@code Vector} of values from map
*/
public Vector values() {
return new Vector(bb, end, byteWidth);
}
/**
* Writes text (json) representation of map in a {@code StringBuilder}.
*
* @param builder {@code StringBuilder} to be appended to
* @return Same {@code StringBuilder} with appended text
*/
public StringBuilder toString(StringBuilder builder) {
builder.append("{ ");
KeyVector keys = keys();
int size = size();
Vector vals = values();
for (int i = 0; i < size; i++) {
builder.append('"')
.append(keys.get(i).toString())
.append("\" : ");
builder.append(vals.get(i).toString());
if (i != size - 1)
builder.append(", ");
}
builder.append(" }");
return builder;
}
// Performs a binary search on a key vector and return index of the key in key vector
private int binarySearch(CharSequence searchedKey) {
int low = 0;
int high = size - 1;
final int num_prefixed_fields = 3;
int keysOffset = end - (byteWidth * num_prefixed_fields);
int keysStart = indirect(bb, keysOffset, byteWidth);
int keyByteWidth = readInt(bb, keysOffset + byteWidth, byteWidth);
while (low <= high) {
int mid = (low + high) >>> 1;
int keyPos = indirect(bb, keysStart + mid * keyByteWidth, keyByteWidth);
int cmp = compareCharSequence(keyPos, searchedKey);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found
}
private int binarySearch(byte[] searchedKey) {
int low = 0;
int high = size - 1;
final int num_prefixed_fields = 3;
int keysOffset = end - (byteWidth * num_prefixed_fields);
int keysStart = indirect(bb, keysOffset, byteWidth);
int keyByteWidth = readInt(bb, keysOffset + byteWidth, byteWidth);
while (low <= high) {
int mid = (low + high) >>> 1;
int keyPos = indirect(bb, keysStart + mid * keyByteWidth, keyByteWidth);
int cmp = compareBytes(bb, keyPos, searchedKey);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found
}
// compares a byte[] against a FBT_KEY
private int compareBytes(ReadBuf bb, int start, byte[] other) {
int l1 = start;
int l2 = 0;
byte c1, c2;
do {
c1 = bb.get(l1);
c2 = other[l2];
if (c1 == '\0')
return c1 - c2;
l1++;
l2++;
if (l2 == other.length) {
// in our buffer we have an additional \0 byte
// but this does not exist in regular Java strings, so we return now
int cmp = c1 - c2;
if (cmp != 0 || bb.get(l1) == '\0') {
return cmp;
} else {
return 1;
}
}
}
while (c1 == c2);
return c1 - c2;
}
// compares a CharSequence against a FBT_KEY
private int compareCharSequence(int start, CharSequence other) {
int bufferPos = start;
int otherPos = 0;
int limit = bb.limit();
int otherLimit = other.length();
// special loop for ASCII characters. Most of keys should be ASCII only, so this
// loop should be optimized for that.
// breaks if a multi-byte character is found
while (otherPos < otherLimit) {
char c2 = other.charAt(otherPos);
if (c2 >= 0x80) {
// not a single byte codepoint
break;
}
byte b = bb.get(bufferPos);
if (b == 0) {
return -c2;
} else if (b < 0) {
break;
} else if ((char) b != c2) {
return b - c2;
}
++bufferPos;
++otherPos;
}
while (bufferPos < limit) {
int sizeInBuff = Utf8.encodeUtf8CodePoint(other, otherPos, comparisonBuffer);
if (sizeInBuff == 0) {
// That means we finish with other and there are not more chars to
// compare. String in the buffer is bigger.
return bb.get(bufferPos);
}
for (int i = 0; i < sizeInBuff; i++) {
byte bufferByte = bb.get(bufferPos++);
byte otherByte = comparisonBuffer[i];
if (bufferByte == 0) {
// Our key is finished, so other is bigger
return -otherByte;
} else if (bufferByte != otherByte) {
return bufferByte - otherByte;
}
}
otherPos += sizeInBuff == 4 ? 2 : 1;
}
return 0;
}
}
/**
* Object that represents a set of elements in the buffer
*/
public static class Vector extends Sized {
private static final Vector EMPTY_VECTOR = new Vector(EMPTY_BB, 1, 1);
Vector(ReadBuf bb, int end, int byteWidth) {
super(bb, end, byteWidth);
}
/**
* Returns an empty {@link Map}
* @return an empty {@link Map}
*/
public static Vector empty() {
return EMPTY_VECTOR;
}
/**
* Checks if the vector is empty
* @return true if vector is empty
*/
public boolean isEmpty() {
return this == EMPTY_VECTOR;
}
/**
* Appends a text(JSON) representation to a `StringBuilder`
*/
@Override
public StringBuilder toString(StringBuilder sb) {
sb.append("[ ");
int size = size();
for (int i = 0; i < size; i++) {
get(i).toString(sb);
if (i != size - 1) {
sb.append(", ");
}
}
sb.append(" ]");
return sb;
}
/**
* Get a element in a vector by index
*
* @param index position of the element
* @return {@code Reference} to the element
*/
public Reference get(int index) {
long len = size();
if (index >= len) {
return Reference.NULL_REFERENCE;
}
int packedType = byteToUnsignedInt(bb.get((int) (end + (len * byteWidth) + index)));
int obj_end = end + index * byteWidth;
return new Reference(bb, obj_end, byteWidth, packedType);
}
}
/**
* Object that represents a set of elements with the same type
*/
public static class TypedVector extends Vector {
private static final TypedVector EMPTY_VECTOR = new TypedVector(EMPTY_BB, 1, 1, FBT_INT);
private final int elemType;
TypedVector(ReadBuf bb, int end, int byteWidth, int elemType) {
super(bb, end, byteWidth);
this.elemType = elemType;
}
public static TypedVector empty() {
return EMPTY_VECTOR;
}
/**
* Returns whether the vector is empty
*
* @return true if empty
*/
public boolean isEmptyVector() {
return this == EMPTY_VECTOR;
}
/**
* Return element type for all elements in the vector
*
* @return element type
*/
public int getElemType() {
return elemType;
}
/**
* Get reference to an object in the {@code Vector}
*
* @param pos position of the object in {@code Vector}
* @return reference to element
*/
@Override
public Reference get(int pos) {
int len = size();
if (pos >= len) return Reference.NULL_REFERENCE;
int childPos = end + pos * byteWidth;
return new Reference(bb, childPos, byteWidth, 1, elemType);
}
}
/**
* Represent a vector of keys in a map
*/
public static class KeyVector {
private final TypedVector vec;
KeyVector(TypedVector vec) {
this.vec = vec;
}
/**
* Return key
*
* @param pos position of the key in key vector
* @return key
*/
public Key get(int pos) {
int len = size();
if (pos >= len) return Key.EMPTY;
int childPos = vec.end + pos * vec.byteWidth;
return new Key(vec.bb, indirect(vec.bb, childPos, vec.byteWidth), 1);
}
/**
* Returns size of key vector
*
* @return size
*/
public int size() {
return vec.size();
}
/**
* Returns a text(JSON) representation
*/
public String toString() {
StringBuilder b = new StringBuilder();
b.append('[');
for (int i = 0; i < vec.size(); i++) {
vec.get(i).toString(b);
if (i != vec.size() - 1) {
b.append(", ");
}
}
return b.append("]").toString();
}
}
public static class FlexBufferException extends RuntimeException {
FlexBufferException(String msg) {
super(msg);
}
}
static class Unsigned {
static int byteToUnsignedInt(byte x) {
return ((int) x) & 0xff;
}
static int shortToUnsignedInt(short x) {
return ((int) x) & 0xffff;
}
static long intToUnsignedLong(int x) {
return ((long) x) & 0xffffffffL;
}
}
}
/// @}