/* * 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 com.google.flatbuffers; import static com.google.flatbuffers.FlexBuffers.Unsigned.byteToUnsignedInt; import static com.google.flatbuffers.FlexBuffers.Unsigned.intToUnsignedLong; import static com.google.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 = readInt(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 return c1 - c2; } } 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 return c1 - c2; } } 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; } } } /// @}