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

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

The 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 static io.objectbox.flatbuffers.Constants.*;

import java.io.IOException;
import java.io.InputStream;
import java.nio.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.lang.Integer;

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

/**
 * Class that helps you build a FlatBuffer.  See the section
 * "Use in Java/C#" in the main FlatBuffers documentation.
 */
public class FlatBufferBuilder {
    /// @cond FLATBUFFERS_INTERNAL
    ByteBuffer bb;                    // Where we construct the FlatBuffer.
    int space;                        // Remaining space in the ByteBuffer.
    int minalign = 1;                 // Minimum alignment encountered so far.
    int[] vtable = null;              // The vtable for the current table.
    int vtable_in_use = 0;            // The amount of fields we're actually using.
    boolean nested = false;           // Whether we are currently serializing a table.
    boolean finished = false;         // Whether the buffer is finished.
    int object_start;                 // Starting offset of the current struct/table.
    int[] vtables = new int[16];      // List of offsets of all vtables.
    int num_vtables = 0;              // Number of entries in `vtables` in use.
    int vector_num_elems = 0;         // For the current vector being built.
    boolean force_defaults = false;   // False omits default values from the serialized data.
    ByteBufferFactory bb_factory;     // Factory for allocating the internal buffer
    final Utf8 utf8;                  // UTF-8 encoder to use
    Map string_pool; // map used to cache shared strings.
    /// @endcond


    /**
     * Maximum size of buffer to allocate. If we're allocating arrays on the heap,
     * the header size of the array counts towards its maximum size.
     */
    private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;

    /**
     * Default buffer size that is allocated if an initial size is not given, or is
     * non positive.
     */
    private static final int DEFAULT_BUFFER_SIZE = 1024;

    /**
     * Start with a buffer of size `initial_size`, then grow as required.
     *
     * @param initial_size The initial size of the internal buffer to use.
     * @param bb_factory The factory to be used for allocating the internal buffer
     */
    public FlatBufferBuilder(int initial_size, ByteBufferFactory bb_factory) {
        this(initial_size, bb_factory, null, Utf8.getDefault());
    }

    /**
     * Start with a buffer of size `initial_size`, then grow as required.
     *
     * @param initial_size The initial size of the internal buffer to use.
     * @param bb_factory The factory to be used for allocating the internal buffer
     * @param existing_bb The byte buffer to reuse.
     * @param utf8 The Utf8 codec
     */
    public FlatBufferBuilder(int initial_size, ByteBufferFactory bb_factory,
                             ByteBuffer existing_bb, Utf8 utf8) {
        if (initial_size <= 0) {
          initial_size = DEFAULT_BUFFER_SIZE;
        }
        this.bb_factory = bb_factory;
        if (existing_bb != null) {
          bb = existing_bb;
          bb.clear();
          bb.order(ByteOrder.LITTLE_ENDIAN);
        } else {
          bb = bb_factory.newByteBuffer(initial_size);
        }
        this.utf8 = utf8;
        space = bb.capacity();
    }

   /**
    * Start with a buffer of size `initial_size`, then grow as required.
    *
    * @param initial_size The initial size of the internal buffer to use.
    */
    public FlatBufferBuilder(int initial_size) {
        this(initial_size, HeapByteBufferFactory.INSTANCE, null, Utf8.getDefault());
    }

    /**
     * Start with a buffer of 1KiB, then grow as required.
     */
    public FlatBufferBuilder() {
        this(DEFAULT_BUFFER_SIZE);
    }

    /**
     * Alternative constructor allowing reuse of {@link ByteBuffer}s.  The builder
     * can still grow the buffer as necessary.  User classes should make sure
     * to call {@link #dataBuffer()} to obtain the resulting encoded message.
     *
     * @param existing_bb The byte buffer to reuse.
     * @param bb_factory The factory to be used for allocating a new internal buffer if
     *                   the existing buffer needs to grow
     */
    public FlatBufferBuilder(ByteBuffer existing_bb, ByteBufferFactory bb_factory) {
        this(existing_bb.capacity(), bb_factory, existing_bb, Utf8.getDefault());
    }

    /**
     * Alternative constructor allowing reuse of {@link ByteBuffer}s.  The builder
     * can still grow the buffer as necessary.  User classes should make sure
     * to call {@link #dataBuffer()} to obtain the resulting encoded message.
     *
     * @param existing_bb The byte buffer to reuse.
     */
    public FlatBufferBuilder(ByteBuffer existing_bb) {
        this(existing_bb, new HeapByteBufferFactory());
    }

