All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.objectbox.flatbuffers.FlexBuffersBuilder Maven / Gradle / Ivy

There is a newer version: 4.0.3
Show newest version
/*
 * 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 java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;

import static io.objectbox.flatbuffers.FlexBuffers.*;
import static io.objectbox.flatbuffers.FlexBuffers.Unsigned.byteToUnsignedInt;
import static io.objectbox.flatbuffers.FlexBuffers.Unsigned.intToUnsignedLong;
import static io.objectbox.flatbuffers.FlexBuffers.Unsigned.shortToUnsignedInt;

/// @file
/// @addtogroup flatbuffers_java_api
/// @{

/**
 * Helper class that builds FlexBuffers
 * 

This class presents all necessary APIs to create FlexBuffers. A `ByteBuffer` will be used to store the * data. It can be created internally, or passed down in the constructor.

* *

There are some limitations when compared to original implementation in C++. Most notably: *

    *
  • No support for mutations (might change in the future).

  • *
  • Buffer size limited to {@link Integer#MAX_VALUE}

  • *
  • Since Java does not support unsigned type, all unsigned operations accepts an immediate higher representation * of similar type.

  • *
*

*/ public class FlexBuffersBuilder { /** * No keys or strings will be shared */ public static final int BUILDER_FLAG_NONE = 0; /** * Keys will be shared between elements. Identical keys will only be serialized once, thus possibly saving space. * But serialization performance might be slower and consumes more memory. */ public static final int BUILDER_FLAG_SHARE_KEYS = 1; /** * Strings will be shared between elements. Identical strings will only be serialized once, thus possibly saving space. * But serialization performance might be slower and consumes more memory. This is ideal if you expect many repeated * strings on the message. */ public static final int BUILDER_FLAG_SHARE_STRINGS = 2; /** * Strings and keys will be shared between elements. */ public static final int BUILDER_FLAG_SHARE_KEYS_AND_STRINGS = 3; /** * Reserved for the future. */ public static final int BUILDER_FLAG_SHARE_KEY_VECTORS = 4; /** * Reserved for the future. */ public static final int BUILDER_FLAG_SHARE_ALL = 7; /// @cond FLATBUFFERS_INTERNAL private static final int WIDTH_8 = 0; private static final int WIDTH_16 = 1; private static final int WIDTH_32 = 2; private static final int WIDTH_64 = 3; private final ReadWriteBuf bb; private final ArrayList stack = new ArrayList<>(); private final HashMap keyPool = new HashMap<>(); private final HashMap stringPool = new HashMap<>(); private final int flags; private boolean finished = false; // A lambda to sort map keys private Comparator keyComparator = new Comparator() { @Override public int compare(Value o1, Value o2) { int ia = o1.key; int io = o2.key; byte c1, c2; do { c1 = bb.get(ia); c2 = bb.get(io); if (c1 == 0) return c1 - c2; ia++; io++; } while (c1 == c2); return c1 - c2; } }; /// @endcond /** * Constructs a newly allocated {@code FlexBuffersBuilder} with {@link #BUILDER_FLAG_SHARE_KEYS} set. * @param bufSize size of buffer in bytes. */ public FlexBuffersBuilder(int bufSize) { this(new ArrayReadWriteBuf(bufSize), BUILDER_FLAG_SHARE_KEYS); } /** * Constructs a newly allocated {@code FlexBuffersBuilder} with {@link #BUILDER_FLAG_SHARE_KEYS} set. */ public FlexBuffersBuilder() { this(256); } /** * Constructs a newly allocated {@code FlexBuffersBuilder}. * * @param bb `ByteBuffer` that will hold the message * @param flags Share flags */ @Deprecated public FlexBuffersBuilder(ByteBuffer bb, int flags) { this(new ArrayReadWriteBuf(bb.array()), flags); } public FlexBuffersBuilder(ReadWriteBuf bb, int flags) { this.bb = bb; this.flags = flags; } /** * Constructs a newly allocated {@code FlexBuffersBuilder}. * By default same keys will be serialized only once * @param bb `ByteBuffer` that will hold the message */ public FlexBuffersBuilder(ByteBuffer bb) { this(bb, BUILDER_FLAG_SHARE_KEYS); } /** * Reset the FlexBuffersBuilder by purging all data that it holds. */ public void clear(){ bb.clear(); stack.clear(); keyPool.clear(); stringPool.clear(); finished = false; } /** * Return `ByteBuffer` containing FlexBuffer message. {@code #finish()} must be called before calling this * function otherwise an assert will trigger. * * @return `ByteBuffer` with finished message */ public ReadWriteBuf getBuffer() { assert (finished); return bb; } /** * Insert a null value into the buffer */ public void putNull() { putNull(null); } /** * Insert a null value into the buffer * @param key key used to store element in map */ public void putNull(String key) { stack.add(Value.nullValue(putKey(key))); } /** * Insert a single boolean into the buffer * @param val true or false */ public void putBoolean(boolean val) { putBoolean(null, val); } /** * Insert a single boolean into the buffer * @param key key used to store element in map * @param val true or false */ public void putBoolean(String key, boolean val) { stack.add(Value.bool(putKey(key), val)); } private int putKey(String key) { if (key == null) { return -1; } int pos = bb.writePosition(); if ((flags & BUILDER_FLAG_SHARE_KEYS) != 0) { Integer keyFromPool = keyPool.get(key); if (keyFromPool == null) { byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8); bb.put(keyBytes, 0, keyBytes.length); bb.put((byte) 0); keyPool.put(key, pos); } else { pos = keyFromPool; } } else { byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8); bb.put(keyBytes, 0, keyBytes.length); bb.put((byte) 0); keyPool.put(key, pos); } return pos; } /** * Adds a integer into the buff * @param val integer */ public void putInt(int val) { putInt(null, val); } /** * Adds a integer into the buff * @param key key used to store element in map * @param val integer */ public void putInt(String key, int val) { putInt(key, (long) val); } /** * Adds a integer into the buff * @param key key used to store element in map * @param val 64-bit integer */ public void putInt(String key, long val) { int iKey = putKey(key); if (Byte.MIN_VALUE <= val && val <= Byte.MAX_VALUE) { stack.add(Value.int8(iKey, (int) val)); } else if (Short.MIN_VALUE <= val && val <= Short.MAX_VALUE) { stack.add(Value.int16(iKey, (int) val)); } else if (Integer.MIN_VALUE <= val && val <= Integer.MAX_VALUE) { stack.add(Value.int32(iKey, (int) val)); } else { stack.add(Value.int64(iKey, val)); } } /** * Adds a 64-bit integer into the buff * @param value integer */ public void putInt(long value) { putInt(null, value); } /** * Adds a unsigned integer into the buff. * @param value integer representing unsigned value */ public void putUInt(int value) { putUInt(null, (long) value); } /** * Adds a unsigned integer (stored in a signed 64-bit integer) into the buff. * @param value integer representing unsigned value */ public void putUInt(long value) { putUInt(null, value); } /** * Adds a 64-bit unsigned integer (stored as {@link BigInteger}) into the buff. * Warning: This operation might be very slow. * @param value integer representing unsigned value */ public void putUInt64(BigInteger value) { putUInt64(null, value.longValue()); } private void putUInt64(String key, long value) { stack.add(Value.uInt64(putKey(key), value)); } private void putUInt(String key, long value) { int iKey = putKey(key); Value vVal; int width = widthUInBits(value); if (width == WIDTH_8) { vVal = Value.uInt8(iKey, (int)value); } else if (width == WIDTH_16) { vVal = Value.uInt16(iKey, (int)value); } else if (width == WIDTH_32) { vVal = Value.uInt32(iKey, (int)value); } else { vVal = Value.uInt64(iKey, value); } stack.add(vVal); } /** * Adds a 32-bit float into the buff. * @param value float representing value */ public void putFloat(float value) { putFloat(null, value); } /** * Adds a 32-bit float into the buff. * @param key key used to store element in map * @param value float representing value */ public void putFloat(String key, float val) { stack.add(Value.float32(putKey(key), val)); } /** * Adds a 64-bit float into the buff. * @param value float representing value */ public void putFloat(double value) { putFloat(null, value); } /** * Adds a 64-bit float into the buff. * @param key key used to store element in map * @param value float representing value */ public void putFloat(String key, double val) { stack.add(Value.float64(putKey(key), val)); } /** * Adds a String into the buffer * @param value string * @return start position of string in the buffer */ public int putString(String value) { return putString(null, value); } /** * Adds a String into the buffer * @param key key used to store element in map * @param value string * @return start position of string in the buffer */ public int putString(String key, String val) { int iKey = putKey(key); if ((flags & FlexBuffersBuilder.BUILDER_FLAG_SHARE_STRINGS) != 0) { Integer i = stringPool.get(val); if (i == null) { Value value = writeString(iKey, val); stringPool.put(val, (int) value.iValue); stack.add(value); return (int) value.iValue; } else { int bitWidth = widthUInBits(val.length()); stack.add(Value.blob(iKey, i, FBT_STRING, bitWidth)); return i; } } else { Value value = writeString(iKey, val); stack.add(value); return (int) value.iValue; } } private Value writeString(int key, String s) { return writeBlob(key, s.getBytes(StandardCharsets.UTF_8), FBT_STRING, true); } // in bits to fit a unsigned int static int widthUInBits(long len) { if (len <= byteToUnsignedInt((byte)0xff)) return WIDTH_8; if (len <= shortToUnsignedInt((short)0xffff)) return WIDTH_16; if (len <= intToUnsignedLong(0xffff_ffff)) return WIDTH_32; return WIDTH_64; } private Value writeBlob(int key, byte[] blob, int type, boolean trailing) { int bitWidth = widthUInBits(blob.length); int byteWidth = align(bitWidth); writeInt(blob.length, byteWidth); int sloc = bb.writePosition(); bb.put(blob, 0, blob.length); if (trailing) { bb.put((byte) 0); } return Value.blob(key, sloc, type, bitWidth); } // Align to prepare for writing a scalar with a certain size. private int align(int alignment) { int byteWidth = 1 << alignment; int padBytes = Value.paddingBytes(bb.writePosition(), byteWidth); while (padBytes-- != 0) { bb.put((byte) 0); } return byteWidth; } private void writeInt(long value, int byteWidth) { switch (byteWidth) { case 1: bb.put((byte) value); break; case 2: bb.putShort((short) value); break; case 4: bb.putInt((int) value); break; case 8: bb.putLong(value); break; } } /** * Adds a byte array into the message * @param value byte array * @return position in buffer as the start of byte array */ public int putBlob(byte[] value) { return putBlob(null, value); } /** * Adds a byte array into the message * @param key key used to store element in map * @param value byte array * @return position in buffer as the start of byte array */ public int putBlob(String key, byte[] val) { int iKey = putKey(key); Value value = writeBlob(iKey, val, FBT_BLOB, false); stack.add(value); return (int) value.iValue; } /** * Start a new vector in the buffer. * @return a reference indicating position of the vector in buffer. This * reference must be passed along when the vector is finished using endVector() */ public int startVector() { return stack.size(); } /** * Finishes a vector, but writing the information in the buffer * @param key key used to store element in map * @param start reference for beginning of the vector. Returned by {@link startVector()} * @param typed boolean indicating whether vector is typed * @param fixed boolean indicating whether vector is fixed * @return Reference to the vector */ public int endVector(String key, int start, boolean typed, boolean fixed) { int iKey = putKey(key); Value vec = createVector(iKey, start, stack.size() - start, typed, fixed, null); // Remove temp elements and return vector. while (stack.size() > start) { stack.remove(stack.size() - 1); } stack.add(vec); return (int) vec.iValue; } /** * Finish writing the message into the buffer. After that no other element must * be inserted into the buffer. Also, you must call this function before start using the * FlexBuffer message * @return `ByteBuffer` containing the FlexBuffer message */ public ByteBuffer finish() { // If you hit this assert, you likely have objects that were never included // in a parent. You need to have exactly one root to finish a buffer. // Check your Start/End calls are matched, and all objects are inside // some other object. assert (stack.size() == 1); // Write root value. int byteWidth = align(stack.get(0).elemWidth(bb.writePosition(), 0)); writeAny(stack.get(0), byteWidth); // Write root type. bb.put(stack.get(0).storedPackedType()); // Write root size. Normally determined by parent, but root has no parent :) bb.put((byte) byteWidth); this.finished = true; return ByteBuffer.wrap(bb.data(), 0, bb.writePosition()); } /* * Create a vector based on the elements stored in the stack * * @param key reference to its key * @param start element in the stack * @param length size of the vector * @param typed whether is TypedVector or not * @param fixed whether is Fixed vector or not * @param keys Value representing key vector * @return Value representing the created vector */ private Value createVector(int key, int start, int length, boolean typed, boolean fixed, Value keys) { if (fixed & !typed) throw new UnsupportedOperationException("Untyped fixed vector is not supported"); // Figure out smallest bit width we can store this vector with. int bitWidth = Math.max(WIDTH_8, widthUInBits(length)); int prefixElems = 1; if (keys != null) { // If this vector is part of a map, we will pre-fix an offset to the keys // to this vector. bitWidth = Math.max(bitWidth, keys.elemWidth(bb.writePosition(), 0)); prefixElems += 2; } int vectorType = FBT_KEY; // Check bit widths and types for all elements. for (int i = start; i < stack.size(); i++) { int elemWidth = stack.get(i).elemWidth(bb.writePosition(), i + prefixElems); bitWidth = Math.max(bitWidth, elemWidth); if (typed) { if (i == start) { vectorType = stack.get(i).type; if (!FlexBuffers.isTypedVectorElementType(vectorType)) { throw new FlexBufferException("TypedVector does not support this element type"); } } else { // If you get this assert, you are writing a typed vector with // elements that are not all the same type. assert (vectorType == stack.get(i).type); } } } // If you get this assert, your fixed types are not one of: // Int / UInt / Float / Key. assert (!fixed || FlexBuffers.isTypedVectorElementType(vectorType)); int byteWidth = align(bitWidth); // Write vector. First the keys width/offset if available, and size. if (keys != null) { writeOffset(keys.iValue, byteWidth); writeInt(1L << keys.minBitWidth, byteWidth); } if (!fixed) { writeInt(length, byteWidth); } // Then the actual data. int vloc = bb.writePosition(); for (int i = start; i < stack.size(); i++) { writeAny(stack.get(i), byteWidth); } // Then the types. if (!typed) { for (int i = start; i < stack.size(); i++) { bb.put(stack.get(i).storedPackedType(bitWidth)); } } return new Value(key, keys != null ? FBT_MAP : (typed ? FlexBuffers.toTypedVector(vectorType, fixed ? length : 0) : FBT_VECTOR), bitWidth, vloc); } private void writeOffset(long val, int byteWidth) { int reloff = (int) (bb.writePosition() - val); assert (byteWidth == 8 || reloff < 1L << (byteWidth * 8)); writeInt(reloff, byteWidth); } private void writeAny(final Value val, int byteWidth) { switch (val.type) { case FBT_NULL: case FBT_BOOL: case FBT_INT: case FBT_UINT: writeInt(val.iValue, byteWidth); break; case FBT_FLOAT: writeDouble(val.dValue, byteWidth); break; default: writeOffset(val.iValue, byteWidth); break; } } private void writeDouble(double val, int byteWidth) { if (byteWidth == 4) { bb.putFloat((float) val); } else if (byteWidth == 8) { bb.putDouble(val); } } /** * Start a new map in the buffer. * @return a reference indicating position of the map in buffer. This * reference must be passed along when the map is finished using endMap() */ public int startMap() { return stack.size(); } /** * Finishes a map, but writing the information in the buffer * @param key key used to store element in map * @param start reference for beginning of the map. Returned by {@link startMap()} * @return Reference to the map */ public int endMap(String key, int start) { int iKey = putKey(key); Collections.sort(stack.subList(start, stack.size()), keyComparator); Value keys = createKeyVector(start, stack.size() - start); Value vec = createVector(iKey, start, stack.size() - start, false, false, keys); // Remove temp elements and return map. while (stack.size() > start) { stack.remove(stack.size() - 1); } stack.add(vec); return (int) vec.iValue; } private Value createKeyVector(int start, int length) { // Figure out smallest bit width we can store this vector with. int bitWidth = Math.max(WIDTH_8, widthUInBits(length)); int prefixElems = 1; // Check bit widths and types for all elements. for (int i = start; i < stack.size(); i++) { int elemWidth = Value.elemWidth(FBT_KEY, WIDTH_8, stack.get(i).key, bb.writePosition(), i + prefixElems); bitWidth = Math.max(bitWidth, elemWidth); } int byteWidth = align(bitWidth); // Write vector. First the keys width/offset if available, and size. writeInt(length, byteWidth); // Then the actual data. int vloc = bb.writePosition(); for (int i = start; i < stack.size(); i++) { int pos = stack.get(i).key; assert(pos != -1); writeOffset(stack.get(i).key, byteWidth); } // Then the types. return new Value(-1, FlexBuffers.toTypedVector(FBT_KEY,0), bitWidth, vloc); } private static class Value { final int type; // for scalars, represents scalar size in bytes // for vectors, represents the size // for string, length final int minBitWidth; // float value final double dValue; // integer value long iValue; // position of the key associated with this value in buffer int key; Value(int key, int type, int bitWidth, long iValue) { this.key = key; this.type = type; this.minBitWidth = bitWidth; this.iValue = iValue; this.dValue = Double.MIN_VALUE; } Value(int key, int type, int bitWidth, double dValue) { this.key = key; this.type = type; this.minBitWidth = bitWidth; this.dValue = dValue; this.iValue = Long.MIN_VALUE; } static Value nullValue(int key) { return new Value(key, FBT_NULL, WIDTH_8, 0); } static Value bool(int key, boolean b) { return new Value(key, FBT_BOOL, WIDTH_8, b ? 1 : 0); } static Value blob(int key, int position, int type, int bitWidth) { return new Value(key, type, bitWidth, position); } static Value int8(int key, int value) { return new Value(key, FBT_INT, WIDTH_8, value); } static Value int16(int key, int value) { return new Value(key, FBT_INT, WIDTH_16, value); } static Value int32(int key, int value) { return new Value(key, FBT_INT, WIDTH_32, value); } static Value int64(int key, long value) { return new Value(key, FBT_INT, WIDTH_64, value); } static Value uInt8(int key, int value) { return new Value(key, FBT_UINT, WIDTH_8, value); } static Value uInt16(int key, int value) { return new Value(key, FBT_UINT, WIDTH_16, value); } static Value uInt32(int key, int value) { return new Value(key, FBT_UINT, WIDTH_32, value); } static Value uInt64(int key, long value) { return new Value(key, FBT_UINT, WIDTH_64, value); } static Value float32(int key, float value) { return new Value(key, FBT_FLOAT, WIDTH_32, value); } static Value float64(int key, double value) { return new Value(key, FBT_FLOAT, WIDTH_64, value); } private byte storedPackedType() { return storedPackedType(WIDTH_8); } private byte storedPackedType(int parentBitWidth) { return packedType(storedWidth(parentBitWidth), type); } private static byte packedType(int bitWidth, int type) { return (byte) (bitWidth | (type << 2)); } private int storedWidth(int parentBitWidth) { if (FlexBuffers.isTypeInline(type)) { return Math.max(minBitWidth, parentBitWidth); } else { return minBitWidth; } } private int elemWidth(int bufSize, int elemIndex) { return elemWidth(type, minBitWidth, iValue, bufSize, elemIndex); } private static int elemWidth(int type, int minBitWidth, long iValue, int bufSize, int elemIndex) { if (FlexBuffers.isTypeInline(type)) { return minBitWidth; } else { // We have an absolute offset, but want to store a relative offset // elem_index elements beyond the current buffer end. Since whether // the relative offset fits in a certain byte_width depends on // the size of the elements before it (and their alignment), we have // to test for each size in turn. // Original implementation checks for largest scalar // which is long unsigned int for (int byteWidth = 1; byteWidth <= 32; byteWidth *= 2) { // Where are we going to write this offset? int offsetLoc = bufSize + paddingBytes(bufSize, byteWidth) + (elemIndex * byteWidth); // Compute relative offset. long offset = offsetLoc - iValue; // Does it fit? int bitWidth = widthUInBits(offset); if (((1L) << bitWidth) == byteWidth) return bitWidth; } assert (false); // Must match one of the sizes above. return WIDTH_64; } } private static int paddingBytes(int bufSize, int scalarSize) { return ((~bufSize) + 1) & (scalarSize - 1); } } } /// @}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy