forked from BigfootDev/flatbuffers
This avoids the following allocations over repeated use: - A ``CharsetEncoder`` - A byte array to contain the contents of the string - A wrapper, via ``ByteBuffer#wrap``, for the previously mentioned byte array This also removes a copy of the temporary byte array. This extra copy is needed because ``String#getBytes`` returns a byte array and its length must match the exact size of the contents. This implementation requires that the builder retain a ``ByteBuffer`` and ``CharEncoder``. This is considered slower only for users that will only allocate a single string in their buffers or none at all. The improvement is especially potent if the builder is constantly reused by its caller. This also eliminates the penalty for users that do not use strings at all since the cost of allocating these fields is now amortized. The only allocation left in this code is a ``CharBuffer`` wrapper. It's possible to eliminate this allocation using an additional copy but need further profiling to see which is the bigger problem.
780 lines
29 KiB
Java
780 lines
29 KiB
Java
/*
|
|
* 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.nio.CharBuffer;
|
|
import java.nio.charset.CharacterCodingException;
|
|
import java.nio.charset.CharsetEncoder;
|
|
import java.nio.charset.CoderResult;
|
|
import java.util.Arrays;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.ByteOrder;
|
|
import java.nio.charset.Charset;
|
|
|
|
/// @file
|
|
/// @addtogroup flatbuffers_java_api
|
|
/// @{
|
|
|
|
/**
|
|
* Class that helps you build a FlatBuffer. See the section
|
|
* @ref flatbuffers_guide_use_java_c-sharp "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.
|
|
static final Charset utf8charset = Charset.forName("UTF-8"); // The UTF-8 character set used by FlatBuffers.
|
|
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.
|
|
CharsetEncoder encoder = utf8charset.newEncoder();
|
|
ByteBuffer dst;
|
|
/// @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.
|
|
*/
|
|
public FlatBufferBuilder(int initial_size) {
|
|
if (initial_size <= 0) initial_size = 1;
|
|
space = initial_size;
|
|
bb = newByteBuffer(initial_size);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
public FlatBufferBuilder(ByteBuffer existing_bb) {
|
|
init(existing_bb);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* @return Returns `this`.
|
|
*/
|
|
public FlatBufferBuilder init(ByteBuffer existing_bb){
|
|
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;
|
|
}
|
|
|
|
/// @cond FLATBUFFERS_INTERNAL
|
|
/**
|
|
* Create a `ByteBuffer` with a given capacity.
|
|
*
|
|
* @param capacity The size of the `ByteBuffer` to allocate.
|
|
* @return Returns the new `ByteBuffer` that was allocated.
|
|
*/
|
|
static ByteBuffer newByteBuffer(int capacity) {
|
|
ByteBuffer newbb = ByteBuffer.allocate(capacity);
|
|
newbb.order(ByteOrder.LITTLE_ENDIAN);
|
|
return newbb;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* @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) {
|
|
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 << 1;
|
|
bb.position(0);
|
|
ByteBuffer nbb = 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();
|
|
bb = growByteBuffer(bb);
|
|
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 -= 1, (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 -= 1, 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 -= 2, 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 -= 4, 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 -= 8, 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 -= 4, 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 -= 8, 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(1, 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(1, 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(2, 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(4, 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(8, 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(4, 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(8, 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
|
|
|
|
/**
|
|
* Encode the string `s` in the buffer using UTF-8.
|
|
*
|
|
* @param s The string to encode.
|
|
* @return The offset in the buffer where the encoded string starts.
|
|
*/
|
|
public int createString(String s) {
|
|
int length = s.length();
|
|
int estimatedDstCapacity = (int) (length * encoder.maxBytesPerChar());
|
|
if (dst == null || dst.capacity() < estimatedDstCapacity) {
|
|
dst = ByteBuffer.allocate(Math.max(128, estimatedDstCapacity));
|
|
}
|
|
|
|
dst.clear();
|
|
|
|
CharBuffer src = CharBuffer.wrap(s);
|
|
CoderResult result = encoder.encode(src, dst, true);
|
|
if (result.isError()) {
|
|
try {
|
|
result.throwException();
|
|
} catch (CharacterCodingException x) {
|
|
throw new Error(x);
|
|
}
|
|
}
|
|
|
|
dst.flip();
|
|
return createString(dst);
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
|
|
/// @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
|
|
* @ref flatbuffers_guide_use_java_c-sharp "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 startObject(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 #startObject(int)
|
|
*/
|
|
public int endObject() {
|
|
if (vtable == null || !nested)
|
|
throw new AssertionError("FlatBuffers: endObject called without startObject");
|
|
addInt(0);
|
|
int vtableloc = offset();
|
|
// Write out the current vtable.
|
|
for (int i = vtable_in_use - 1; 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)((vtable_in_use + standard_fields) * SIZEOF_SHORT));
|
|
|
|
// Search for an existing vtable that matches the current one.
|
|
int existing_vtable = 0;
|
|
outer_loop:
|
|
for (int 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.
|
|
*/
|
|
public void finish(int root_table) {
|
|
prep(minalign, SIZEOF_INT);
|
|
addOffset(root_table);
|
|
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.
|
|
* @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) {
|
|
prep(minalign, SIZEOF_INT + FILE_IDENTIFIER_LENGTH);
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* 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`.
|
|
*/
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// @}
|