    /**
     * Alternative initializer that allows reusing this object on an existing
     * `ByteBuffer`. This method resets the builder's internal state, but keeps
     * objects that have been allocated for temporary storage.
     *
     * @param existing_bb The byte buffer to reuse.
     * @param bb_factory The factory to be used for allocating a new internal buffer if
     *                   the existing buffer needs to grow
     * @return Returns `this`.
     */
    public FlatBufferBuilder init(ByteBuffer existing_bb, ByteBufferFactory bb_factory){
        this.bb_factory = bb_factory;
        bb = existing_bb;
        bb.clear();
        bb.order(ByteOrder.LITTLE_ENDIAN);
        minalign = 1;
        space = bb.capacity();
        vtable_in_use = 0;
        nested = false;
        finished = false;
        object_start = 0;
        num_vtables = 0;
        vector_num_elems = 0;
        if (string_pool != null) {
            string_pool.clear();
        }
        return this;
    }

    /**
     * An interface that provides a user of the FlatBufferBuilder class the ability to specify
     * the method in which the internal buffer gets allocated. This allows for alternatives
     * to the default behavior, which is to allocate memory for a new byte-array
     * backed `ByteBuffer` array inside the JVM.
     *
     * The FlatBufferBuilder class contains the HeapByteBufferFactory class to
     * preserve the default behavior in the event that the user does not provide
     * their own implementation of this interface.
     */
    public static abstract class ByteBufferFactory {
        /**
         * Create a `ByteBuffer` with a given capacity.
         * The returned ByteBuf must have a ByteOrder.LITTLE_ENDIAN ByteOrder.
         *
         * @param capacity The size of the `ByteBuffer` to allocate.
         * @return Returns the new `ByteBuffer` that was allocated.
         */
        public abstract ByteBuffer newByteBuffer(int capacity);

        /**
         * Release a ByteBuffer. Current {@link FlatBufferBuilder}
         * released any reference to it, so it is safe to dispose the buffer
         * or return it to a pool.
         * It is not guaranteed that the buffer has been created
         * with {@link #newByteBuffer(int) }.
         *
         * @param bb the buffer to release
         */
        public void releaseByteBuffer(ByteBuffer bb) {
        }
    }

    /**
     * An implementation of the ByteBufferFactory interface that is used when
     * one is not provided by the user.
     *
     * Allocate memory for a new byte-array backed `ByteBuffer` array inside the JVM.
     */
    public static final class HeapByteBufferFactory extends ByteBufferFactory {

        public static final HeapByteBufferFactory INSTANCE = new HeapByteBufferFactory();

        @Override
        public ByteBuffer newByteBuffer(int capacity) {
            return ByteBuffer.allocate(capacity).order(ByteOrder.LITTLE_ENDIAN);
        }
    }

   /**
   * Helper function to test if a field is present in the table
   *
   * @param table Flatbuffer table
   * @param offset virtual table offset
   * @return true if the filed is present
   */
   public static boolean isFieldPresent(Table table, int offset) {
     return table.__offset(offset) != 0;
   }

    /**
     * Reset the FlatBufferBuilder by purging all data that it holds.
     */
    public void clear(){
        space = bb.capacity();
        bb.clear();
        minalign = 1;
        while(vtable_in_use > 0) vtable[--vtable_in_use] = 0;
        vtable_in_use = 0;
        nested = false;
        finished = false;
        object_start = 0;
        num_vtables = 0;
        vector_num_elems = 0;
        if (string_pool != null) {
            string_pool.clear();
        }
    }

    /**
     * Doubles the size of the backing {@link ByteBuffer} and copies the old data towards the
     * end of the new buffer (since we build the buffer backwards).
     *
     * @param bb The current buffer with the existing data.
     * @param bb_factory The factory to be used for allocating the new internal buffer
     * @return A new byte buffer with the old data copied copied to it.  The data is
     * located at the end of the buffer.
     */
    static ByteBuffer growByteBuffer(ByteBuffer bb, ByteBufferFactory bb_factory) {
        int old_buf_size = bb.capacity();

        int new_buf_size;

        if (old_buf_size == 0) {
            new_buf_size = DEFAULT_BUFFER_SIZE;
        }
        else {
            if (old_buf_size == MAX_BUFFER_SIZE) { // Ensure we don't grow beyond what fits in an int.
                throw new AssertionError("FlatBuffers: cannot grow buffer beyond 2 gigabytes.");
            }
            new_buf_size = (old_buf_size & 0xC0000000) != 0 ? MAX_BUFFER_SIZE : old_buf_size << 1;
        }

        bb.position(0);
        ByteBuffer nbb = bb_factory.newByteBuffer(new_buf_size);
        new_buf_size = nbb.clear().capacity(); // Ensure the returned buffer is treated as empty
        nbb.position(new_buf_size - old_buf_size);
        nbb.put(bb);
        return nbb;
    }

