mirror of
https://github.com/google/flatbuffers.git
synced 2026-06-02 12:05:50 +00:00
Related with issue #6113. `(old_buf_size & 0xC0000000) != 0` checks if we can duplicate old_buf_size and still be under 2GB (by checking if bit 30 or 31 is 1). This doesn't allow buffers larger than 1GB. The strategy now is to allocate a buffer with the maximum array size when we detect that we are overflowing the 2GB. Also changed default buffer size to 1024.
1121 lines
41 KiB
Java
1121 lines
41 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.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;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/// @}
|