content: /*
   * 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.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, Integer> 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 com.google.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.
      * <p>
      * The expected sequence of calls is:
      * <ol>
      * <li>Start the array using this method.</li>
      * <li>Call {@link #addOffset(int)} `num_elems` number of times to set
      * the offset of each element in the array.</li>
      * <li>Call {@link #endVector()} to retrieve the offset of the array.</li>
      * </ol>
      * <p>
      * For example, to create an array of strings, do:
      * <pre>{@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();
      * }</pre>
      *
      * @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 <T extends Table> 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.
      * <p>
      * For example, using the "Monster" code found on the "landing page". An
      * object of type `Monster` can be created using the following code:
      *
      * <pre>{@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);
      * }</pre>
      * <p>
      * Here:
      * <ul>
      * <li>The call to `Monster#startMonster(FlatBufferBuilder)` will call this
      * method with the right number of fields set.</li>
      * <li>`Monster#endMonster(FlatBufferBuilder)` will ensure {@link #endObject()} is called.</li>
      * </ul>
      * <p>
      * 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;
              }
          }
      }
  
  }
  
  /// @}
  