   /**
    * Offset relative to the end of the buffer.
    *
    * @return Offset relative to the end of the buffer.
    */
    public int offset() {
        return bb.capacity() - space;
    }

   /**
    * Add zero valued bytes to prepare a new entry to be added.
    *
    * @param byte_size Number of bytes to add.
    */
    public void pad(int byte_size) {
        for (int i = 0; i < byte_size; i++) bb.put(--space, (byte)0);
    }

   /**
    * Prepare to write an element of `size` after `additional_bytes`
    * have been written, e.g. if you write a string, you need to align such
    * the int length field is aligned to {@link io.objectbox.flatbuffers.Constants#SIZEOF_INT}, and
    * the string data follows it directly.  If all you need to do is alignment, `additional_bytes`
    * will be 0.
    *
    * @param size This is the of the new element to write.
    * @param additional_bytes The padding size.
    */
    public void prep(int size, int additional_bytes) {
        // Track the biggest thing we've ever aligned to.
        if (size > minalign) minalign = size;
        // Find the amount of alignment needed such that `size` is properly
        // aligned after `additional_bytes`
        int align_size = ((~(bb.capacity() - space + additional_bytes)) + 1) & (size - 1);
        // Reallocate the buffer if needed.
        while (space < align_size + size + additional_bytes) {
            int old_buf_size = bb.capacity();
            ByteBuffer old = bb;
            bb = growByteBuffer(old, bb_factory);
            if (old != bb) {
                bb_factory.releaseByteBuffer(old);
            }
            space += bb.capacity() - old_buf_size;
        }
        pad(align_size);
    }

    /**
     * Add a `boolean` to the buffer, backwards from the current location. Doesn't align nor
     * check for space.
     *
     * @param x A `boolean` to put into the buffer.
     */
    public void putBoolean(boolean x) { bb.put      (space -= Constants.SIZEOF_BYTE, (byte)(x ? 1 : 0)); }

    /**
     * Add a `byte` to the buffer, backwards from the current location. Doesn't align nor
     * check for space.
     *
     * @param x A `byte` to put into the buffer.
     */
    public void putByte   (byte    x) { bb.put      (space -= Constants.SIZEOF_BYTE, x); }

    /**
     * Add a `short` to the buffer, backwards from the current location. Doesn't align nor
     * check for space.
     *
     * @param x A `short` to put into the buffer.
     */
    public void putShort  (short   x) { bb.putShort (space -= Constants.SIZEOF_SHORT, x); }

    /**
     * Add an `int` to the buffer, backwards from the current location. Doesn't align nor
     * check for space.
     *
     * @param x An `int` to put into the buffer.
     */
    public void putInt    (int     x) { bb.putInt   (space -= Constants.SIZEOF_INT, x); }

    /**
     * Add a `long` to the buffer, backwards from the current location. Doesn't align nor
     * check for space.
     *
     * @param x A `long` to put into the buffer.
     */
    public void putLong   (long    x) { bb.putLong  (space -= Constants.SIZEOF_LONG, x); }

    /**
     * Add a `float` to the buffer, backwards from the current location. Doesn't align nor
     * check for space.
     *
     * @param x A `float` to put into the buffer.
     */
    public void putFloat  (float   x) { bb.putFloat (space -= Constants.SIZEOF_FLOAT, x); }

    /**
     * Add a `double` to the buffer, backwards from the current location. Doesn't align nor
     * check for space.
     *
     * @param x A `double` to put into the buffer.
     */
    public void putDouble (double  x) { bb.putDouble(space -= Constants.SIZEOF_DOUBLE, x); }
    /// @endcond

    /**
     * Add a `boolean` to the buffer, properly aligned, and grows the buffer (if necessary).
     *
     * @param x A `boolean` to put into the buffer.
     */
    public void addBoolean(boolean x) { prep(Constants.SIZEOF_BYTE, 0); putBoolean(x); }

