| /* |
| * 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; |
| |
| /// @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 |
| /// @endcond |
| |
| /** |
| * 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 = 1; |
| } |
| space = initial_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; |
| } |
| |
| /** |
| * 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(1024); |
| } |
| |
| /** |
| * 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; |
| 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); |
| } |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * 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(); |
| if ((old_buf_size & 0xC0000000) != 0) // Ensure we don't grow beyond what fits in an int. |
| throw new AssertionError("FlatBuffers: cannot grow buffer beyond 2 gigabytes."); |
| int new_buf_size = old_buf_size == 0 ? 1 : old_buf_size << 1; |
| bb.position(0); |
| ByteBuffer nbb = bb_factory.newByteBuffer(new_buf_size); |
| 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 {@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(); |
| } |
| |
| /// @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; |
| } |
| } |
| } |
| |
| } |
| |
| /// @} |