diff options
Diffstat (limited to 'java/com/google/flatbuffers/FlexBuffers.java')
-rw-r--r-- | java/com/google/flatbuffers/FlexBuffers.java | 1102 |
1 files changed, 1102 insertions, 0 deletions
diff --git a/java/com/google/flatbuffers/FlexBuffers.java b/java/com/google/flatbuffers/FlexBuffers.java new file mode 100644 index 00000000..d7df08c1 --- /dev/null +++ b/java/com/google/flatbuffers/FlexBuffers.java @@ -0,0 +1,1102 @@ +/* + * 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. + * <p> + * For generating FlexBuffer messages, use {@link FlexBuffersBuilder}. + * <p> + * Example of usage: + * <pre> + * 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" + * </pre> + */ +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. + * <p> For vector element, it will return size of the vector</p> + * <p> For String element, it will type to be parsed as integer</p> + * <p> Unsigned elements will become negative</p> + * <p> Float elements will be casted to integer </p> + * @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. + * <p> For vector element, it will return size of the vector</p> + * <p> For String element, it will type to be parsed as integer</p> + * <p> Negative signed elements will become unsigned counterpart</p> + * <p> Float elements will be casted to integer </p> + * @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. + * <p> For vector element, it will return size of the vector</p> + * <p> For String element, it will type to be parsed as integer</p> + * <p> Unsigned elements will become negative</p> + * <p> Float elements will be casted to integer </p> + * @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. + * <p> For vector element, it will return size of the vector</p> + * <p> For String element, it will type to be parsed as integer</p> + * @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 + * <p>If element type is not boolean, it will be casted to integer and compared against 0</p> + * @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 + * + * <p>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)}</p> + */ + 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); + + 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) { + return get(key.getBytes(StandardCharsets.UTF_8)); + } + + /** + * @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) { + KeyVector keys = keys(); + int size = keys.size(); + int index = binarySearch(keys, 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(KeyVector keys, byte[] searchedKey) { + int low = 0; + int high = keys.size() - 1; + + while (low <= high) { + int mid = (low + high) >>> 1; + Key k = keys.get(mid); + int cmp = k.compareTo(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 + } + } + + /** + * 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; + } + } +} +/// @} |