    /**
     * Add a `byte` to the buffer, properly aligned, and grows the buffer (if necessary).
     *
     * @param x A `byte` to put into the buffer.
     */
    public void addByte   (byte    x) { prep(Constants.SIZEOF_BYTE, 0); putByte   (x); }

    /**
     * Add a `short` to the buffer, properly aligned, and grows the buffer (if necessary).
     *
     * @param x A `short` to put into the buffer.
     */
    public void addShort  (short   x) { prep(Constants.SIZEOF_SHORT, 0); putShort  (x); }

    /**
     * Add an `int` to the buffer, properly aligned, and grows the buffer (if necessary).
     *
     * @param x An `int` to put into the buffer.
     */
    public void addInt    (int     x) { prep(Constants.SIZEOF_INT, 0); putInt    (x); }

    /**
     * Add a `long` to the buffer, properly aligned, and grows the buffer (if necessary).
     *
     * @param x A `long` to put into the buffer.
     */
    public void addLong   (long    x) { prep(Constants.SIZEOF_LONG, 0); putLong   (x); }

    /**
     * Add a `float` to the buffer, properly aligned, and grows the buffer (if necessary).
     *
     * @param x A `float` to put into the buffer.
     */
    public void addFloat  (float   x) { prep(Constants.SIZEOF_FLOAT, 0); putFloat  (x); }

    /**
     * Add a `double` to the buffer, properly aligned, and grows the buffer (if necessary).
     *
     * @param x A `double` to put into the buffer.
     */
    public void addDouble (double  x) { prep(Constants.SIZEOF_DOUBLE, 0); putDouble (x); }

   /**
    * Adds on offset, relative to where it will be written.
    *
    * @param off The offset to add.
    */
    public void addOffset(int off) {
        prep(SIZEOF_INT, 0);  // Ensure alignment is already done.
        assert off <= offset();
        off = offset() - off + SIZEOF_INT;
        putInt(off);
    }

   /// @cond FLATBUFFERS_INTERNAL
   /**
    * Start a new array/vector of objects.  Users usually will not call
    * this directly.  The `FlatBuffers` compiler will create a start/end
    * method for vector types in generated code.
    * 

* The expected sequence of calls is: *

    *
  1. Start the array using this method.
  2. *
  3. Call {@link #addOffset(int)} `num_elems` number of times to set * the offset of each element in the array.
  4. *
  5. Call {@link #endVector()} to retrieve the offset of the array.
  6. *
*

* For example, to create an array of strings, do: *

{@code
    * // Need 10 strings
    * FlatBufferBuilder builder = new FlatBufferBuilder(existingBuffer);
    * int[] offsets = new int[10];
    *
    * for (int i = 0; i < 10; i++) {
    *   offsets[i] = fbb.createString(" " + i);
    * }
    *
    * // Have the strings in the buffer, but don't have a vector.
    * // Add a vector that references the newly created strings:
    * builder.startVector(4, offsets.length, 4);
    *
    * // Add each string to the newly created vector
    * // The strings are added in reverse order since the buffer
    * // is filled in back to front
    * for (int i = offsets.length - 1; i >= 0; i--) {
    *   builder.addOffset(offsets[i]);
    * }
    *
    * // Finish off the vector
    * int offsetOfTheVector = fbb.endVector();
    * }
* * @param elem_size The size of each element in the array. * @param num_elems The number of elements in the array. * @param alignment The alignment of the array. */ public void startVector(int elem_size, int num_elems, int alignment) { notNested(); vector_num_elems = num_elems; prep(SIZEOF_INT, elem_size * num_elems); prep(alignment, elem_size * num_elems); // Just in case alignment > int. nested = true; } /** * Finish off the creation of an array and all its elements. The array * must be created with {@link #startVector(int, int, int)}. * * @return The offset at which the newly created array starts. * @see #startVector(int, int, int) */ public int endVector() { if (!nested) throw new AssertionError("FlatBuffers: endVector called without startVector"); nested = false; putInt(vector_num_elems); return offset(); } /// @endcond /** * Create a new array/vector and return a ByteBuffer to be filled later. * Call {@link #endVector} after this method to get an offset to the beginning * of vector. * * @param elem_size the size of each element in bytes. * @param num_elems number of elements in the vector. * @param alignment byte alignment. * @return ByteBuffer with position and limit set to the space allocated for the array. */ public ByteBuffer createUnintializedVector(int elem_size, int num_elems, int alignment) { int length = elem_size * num_elems; startVector(elem_size, num_elems, alignment); bb.position(space -= length); // Slice and limit the copy vector to point to the 'array' ByteBuffer copy = bb.slice().order(ByteOrder.LITTLE_ENDIAN); copy.limit(length); return copy; } /** * Create a vector of tables. * * @param offsets Offsets of the tables. * @return Returns offset of the vector. */ public int createVectorOfTables(int[] offsets) { notNested(); startVector(Constants.SIZEOF_INT, offsets.length, Constants.SIZEOF_INT); for(int i = offsets.length - 1; i >= 0; i--) addOffset(offsets[i]); return endVector(); } /** * Create a vector of sorted by the key tables. * * @param obj Instance of the table subclass. * @param offsets Offsets of the tables. * @return Returns offset of the sorted vector. */ public int createSortedVectorOfTables(T obj, int[] offsets) { obj.sortTables(offsets, bb); return createVectorOfTables(offsets); } /** * Encode the String `s` in the buffer using UTF-8. If a String with * this exact contents has already been serialized using this method, * instead simply returns the offset of the existing String. * * Usage of the method will incur into additional allocations, * so it is advisable to use it only when it is known upfront that * your message will have several repeated strings. * * @param s The String to encode. * @return The offset in the buffer where the encoded String starts. */ public int createSharedString(String s) { if (string_pool == null) { string_pool = new HashMap<>(); int offset = createString(s); string_pool.put(s, offset); return offset; } Integer offset = string_pool.get(s); if(offset == null) { offset = createString(s); string_pool.put(s, offset); } return offset; } /** * Encode the string `s` in the buffer using UTF-8. If {@code s} is * already a {@link CharBuffer}, this method is allocation free. * * @param s The string to encode. * @return The offset in the buffer where the encoded string starts. */ public int createString(CharSequence s) { int length = utf8.encodedLength(s); addByte((byte)0); startVector(1, length, 1); bb.position(space -= length); utf8.encodeUtf8(s, bb); return endVector(); } /** * Create a string in the buffer from an already encoded UTF-8 string in a ByteBuffer. * * @param s An already encoded UTF-8 string as a `ByteBuffer`. * @return The offset in the buffer where the encoded string starts. */ public int createString(ByteBuffer s) { int length = s.remaining(); addByte((byte)0); startVector(1, length, 1); bb.position(space -= length); bb.put(s); return endVector(); } /** * Create a byte array in the buffer. * * @param arr A source array with data * @return The offset in the buffer where the encoded array starts. */ public int createByteVector(byte[] arr) { int length = arr.length; startVector(1, length, 1); bb.position(space -= length); bb.put(arr); return endVector(); } /** * Create a byte array in the buffer. * * @param arr a source array with data. * @param offset the offset in the source array to start copying from. * @param length the number of bytes to copy from the source array. * @return The offset in the buffer where the encoded array starts. */ public int createByteVector(byte[] arr, int offset, int length) { startVector(1, length, 1); bb.position(space -= length); bb.put(arr, offset, length); return endVector(); } /** * Create a byte array in the buffer. * * The source {@link ByteBuffer} position is advanced by {@link ByteBuffer#remaining()} places * after this call. * * @param byteBuffer A source {@link ByteBuffer} with data. * @return The offset in the buffer where the encoded array starts. */ public int createByteVector(ByteBuffer byteBuffer) { int length = byteBuffer.remaining(); startVector(1, length, 1); bb.position(space -= length); bb.put(byteBuffer); return endVector(); } /// @cond FLATBUFFERS_INTERNAL /** * Should not be accessing the final buffer before it is finished. */ public void finished() { if (!finished) throw new AssertionError( "FlatBuffers: you can only access the serialized buffer after it has been" + " finished by FlatBufferBuilder.finish()."); } /** * Should not be creating any other object, string or vector * while an object is being constructed. */ public void notNested() { if (nested) throw new AssertionError("FlatBuffers: object serialization must not be nested."); } /** * Structures are always stored inline, they need to be created right * where they're used. You'll get this assertion failure if you * created it elsewhere. * * @param obj The offset of the created object. */ public void Nested(int obj) { if (obj != offset()) throw new AssertionError("FlatBuffers: struct must be serialized inline."); } /** * Start encoding a new object in the buffer. Users will not usually need to * call this directly. The `FlatBuffers` compiler will generate helper methods * that call this method internally. *

* For example, using the "Monster" code found on the "landing page". An * object of type `Monster` can be created using the following code: * *

{@code
    * int testArrayOfString = Monster.createTestarrayofstringVector(fbb, new int[] {
    *   fbb.createString("test1"),
    *   fbb.createString("test2")
    * });
    *
    * Monster.startMonster(fbb);
    * Monster.addPos(fbb, Vec3.createVec3(fbb, 1.0f, 2.0f, 3.0f, 3.0,
    *   Color.Green, (short)5, (byte)6));
    * Monster.addHp(fbb, (short)80);
    * Monster.addName(fbb, str);
    * Monster.addInventory(fbb, inv);
    * Monster.addTestType(fbb, (byte)Any.Monster);
    * Monster.addTest(fbb, mon2);
    * Monster.addTest4(fbb, test4);
    * Monster.addTestarrayofstring(fbb, testArrayOfString);
    * int mon = Monster.endMonster(fbb);
    * }
*

* Here: *

    *
  • The call to `Monster#startMonster(FlatBufferBuilder)` will call this * method with the right number of fields set.
  • *
  • `Monster#endMonster(FlatBufferBuilder)` will ensure {@link #endObject()} is called.
  • *
*

* It's not recommended to call this method directly. If it's called manually, you must ensure * to audit all calls to it whenever fields are added or removed from your schema. This is * automatically done by the code generated by the `FlatBuffers` compiler. * * @param numfields The number of fields found in this object. */ public void startTable(int numfields) { notNested(); if (vtable == null || vtable.length < numfields) vtable = new int[numfields]; vtable_in_use = numfields; Arrays.fill(vtable, 0, vtable_in_use, 0); nested = true; object_start = offset(); } /** * Add a `boolean` to a table at `o` into its vtable, with value `x` and default `d`. * * @param o The index into the vtable. * @param x A `boolean` to put into the buffer, depending on how defaults are handled. If * `force_defaults` is `false`, compare `x` against the default value `d`. If `x` contains the * default value, it can be skipped. * @param d A `boolean` default value to compare against when `force_defaults` is `false`. */ public void addBoolean(int o, boolean x, boolean d) { if(force_defaults || x != d) { addBoolean(x); slot(o); } } /** * Add a `byte` to a table at `o` into its vtable, with value `x` and default `d`. * * @param o The index into the vtable. * @param x A `byte` to put into the buffer, depending on how defaults are handled. If * `force_defaults` is `false`, compare `x` against the default value `d`. If `x` contains the * default value, it can be skipped. * @param d A `byte` default value to compare against when `force_defaults` is `false`. */ public void addByte (int o, byte x, int d) { if(force_defaults || x != d) { addByte (x); slot(o); } } /** * Add a `short` to a table at `o` into its vtable, with value `x` and default `d`. * * @param o The index into the vtable. * @param x A `short` to put into the buffer, depending on how defaults are handled. If * `force_defaults` is `false`, compare `x` against the default value `d`. If `x` contains the * default value, it can be skipped. * @param d A `short` default value to compare against when `force_defaults` is `false`. */ public void addShort (int o, short x, int d) { if(force_defaults || x != d) { addShort (x); slot(o); } } /** * Add an `int` to a table at `o` into its vtable, with value `x` and default `d`. * * @param o The index into the vtable. * @param x An `int` to put into the buffer, depending on how defaults are handled. If * `force_defaults` is `false`, compare `x` against the default value `d`. If `x` contains the * default value, it can be skipped. * @param d An `int` default value to compare against when `force_defaults` is `false`. */ public void addInt (int o, int x, int d) { if(force_defaults || x != d) { addInt (x); slot(o); } } /** * Add a `long` to a table at `o` into its vtable, with value `x` and default `d`. * * @param o The index into the vtable. * @param x A `long` to put into the buffer, depending on how defaults are handled. If * `force_defaults` is `false`, compare `x` against the default value `d`. If `x` contains the * default value, it can be skipped. * @param d A `long` default value to compare against when `force_defaults` is `false`. */ public void addLong (int o, long x, long d) { if(force_defaults || x != d) { addLong (x); slot(o); } } /** * Add a `float` to a table at `o` into its vtable, with value `x` and default `d`. * * @param o The index into the vtable. * @param x A `float` to put into the buffer, depending on how defaults are handled. If * `force_defaults` is `false`, compare `x` against the default value `d`. If `x` contains the * default value, it can be skipped. * @param d A `float` default value to compare against when `force_defaults` is `false`. */ public void addFloat (int o, float x, double d) { if(force_defaults || x != d) { addFloat (x); slot(o); } } /** * Add a `double` to a table at `o` into its vtable, with value `x` and default `d`. * * @param o The index into the vtable. * @param x A `double` to put into the buffer, depending on how defaults are handled. If * `force_defaults` is `false`, compare `x` against the default value `d`. If `x` contains the * default value, it can be skipped. * @param d A `double` default value to compare against when `force_defaults` is `false`. */ public void addDouble (int o, double x, double d) { if(force_defaults || x != d) { addDouble (x); slot(o); } } /** * Add an `offset` to a table at `o` into its vtable, with value `x` and default `d`. * * @param o The index into the vtable. * @param x An `offset` to put into the buffer, depending on how defaults are handled. If * `force_defaults` is `false`, compare `x` against the default value `d`. If `x` contains the * default value, it can be skipped. * @param d An `offset` default value to compare against when `force_defaults` is `false`. */ public void addOffset (int o, int x, int d) { if(force_defaults || x != d) { addOffset (x); slot(o); } } /** * Add a struct to the table. Structs are stored inline, so nothing additional is being added. * * @param voffset The index into the vtable. * @param x The offset of the created struct. * @param d The default value is always `0`. */ public void addStruct(int voffset, int x, int d) { if(x != d) { Nested(x); slot(voffset); } } /** * Set the current vtable at `voffset` to the current location in the buffer. * * @param voffset The index into the vtable to store the offset relative to the end of the * buffer. */ public void slot(int voffset) { vtable[voffset] = offset(); } /** * Finish off writing the object that is under construction. * * @return The offset to the object inside {@link #dataBuffer()}. * @see #startTable(int) */ public int endTable() { if (vtable == null || !nested) throw new AssertionError("FlatBuffers: endTable called without startTable"); addInt(0); int vtableloc = offset(); // Write out the current vtable. int i = vtable_in_use - 1; // Trim trailing zeroes. for (; i >= 0 && vtable[i] == 0; i--) {} int trimmed_size = i + 1; for (; i >= 0 ; i--) { // Offset relative to the start of the table. short off = (short)(vtable[i] != 0 ? vtableloc - vtable[i] : 0); addShort(off); } final int standard_fields = 2; // The fields below: addShort((short)(vtableloc - object_start)); addShort((short)((trimmed_size + standard_fields) * SIZEOF_SHORT)); // Search for an existing vtable that matches the current one. int existing_vtable = 0; outer_loop: for (i = 0; i < num_vtables; i++) { int vt1 = bb.capacity() - vtables[i]; int vt2 = space; short len = bb.getShort(vt1); if (len == bb.getShort(vt2)) { for (int j = SIZEOF_SHORT; j < len; j += SIZEOF_SHORT) { if (bb.getShort(vt1 + j) != bb.getShort(vt2 + j)) { continue outer_loop; } } existing_vtable = vtables[i]; break outer_loop; } } if (existing_vtable != 0) { // Found a match: // Remove the current vtable. space = bb.capacity() - vtableloc; // Point table to existing vtable. bb.putInt(space, existing_vtable - vtableloc); } else { // No match: // Add the location of the current vtable to the list of vtables. if (num_vtables == vtables.length) vtables = Arrays.copyOf(vtables, num_vtables * 2); vtables[num_vtables++] = offset(); // Point table to current vtable. bb.putInt(bb.capacity() - vtableloc, offset() - vtableloc); } nested = false; return vtableloc; } /** * Checks that a required field has been set in a given table that has * just been constructed. * * @param table The offset to the start of the table from the `ByteBuffer` capacity. * @param field The offset to the field in the vtable. */ public void required(int table, int field) { int table_start = bb.capacity() - table; int vtable_start = table_start - bb.getInt(table_start); boolean ok = bb.getShort(vtable_start + field) != 0; // If this fails, the caller will show what field needs to be set. if (!ok) throw new AssertionError("FlatBuffers: field " + field + " must be set"); } /// @endcond /** * Finalize a buffer, pointing to the given `root_table`. * * @param root_table An offset to be added to the buffer. * @param size_prefix Whether to prefix the size to the buffer. */ protected void finish(int root_table, boolean size_prefix) { prep(minalign, SIZEOF_INT + (size_prefix ? SIZEOF_INT : 0)); addOffset(root_table); if (size_prefix) { addInt(bb.capacity() - space); } bb.position(space); finished = true; } /** * Finalize a buffer, pointing to the given `root_table`. * * @param root_table An offset to be added to the buffer. */ public void finish(int root_table) { finish(root_table, false); } /** * Finalize a buffer, pointing to the given `root_table`, with the size prefixed. * * @param root_table An offset to be added to the buffer. */ public void finishSizePrefixed(int root_table) { finish(root_table, true); } /** * Finalize a buffer, pointing to the given `root_table`. * * @param root_table An offset to be added to the buffer. * @param file_identifier A FlatBuffer file identifier to be added to the buffer before * `root_table`. * @param size_prefix Whether to prefix the size to the buffer. */ protected void finish(int root_table, String file_identifier, boolean size_prefix) { prep(minalign, SIZEOF_INT + FILE_IDENTIFIER_LENGTH + (size_prefix ? SIZEOF_INT : 0)); if (file_identifier.length() != FILE_IDENTIFIER_LENGTH) throw new AssertionError("FlatBuffers: file identifier must be length " + FILE_IDENTIFIER_LENGTH); for (int i = FILE_IDENTIFIER_LENGTH - 1; i >= 0; i--) { addByte((byte)file_identifier.charAt(i)); } finish(root_table, size_prefix); } /** * Finalize a buffer, pointing to the given `root_table`. * * @param root_table An offset to be added to the buffer. * @param file_identifier A FlatBuffer file identifier to be added to the buffer before * `root_table`. */ public void finish(int root_table, String file_identifier) { finish(root_table, file_identifier, false); } /** * Finalize a buffer, pointing to the given `root_table`, with the size prefixed. * * @param root_table An offset to be added to the buffer. * @param file_identifier A FlatBuffer file identifier to be added to the buffer before * `root_table`. */ public void finishSizePrefixed(int root_table, String file_identifier) { finish(root_table, file_identifier, true); } /** * In order to save space, fields that are set to their default value * don't get serialized into the buffer. Forcing defaults provides a * way to manually disable this optimization. * * @param forceDefaults When set to `true`, always serializes default values. * @return Returns `this`. */ public FlatBufferBuilder forceDefaults(boolean forceDefaults){ this.force_defaults = forceDefaults; return this; } /** * Get the ByteBuffer representing the FlatBuffer. Only call this after you've * called `finish()`. The actual data starts at the ByteBuffer's current position, * not necessarily at `0`. * * @return The {@link ByteBuffer} representing the FlatBuffer */ public ByteBuffer dataBuffer() { finished(); return bb; } /** * The FlatBuffer data doesn't start at offset 0 in the {@link ByteBuffer}, but * now the {@code ByteBuffer}'s position is set to that location upon {@link #finish(int)}. * * @return The {@link ByteBuffer#position() position} the data starts in {@link #dataBuffer()} * @deprecated This method should not be needed anymore, but is left * here for the moment to document this API change. It will be removed in the future. */ @Deprecated private int dataStart() { finished(); return space; } /** * A utility function to copy and return the ByteBuffer data from `start` to * `start` + `length` as a `byte[]`. * * @param start Start copying at this offset. * @param length How many bytes to copy. * @return A range copy of the {@link #dataBuffer() data buffer}. * @throws IndexOutOfBoundsException If the range of bytes is ouf of bound. */ public byte[] sizedByteArray(int start, int length){ finished(); byte[] array = new byte[length]; bb.position(start); bb.get(array); return array; } /** * A utility function to copy and return the ByteBuffer data as a `byte[]`. * * @return A full copy of the {@link #dataBuffer() data buffer}. */ public byte[] sizedByteArray() { return sizedByteArray(space, bb.capacity() - space); } /** * A utility function to return an InputStream to the ByteBuffer data * * @return An InputStream that starts at the beginning of the ByteBuffer data * and can read to the end of it. */ public InputStream sizedInputStream() { finished(); ByteBuffer duplicate = bb.duplicate(); duplicate.position(space); duplicate.limit(bb.capacity()); return new ByteBufferBackedInputStream(duplicate); } /** * A class that allows a user to create an InputStream from a ByteBuffer. */ static class ByteBufferBackedInputStream extends InputStream { ByteBuffer buf; public ByteBufferBackedInputStream(ByteBuffer buf) { this.buf = buf; } public int read() throws IOException { try { return buf.get() & 0xFF; } catch(BufferUnderflowException e) { return -1; } } } } /// @}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy