Squashed 'third_party/flatbuffers/' content from commit acc9990ab
Change-Id: I48550d40d78fea996ebe74e9723a5d1f910de491
git-subtree-dir: third_party/flatbuffers
git-subtree-split: acc9990abd2206491480291b0f85f925110102ea
diff --git a/java/com/google/flatbuffers/ByteBufferUtil.java b/java/com/google/flatbuffers/ByteBufferUtil.java
new file mode 100644
index 0000000..624dc4e
--- /dev/null
+++ b/java/com/google/flatbuffers/ByteBufferUtil.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017 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.ByteBuffer;
+
+/// @file
+/// @addtogroup flatbuffers_java_api
+/// @{
+
+/**
+ * Class that collects utility functions around `ByteBuffer`.
+ */
+public class ByteBufferUtil {
+
+ /**
+ * Extract the size prefix from a `ByteBuffer`.
+ *
+ * @param bb a size-prefixed buffer
+ * @return the size prefix
+ */
+ public static int getSizePrefix(ByteBuffer bb) {
+ return bb.getInt(bb.position());
+ }
+
+ /**
+ * Create a duplicate of a size-prefixed `ByteBuffer` that has its position
+ * advanced just past the size prefix.
+ *
+ * @param bb a size-prefixed buffer
+ * @return a new buffer on the same underlying data that has skipped the
+ * size prefix
+ */
+ public static ByteBuffer removeSizePrefix(ByteBuffer bb) {
+ ByteBuffer s = bb.duplicate();
+ s.position(s.position() + SIZE_PREFIX_LENGTH);
+ return s;
+ }
+
+}
+
+/// @}
diff --git a/java/com/google/flatbuffers/Constants.java b/java/com/google/flatbuffers/Constants.java
new file mode 100644
index 0000000..e974a80
--- /dev/null
+++ b/java/com/google/flatbuffers/Constants.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+/// @cond FLATBUFFERS_INTERNAL
+
+/**
+ * Class that holds shared constants
+ */
+public class Constants {
+ // Java doesn't seem to have these.
+ /** The number of bytes in an `byte`. */
+ static final int SIZEOF_BYTE = 1;
+ /** The number of bytes in a `short`. */
+ static final int SIZEOF_SHORT = 2;
+ /** The number of bytes in an `int`. */
+ static final int SIZEOF_INT = 4;
+ /** The number of bytes in an `float`. */
+ static final int SIZEOF_FLOAT = 4;
+ /** The number of bytes in an `long`. */
+ static final int SIZEOF_LONG = 8;
+ /** The number of bytes in an `double`. */
+ static final int SIZEOF_DOUBLE = 8;
+ /** The number of bytes in a file identifier. */
+ static final int FILE_IDENTIFIER_LENGTH = 4;
+ /** The number of bytes in a size prefix. */
+ public static final int SIZE_PREFIX_LENGTH = 4;
+ /** A version identifier to force a compile error if someone
+ accidentally tries to build generated code with a runtime of
+ two mismatched version. Versions need to always match, as
+ the runtime and generated code are modified in sync.
+ Changes to the Java implementation need to be sure to change
+ the version here and in the code generator on every possible
+ incompatible change */
+ public static void FLATBUFFERS_1_11_1() {}
+}
+
+/// @endcond
diff --git a/java/com/google/flatbuffers/FlatBufferBuilder.java b/java/com/google/flatbuffers/FlatBufferBuilder.java
new file mode 100644
index 0000000..f224610
--- /dev/null
+++ b/java/com/google/flatbuffers/FlatBufferBuilder.java
@@ -0,0 +1,1012 @@
+/*
+ * 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;
+ }
+ }
+ }
+
+}
+
+/// @}
diff --git a/java/com/google/flatbuffers/Struct.java b/java/com/google/flatbuffers/Struct.java
new file mode 100644
index 0000000..c92164f
--- /dev/null
+++ b/java/com/google/flatbuffers/Struct.java
@@ -0,0 +1,61 @@
+/*
+ * 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 java.nio.ByteBuffer;
+
+/// @cond FLATBUFFERS_INTERNAL
+
+/**
+ * All structs in the generated code derive from this class, and add their own accessors.
+ */
+public class Struct {
+ /** Used to hold the position of the `bb` buffer. */
+ protected int bb_pos;
+ /** The underlying ByteBuffer to hold the data of the Struct. */
+ protected ByteBuffer bb;
+
+ /**
+ * Re-init the internal state with an external buffer {@code ByteBuffer} and an offset within.
+ *
+ * This method exists primarily to allow recycling Table instances without risking memory leaks
+ * due to {@code ByteBuffer} references.
+ */
+ protected void __reset(int _i, ByteBuffer _bb) {
+ bb = _bb;
+ if (bb != null) {
+ bb_pos = _i;
+ } else {
+ bb_pos = 0;
+ }
+ }
+
+ /**
+ * Resets internal state with a null {@code ByteBuffer} and a zero position.
+ *
+ * This method exists primarily to allow recycling Struct instances without risking memory leaks
+ * due to {@code ByteBuffer} references. The instance will be unusable until it is assigned
+ * again to a {@code ByteBuffer}.
+ *
+ * @param struct the instance to reset to initial state
+ */
+ public void __reset() {
+ __reset(0, null);
+ }
+}
+
+/// @endcond
diff --git a/java/com/google/flatbuffers/Table.java b/java/com/google/flatbuffers/Table.java
new file mode 100644
index 0000000..ff069f2
--- /dev/null
+++ b/java/com/google/flatbuffers/Table.java
@@ -0,0 +1,297 @@
+/*
+ * 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.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+
+/// @cond FLATBUFFERS_INTERNAL
+
+/**
+ * All tables in the generated code derive from this class, and add their own accessors.
+ */
+public class Table {
+ public final static ThreadLocal<Charset> UTF8_CHARSET = new ThreadLocal<Charset>() {
+ @Override
+ protected Charset initialValue() {
+ return Charset.forName("UTF-8");
+ }
+ };
+ /** Used to hold the position of the `bb` buffer. */
+ protected int bb_pos;
+ /** The underlying ByteBuffer to hold the data of the Table. */
+ protected ByteBuffer bb;
+ /** Used to hold the vtable position. */
+ private int vtable_start;
+ /** Used to hold the vtable size. */
+ private int vtable_size;
+ Utf8 utf8 = Utf8.getDefault();
+
+ /**
+ * Get the underlying ByteBuffer.
+ *
+ * @return Returns the Table's ByteBuffer.
+ */
+ public ByteBuffer getByteBuffer() { return bb; }
+
+ /**
+ * Look up a field in the vtable.
+ *
+ * @param vtable_offset An `int` offset to the vtable in the Table's ByteBuffer.
+ * @return Returns an offset into the object, or `0` if the field is not present.
+ */
+ protected int __offset(int vtable_offset) {
+ return vtable_offset < vtable_size ? bb.getShort(vtable_start + vtable_offset) : 0;
+ }
+
+ protected static int __offset(int vtable_offset, int offset, ByteBuffer bb) {
+ int vtable = bb.capacity() - offset;
+ return bb.getShort(vtable + vtable_offset - bb.getInt(vtable)) + vtable;
+ }
+
+ /**
+ * Retrieve a relative offset.
+ *
+ * @param offset An `int` index into the Table's ByteBuffer containing the relative offset.
+ * @return Returns the relative offset stored at `offset`.
+ */
+ protected int __indirect(int offset) {
+ return offset + bb.getInt(offset);
+ }
+
+ protected static int __indirect(int offset, ByteBuffer bb) {
+ return offset + bb.getInt(offset);
+ }
+
+ /**
+ * Create a Java `String` from UTF-8 data stored inside the FlatBuffer.
+ *
+ * This allocates a new string and converts to wide chars upon each access,
+ * which is not very efficient. Instead, each FlatBuffer string also comes with an
+ * accessor based on __vector_as_bytebuffer below, which is much more efficient,
+ * assuming your Java program can handle UTF-8 data directly.
+ *
+ * @param offset An `int` index into the Table's ByteBuffer.
+ * @return Returns a `String` from the data stored inside the FlatBuffer at `offset`.
+ */
+ protected String __string(int offset) {
+ offset += bb.getInt(offset);
+ int length = bb.getInt(offset);
+ return utf8.decodeUtf8(bb, offset + SIZEOF_INT, length);
+ }
+
+ /**
+ * Get the length of a vector.
+ *
+ * @param offset An `int` index into the Table's ByteBuffer.
+ * @return Returns the length of the vector whose offset is stored at `offset`.
+ */
+ protected int __vector_len(int offset) {
+ offset += bb_pos;
+ offset += bb.getInt(offset);
+ return bb.getInt(offset);
+ }
+
+ /**
+ * Get the start data of a vector.
+ *
+ * @param offset An `int` index into the Table's ByteBuffer.
+ * @return Returns the start of the vector data whose offset is stored at `offset`.
+ */
+ protected int __vector(int offset) {
+ offset += bb_pos;
+ return offset + bb.getInt(offset) + SIZEOF_INT; // data starts after the length
+ }
+
+ /**
+ * Get a whole vector as a ByteBuffer.
+ *
+ * This is efficient, since it only allocates a new {@link ByteBuffer} object,
+ * but does not actually copy the data, it still refers to the same bytes
+ * as the original ByteBuffer. Also useful with nested FlatBuffers, etc.
+ *
+ * @param vector_offset The position of the vector in the byte buffer
+ * @param elem_size The size of each element in the array
+ * @return The {@link ByteBuffer} for the array
+ */
+ protected ByteBuffer __vector_as_bytebuffer(int vector_offset, int elem_size) {
+ int o = __offset(vector_offset);
+ if (o == 0) return null;
+ ByteBuffer bb = this.bb.duplicate().order(ByteOrder.LITTLE_ENDIAN);
+ int vectorstart = __vector(o);
+ bb.position(vectorstart);
+ bb.limit(vectorstart + __vector_len(o) * elem_size);
+ return bb;
+ }
+
+ /**
+ * Initialize vector as a ByteBuffer.
+ *
+ * This is more efficient than using duplicate, since it doesn't copy the data
+ * nor allocattes a new {@link ByteBuffer}, creating no garbage to be collected.
+ *
+ * @param bb The {@link ByteBuffer} for the array
+ * @param vector_offset The position of the vector in the byte buffer
+ * @param elem_size The size of each element in the array
+ * @return The {@link ByteBuffer} for the array
+ */
+ protected ByteBuffer __vector_in_bytebuffer(ByteBuffer bb, int vector_offset, int elem_size) {
+ int o = this.__offset(vector_offset);
+ if (o == 0) return null;
+ int vectorstart = __vector(o);
+ bb.rewind();
+ bb.limit(vectorstart + __vector_len(o) * elem_size);
+ bb.position(vectorstart);
+ return bb;
+ }
+
+ /**
+ * Initialize any Table-derived type to point to the union at the given `offset`.
+ *
+ * @param t A `Table`-derived type that should point to the union at `offset`.
+ * @param offset An `int` index into the Table's ByteBuffer.
+ * @return Returns the Table that points to the union at `offset`.
+ */
+ protected Table __union(Table t, int offset) {
+ offset += bb_pos;
+ t.bb_pos = offset + bb.getInt(offset);
+ t.bb = bb;
+ t.vtable_start = t.bb_pos - bb.getInt(t.bb_pos);
+ t.vtable_size = bb.getShort(t.vtable_start);
+ return t;
+ }
+
+ /**
+ * Check if a {@link ByteBuffer} contains a file identifier.
+ *
+ * @param bb A {@code ByteBuffer} to check if it contains the identifier
+ * `ident`.
+ * @param ident A `String` identifier of the FlatBuffer file.
+ * @return True if the buffer contains the file identifier
+ */
+ protected static boolean __has_identifier(ByteBuffer bb, String ident) {
+ if (ident.length() != FILE_IDENTIFIER_LENGTH)
+ throw new AssertionError("FlatBuffers: file identifier must be length " +
+ FILE_IDENTIFIER_LENGTH);
+ for (int i = 0; i < FILE_IDENTIFIER_LENGTH; i++) {
+ if (ident.charAt(i) != (char)bb.get(bb.position() + SIZEOF_INT + i)) return false;
+ }
+ return true;
+ }
+
+ /**
+ * Sort tables by the key.
+ *
+ * @param offsets An 'int' indexes of the tables into the bb.
+ * @param bb A {@code ByteBuffer} to get the tables.
+ */
+ protected void sortTables(int[] offsets, final ByteBuffer bb) {
+ Integer[] off = new Integer[offsets.length];
+ for (int i = 0; i < offsets.length; i++) off[i] = offsets[i];
+ java.util.Arrays.sort(off, new java.util.Comparator<Integer>() {
+ public int compare(Integer o1, Integer o2) {
+ return keysCompare(o1, o2, bb);
+ }
+ });
+ for (int i = 0; i < offsets.length; i++) offsets[i] = off[i];
+ }
+
+ /**
+ * Compare two tables by the key.
+ *
+ * @param o1 An 'Integer' index of the first key into the bb.
+ * @param o2 An 'Integer' index of the second key into the bb.
+ * @param bb A {@code ByteBuffer} to get the keys.
+ */
+ protected int keysCompare(Integer o1, Integer o2, ByteBuffer bb) { return 0; }
+
+ /**
+ * Compare two strings in the buffer.
+ *
+ * @param offset_1 An 'int' index of the first string into the bb.
+ * @param offset_2 An 'int' index of the second string into the bb.
+ * @param bb A {@code ByteBuffer} to get the strings.
+ */
+ protected static int compareStrings(int offset_1, int offset_2, ByteBuffer bb) {
+ offset_1 += bb.getInt(offset_1);
+ offset_2 += bb.getInt(offset_2);
+ int len_1 = bb.getInt(offset_1);
+ int len_2 = bb.getInt(offset_2);
+ int startPos_1 = offset_1 + SIZEOF_INT;
+ int startPos_2 = offset_2 + SIZEOF_INT;
+ int len = Math.min(len_1, len_2);
+ for(int i = 0; i < len; i++) {
+ if (bb.get(i + startPos_1) != bb.get(i + startPos_2))
+ return bb.get(i + startPos_1) - bb.get(i + startPos_2);
+ }
+ return len_1 - len_2;
+ }
+
+ /**
+ * Compare string from the buffer with the 'String' object.
+ *
+ * @param offset_1 An 'int' index of the first string into the bb.
+ * @param key Second string as a byte array.
+ * @param bb A {@code ByteBuffer} to get the first string.
+ */
+ protected static int compareStrings(int offset_1, byte[] key, ByteBuffer bb) {
+ offset_1 += bb.getInt(offset_1);
+ int len_1 = bb.getInt(offset_1);
+ int len_2 = key.length;
+ int startPos_1 = offset_1 + Constants.SIZEOF_INT;
+ int len = Math.min(len_1, len_2);
+ for (int i = 0; i < len; i++) {
+ if (bb.get(i + startPos_1) != key[i])
+ return bb.get(i + startPos_1) - key[i];
+ }
+ return len_1 - len_2;
+ }
+
+ /**
+ * Re-init the internal state with an external buffer {@code ByteBuffer} and an offset within.
+ *
+ * This method exists primarily to allow recycling Table instances without risking memory leaks
+ * due to {@code ByteBuffer} references.
+ */
+ protected void __reset(int _i, ByteBuffer _bb) {
+ bb = _bb;
+ if (bb != null) {
+ bb_pos = _i;
+ vtable_start = bb_pos - bb.getInt(bb_pos);
+ vtable_size = bb.getShort(vtable_start);
+ } else {
+ bb_pos = 0;
+ vtable_start = 0;
+ vtable_size = 0;
+ }
+ }
+
+ /**
+ * Resets the internal state with a null {@code ByteBuffer} and a zero position.
+ *
+ * This method exists primarily to allow recycling Table instances without risking memory leaks
+ * due to {@code ByteBuffer} references. The instance will be unusable until it is assigned
+ * again to a {@code ByteBuffer}.
+ */
+ public void __reset() {
+ __reset(0, null);
+ }
+}
+
+/// @endcond
diff --git a/java/com/google/flatbuffers/Utf8.java b/java/com/google/flatbuffers/Utf8.java
new file mode 100644
index 0000000..efb6811
--- /dev/null
+++ b/java/com/google/flatbuffers/Utf8.java
@@ -0,0 +1,193 @@
+/*
+ * 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 java.nio.ByteBuffer;
+
+import static java.lang.Character.MIN_HIGH_SURROGATE;
+import static java.lang.Character.MIN_LOW_SURROGATE;
+import static java.lang.Character.MIN_SUPPLEMENTARY_CODE_POINT;
+
+public abstract class Utf8 {
+
+ /**
+ * Returns the number of bytes in the UTF-8-encoded form of {@code sequence}. For a string,
+ * this method is equivalent to {@code string.getBytes(UTF_8).length}, but is more efficient in
+ * both time and space.
+ *
+ * @throws IllegalArgumentException if {@code sequence} contains ill-formed UTF-16 (unpaired
+ * surrogates)
+ */
+ public abstract int encodedLength(CharSequence sequence);
+
+ /**
+ * Encodes the given characters to the target {@link ByteBuffer} using UTF-8 encoding.
+ *
+ * <p>Selects an optimal algorithm based on the type of {@link ByteBuffer} (i.e. heap or direct)
+ * and the capabilities of the platform.
+ *
+ * @param in the source string to be encoded
+ * @param out the target buffer to receive the encoded string.
+ */
+ public abstract void encodeUtf8(CharSequence in, ByteBuffer out);
+
+ /**
+ * Decodes the given UTF-8 portion of the {@link ByteBuffer} into a {@link String}.
+ *
+ * @throws IllegalArgumentException if the input is not valid UTF-8.
+ */
+ public abstract String decodeUtf8(ByteBuffer buffer, int offset, int length);
+
+ private static Utf8 DEFAULT;
+
+ /**
+ * Get the default UTF-8 processor.
+ * @return the default processor
+ */
+ public static Utf8 getDefault() {
+ if (DEFAULT == null) {
+ DEFAULT = new Utf8Safe();
+ }
+ return DEFAULT;
+ }
+
+ /**
+ * Set the default instance of the UTF-8 processor.
+ * @param instance the new instance to use
+ */
+ public static void setDefault(Utf8 instance) {
+ DEFAULT = instance;
+ }
+
+ /**
+ * Utility methods for decoding bytes into {@link String}. Callers are responsible for extracting
+ * bytes (possibly using Unsafe methods), and checking remaining bytes. All other UTF-8 validity
+ * checks and codepoint conversion happen in this class.
+ */
+ static class DecodeUtil {
+
+ /**
+ * Returns whether this is a single-byte codepoint (i.e., ASCII) with the form '0XXXXXXX'.
+ */
+ static boolean isOneByte(byte b) {
+ return b >= 0;
+ }
+
+ /**
+ * Returns whether this is a two-byte codepoint with the form '10XXXXXX'.
+ */
+ static boolean isTwoBytes(byte b) {
+ return b < (byte) 0xE0;
+ }
+
+ /**
+ * Returns whether this is a three-byte codepoint with the form '110XXXXX'.
+ */
+ static boolean isThreeBytes(byte b) {
+ return b < (byte) 0xF0;
+ }
+
+ static void handleOneByte(byte byte1, char[] resultArr, int resultPos) {
+ resultArr[resultPos] = (char) byte1;
+ }
+
+ static void handleTwoBytes(
+ byte byte1, byte byte2, char[] resultArr, int resultPos)
+ throws IllegalArgumentException {
+ // Simultaneously checks for illegal trailing-byte in leading position (<= '11000000') and
+ // overlong 2-byte, '11000001'.
+ if (byte1 < (byte) 0xC2) {
+ throw new IllegalArgumentException("Invalid UTF-8: Illegal leading byte in 2 bytes utf");
+ }
+ if (isNotTrailingByte(byte2)) {
+ throw new IllegalArgumentException("Invalid UTF-8: Illegal trailing byte in 2 bytes utf");
+ }
+ resultArr[resultPos] = (char) (((byte1 & 0x1F) << 6) | trailingByteValue(byte2));
+ }
+
+ static void handleThreeBytes(
+ byte byte1, byte byte2, byte byte3, char[] resultArr, int resultPos)
+ throws IllegalArgumentException {
+ if (isNotTrailingByte(byte2)
+ // overlong? 5 most significant bits must not all be zero
+ || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0)
+ // check for illegal surrogate codepoints
+ || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0)
+ || isNotTrailingByte(byte3)) {
+ throw new IllegalArgumentException("Invalid UTF-8");
+ }
+ resultArr[resultPos] = (char)
+ (((byte1 & 0x0F) << 12) | (trailingByteValue(byte2) << 6) | trailingByteValue(byte3));
+ }
+
+ static void handleFourBytes(
+ byte byte1, byte byte2, byte byte3, byte byte4, char[] resultArr, int resultPos)
+ throws IllegalArgumentException{
+ if (isNotTrailingByte(byte2)
+ // Check that 1 <= plane <= 16. Tricky optimized form of:
+ // valid 4-byte leading byte?
+ // if (byte1 > (byte) 0xF4 ||
+ // overlong? 4 most significant bits must not all be zero
+ // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 ||
+ // codepoint larger than the highest code point (U+10FFFF)?
+ // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F)
+ || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0
+ || isNotTrailingByte(byte3)
+ || isNotTrailingByte(byte4)) {
+ throw new IllegalArgumentException("Invalid UTF-8");
+ }
+ int codepoint = ((byte1 & 0x07) << 18)
+ | (trailingByteValue(byte2) << 12)
+ | (trailingByteValue(byte3) << 6)
+ | trailingByteValue(byte4);
+ resultArr[resultPos] = DecodeUtil.highSurrogate(codepoint);
+ resultArr[resultPos + 1] = DecodeUtil.lowSurrogate(codepoint);
+ }
+
+ /**
+ * Returns whether the byte is not a valid continuation of the form '10XXXXXX'.
+ */
+ private static boolean isNotTrailingByte(byte b) {
+ return b > (byte) 0xBF;
+ }
+
+ /**
+ * Returns the actual value of the trailing byte (removes the prefix '10') for composition.
+ */
+ private static int trailingByteValue(byte b) {
+ return b & 0x3F;
+ }
+
+ private static char highSurrogate(int codePoint) {
+ return (char) ((MIN_HIGH_SURROGATE - (MIN_SUPPLEMENTARY_CODE_POINT >>> 10))
+ + (codePoint >>> 10));
+ }
+
+ private static char lowSurrogate(int codePoint) {
+ return (char) (MIN_LOW_SURROGATE + (codePoint & 0x3ff));
+ }
+ }
+
+ // These UTF-8 handling methods are copied from Guava's Utf8Unsafe class with a modification to throw
+ // a protocol buffer local exception. This exception is then caught in CodedOutputStream so it can
+ // fallback to more lenient behavior.
+ static class UnpairedSurrogateException extends IllegalArgumentException {
+ UnpairedSurrogateException(int index, int length) {
+ super("Unpaired surrogate at index " + index + " of " + length);
+ }
+ }
+}
diff --git a/java/com/google/flatbuffers/Utf8Old.java b/java/com/google/flatbuffers/Utf8Old.java
new file mode 100644
index 0000000..3dac714
--- /dev/null
+++ b/java/com/google/flatbuffers/Utf8Old.java
@@ -0,0 +1,99 @@
+/*
+ * 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 java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * This class implements the Utf8 API using the Java Utf8 encoder. Use
+ * Utf8.setDefault(new Utf8Old()); to use it.
+ */
+public class Utf8Old extends Utf8 {
+
+ private static class Cache {
+ final CharsetEncoder encoder;
+ final CharsetDecoder decoder;
+ CharSequence lastInput = null;
+ ByteBuffer lastOutput = null;
+
+ Cache() {
+ encoder = StandardCharsets.UTF_8.newEncoder();
+ decoder = StandardCharsets.UTF_8.newDecoder();
+ }
+ }
+
+ private static final ThreadLocal<Cache> CACHE =
+ ThreadLocal.withInitial(() -> new Cache());
+
+ // Play some games so that the old encoder doesn't pay twice for computing
+ // the length of the encoded string.
+
+ @Override
+ public int encodedLength(CharSequence in) {
+ final Cache cache = CACHE.get();
+ int estimated = (int) (in.length() * cache.encoder.maxBytesPerChar());
+ if (cache.lastOutput == null || cache.lastOutput.capacity() < estimated) {
+ cache.lastOutput = ByteBuffer.allocate(Math.max(128, estimated));
+ }
+ cache.lastOutput.clear();
+ cache.lastInput = in;
+ CharBuffer wrap = (in instanceof CharBuffer) ?
+ (CharBuffer) in : CharBuffer.wrap(in);
+ CoderResult result = cache.encoder.encode(wrap, cache.lastOutput, true);
+ if (result.isError()) {
+ try {
+ result.throwException();
+ } catch (CharacterCodingException e) {
+ throw new IllegalArgumentException("bad character encoding", e);
+ }
+ }
+ cache.lastOutput.flip();
+ return cache.lastOutput.remaining();
+ }
+
+ @Override
+ public void encodeUtf8(CharSequence in, ByteBuffer out) {
+ final Cache cache = CACHE.get();
+ if (cache.lastInput != in) {
+ // Update the lastOutput to match our input, although flatbuffer should
+ // never take this branch.
+ encodedLength(in);
+ }
+ out.put(cache.lastOutput);
+ }
+
+ @Override
+ public String decodeUtf8(ByteBuffer buffer, int offset, int length) {
+ CharsetDecoder decoder = CACHE.get().decoder;
+ decoder.reset();
+ buffer = buffer.duplicate();
+ buffer.position(offset);
+ buffer.limit(offset + length);
+ try {
+ CharBuffer result = decoder.decode(buffer);
+ return result.toString();
+ } catch (CharacterCodingException e) {
+ throw new IllegalArgumentException("Bad encoding", e);
+ }
+ }
+}
diff --git a/java/com/google/flatbuffers/Utf8Safe.java b/java/com/google/flatbuffers/Utf8Safe.java
new file mode 100644
index 0000000..06ea420
--- /dev/null
+++ b/java/com/google/flatbuffers/Utf8Safe.java
@@ -0,0 +1,451 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.flatbuffers;
+
+import java.nio.ByteBuffer;
+import static java.lang.Character.MAX_SURROGATE;
+import static java.lang.Character.MIN_SUPPLEMENTARY_CODE_POINT;
+import static java.lang.Character.MIN_SURROGATE;
+import static java.lang.Character.isSurrogatePair;
+import static java.lang.Character.toCodePoint;
+
+/**
+ * A set of low-level, high-performance static utility methods related
+ * to the UTF-8 character encoding. This class has no dependencies
+ * outside of the core JDK libraries.
+ *
+ * <p>There are several variants of UTF-8. The one implemented by
+ * this class is the restricted definition of UTF-8 introduced in
+ * Unicode 3.1, which mandates the rejection of "overlong" byte
+ * sequences as well as rejection of 3-byte surrogate codepoint byte
+ * sequences. Note that the UTF-8 decoder included in Oracle's JDK
+ * has been modified to also reject "overlong" byte sequences, but (as
+ * of 2011) still accepts 3-byte surrogate codepoint byte sequences.
+ *
+ * <p>The byte sequences considered valid by this class are exactly
+ * those that can be roundtrip converted to Strings and back to bytes
+ * using the UTF-8 charset, without loss: <pre> {@code
+ * Arrays.equals(bytes, new String(bytes, Internal.UTF_8).getBytes(Internal.UTF_8))
+ * }</pre>
+ *
+ * <p>See the Unicode Standard,</br>
+ * Table 3-6. <em>UTF-8 Bit Distribution</em>,</br>
+ * Table 3-7. <em>Well Formed UTF-8 Byte Sequences</em>.
+ */
+final public class Utf8Safe extends Utf8 {
+
+ /**
+ * Returns the number of bytes in the UTF-8-encoded form of {@code sequence}. For a string,
+ * this method is equivalent to {@code string.getBytes(UTF_8).length}, but is more efficient in
+ * both time and space.
+ *
+ * @throws IllegalArgumentException if {@code sequence} contains ill-formed UTF-16 (unpaired
+ * surrogates)
+ */
+ private static int computeEncodedLength(CharSequence sequence) {
+ // Warning to maintainers: this implementation is highly optimized.
+ int utf16Length = sequence.length();
+ int utf8Length = utf16Length;
+ int i = 0;
+
+ // This loop optimizes for pure ASCII.
+ while (i < utf16Length && sequence.charAt(i) < 0x80) {
+ i++;
+ }
+
+ // This loop optimizes for chars less than 0x800.
+ for (; i < utf16Length; i++) {
+ char c = sequence.charAt(i);
+ if (c < 0x800) {
+ utf8Length += ((0x7f - c) >>> 31); // branch free!
+ } else {
+ utf8Length += encodedLengthGeneral(sequence, i);
+ break;
+ }
+ }
+
+ if (utf8Length < utf16Length) {
+ // Necessary and sufficient condition for overflow because of maximum 3x expansion
+ throw new IllegalArgumentException("UTF-8 length does not fit in int: "
+ + (utf8Length + (1L << 32)));
+ }
+ return utf8Length;
+ }
+
+ private static int encodedLengthGeneral(CharSequence sequence, int start) {
+ int utf16Length = sequence.length();
+ int utf8Length = 0;
+ for (int i = start; i < utf16Length; i++) {
+ char c = sequence.charAt(i);
+ if (c < 0x800) {
+ utf8Length += (0x7f - c) >>> 31; // branch free!
+ } else {
+ utf8Length += 2;
+ // jdk7+: if (Character.isSurrogate(c)) {
+ if (Character.MIN_SURROGATE <= c && c <= Character.MAX_SURROGATE) {
+ // Check that we have a well-formed surrogate pair.
+ int cp = Character.codePointAt(sequence, i);
+ if (cp < MIN_SUPPLEMENTARY_CODE_POINT) {
+ throw new Utf8Safe.UnpairedSurrogateException(i, utf16Length);
+ }
+ i++;
+ }
+ }
+ }
+ return utf8Length;
+ }
+
+ private static String decodeUtf8Array(byte[] bytes, int index, int size) {
+ // Bitwise OR combines the sign bits so any negative value fails the check.
+ if ((index | size | bytes.length - index - size) < 0) {
+ throw new ArrayIndexOutOfBoundsException(
+ String.format("buffer length=%d, index=%d, size=%d", bytes.length, index, size));
+ }
+
+ int offset = index;
+ final int limit = offset + size;
+
+ // The longest possible resulting String is the same as the number of input bytes, when it is
+ // all ASCII. For other cases, this over-allocates and we will truncate in the end.
+ char[] resultArr = new char[size];
+ int resultPos = 0;
+
+ // Optimize for 100% ASCII (Hotspot loves small simple top-level loops like this).
+ // This simple loop stops when we encounter a byte >= 0x80 (i.e. non-ASCII).
+ while (offset < limit) {
+ byte b = bytes[offset];
+ if (!DecodeUtil.isOneByte(b)) {
+ break;
+ }
+ offset++;
+ DecodeUtil.handleOneByte(b, resultArr, resultPos++);
+ }
+
+ while (offset < limit) {
+ byte byte1 = bytes[offset++];
+ if (DecodeUtil.isOneByte(byte1)) {
+ DecodeUtil.handleOneByte(byte1, resultArr, resultPos++);
+ // It's common for there to be multiple ASCII characters in a run mixed in, so add an
+ // extra optimized loop to take care of these runs.
+ while (offset < limit) {
+ byte b = bytes[offset];
+ if (!DecodeUtil.isOneByte(b)) {
+ break;
+ }
+ offset++;
+ DecodeUtil.handleOneByte(b, resultArr, resultPos++);
+ }
+ } else if (DecodeUtil.isTwoBytes(byte1)) {
+ if (offset >= limit) {
+ throw new IllegalArgumentException("Invalid UTF-8");
+ }
+ DecodeUtil.handleTwoBytes(byte1, /* byte2 */ bytes[offset++], resultArr, resultPos++);
+ } else if (DecodeUtil.isThreeBytes(byte1)) {
+ if (offset >= limit - 1) {
+ throw new IllegalArgumentException("Invalid UTF-8");
+ }
+ DecodeUtil.handleThreeBytes(
+ byte1,
+ /* byte2 */ bytes[offset++],
+ /* byte3 */ bytes[offset++],
+ resultArr,
+ resultPos++);
+ } else {
+ if (offset >= limit - 2) {
+ throw new IllegalArgumentException("Invalid UTF-8");
+ }
+ DecodeUtil.handleFourBytes(
+ byte1,
+ /* byte2 */ bytes[offset++],
+ /* byte3 */ bytes[offset++],
+ /* byte4 */ bytes[offset++],
+ resultArr,
+ resultPos++);
+ // 4-byte case requires two chars.
+ resultPos++;
+ }
+ }
+
+ return new String(resultArr, 0, resultPos);
+ }
+
+ private static String decodeUtf8Buffer(ByteBuffer buffer, int offset,
+ int length) {
+ // Bitwise OR combines the sign bits so any negative value fails the check.
+ if ((offset | length | buffer.limit() - offset - length) < 0) {
+ throw new ArrayIndexOutOfBoundsException(
+ String.format("buffer limit=%d, index=%d, limit=%d", buffer.limit(),
+ offset, length));
+ }
+
+ final int limit = offset + length;
+
+ // The longest possible resulting String is the same as the number of input bytes, when it is
+ // all ASCII. For other cases, this over-allocates and we will truncate in the end.
+ char[] resultArr = new char[length];
+ int resultPos = 0;
+
+ // Optimize for 100% ASCII (Hotspot loves small simple top-level loops like this).
+ // This simple loop stops when we encounter a byte >= 0x80 (i.e. non-ASCII).
+ while (offset < limit) {
+ byte b = buffer.get(offset);
+ if (!DecodeUtil.isOneByte(b)) {
+ break;
+ }
+ offset++;
+ DecodeUtil.handleOneByte(b, resultArr, resultPos++);
+ }
+
+ while (offset < limit) {
+ byte byte1 = buffer.get(offset++);
+ if (DecodeUtil.isOneByte(byte1)) {
+ DecodeUtil.handleOneByte(byte1, resultArr, resultPos++);
+ // It's common for there to be multiple ASCII characters in a run mixed in, so add an
+ // extra optimized loop to take care of these runs.
+ while (offset < limit) {
+ byte b = buffer.get(offset);
+ if (!DecodeUtil.isOneByte(b)) {
+ break;
+ }
+ offset++;
+ DecodeUtil.handleOneByte(b, resultArr, resultPos++);
+ }
+ } else if (DecodeUtil.isTwoBytes(byte1)) {
+ if (offset >= limit) {
+ throw new IllegalArgumentException("Invalid UTF-8");
+ }
+ DecodeUtil.handleTwoBytes(
+ byte1, /* byte2 */ buffer.get(offset++), resultArr, resultPos++);
+ } else if (DecodeUtil.isThreeBytes(byte1)) {
+ if (offset >= limit - 1) {
+ throw new IllegalArgumentException("Invalid UTF-8");
+ }
+ DecodeUtil.handleThreeBytes(
+ byte1,
+ /* byte2 */ buffer.get(offset++),
+ /* byte3 */ buffer.get(offset++),
+ resultArr,
+ resultPos++);
+ } else {
+ if (offset >= limit - 2) {
+ throw new IllegalArgumentException("Invalid UTF-8");
+ }
+ DecodeUtil.handleFourBytes(
+ byte1,
+ /* byte2 */ buffer.get(offset++),
+ /* byte3 */ buffer.get(offset++),
+ /* byte4 */ buffer.get(offset++),
+ resultArr,
+ resultPos++);
+ // 4-byte case requires two chars.
+ resultPos++;
+ }
+ }
+
+ return new String(resultArr, 0, resultPos);
+ }
+
+ @Override
+ public int encodedLength(CharSequence in) {
+ return computeEncodedLength(in);
+ }
+
+ /**
+ * Decodes the given UTF-8 portion of the {@link ByteBuffer} into a {@link String}.
+ *
+ * @throws IllegalArgumentException if the input is not valid UTF-8.
+ */
+ @Override
+ public String decodeUtf8(ByteBuffer buffer, int offset, int length)
+ throws IllegalArgumentException {
+ if (buffer.hasArray()) {
+ return decodeUtf8Array(buffer.array(), buffer.arrayOffset() + offset, length);
+ } else {
+ return decodeUtf8Buffer(buffer, offset, length);
+ }
+ }
+
+
+ private static void encodeUtf8Buffer(CharSequence in, ByteBuffer out) {
+ final int inLength = in.length();
+ int outIx = out.position();
+ int inIx = 0;
+
+ // Since ByteBuffer.putXXX() already checks boundaries for us, no need to explicitly check
+ // access. Assume the buffer is big enough and let it handle the out of bounds exception
+ // if it occurs.
+ try {
+ // Designed to take advantage of
+ // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination
+ for (char c; inIx < inLength && (c = in.charAt(inIx)) < 0x80; ++inIx) {
+ out.put(outIx + inIx, (byte) c);
+ }
+ if (inIx == inLength) {
+ // Successfully encoded the entire string.
+ out.position(outIx + inIx);
+ return;
+ }
+
+ outIx += inIx;
+ for (char c; inIx < inLength; ++inIx, ++outIx) {
+ c = in.charAt(inIx);
+ if (c < 0x80) {
+ // One byte (0xxx xxxx)
+ out.put(outIx, (byte) c);
+ } else if (c < 0x800) {
+ // Two bytes (110x xxxx 10xx xxxx)
+
+ // Benchmarks show put performs better than putShort here (for HotSpot).
+ out.put(outIx++, (byte) (0xC0 | (c >>> 6)));
+ out.put(outIx, (byte) (0x80 | (0x3F & c)));
+ } else if (c < MIN_SURROGATE || MAX_SURROGATE < c) {
+ // Three bytes (1110 xxxx 10xx xxxx 10xx xxxx)
+ // Maximum single-char code point is 0xFFFF, 16 bits.
+
+ // Benchmarks show put performs better than putShort here (for HotSpot).
+ out.put(outIx++, (byte) (0xE0 | (c >>> 12)));
+ out.put(outIx++, (byte) (0x80 | (0x3F & (c >>> 6))));
+ out.put(outIx, (byte) (0x80 | (0x3F & c)));
+ } else {
+ // Four bytes (1111 xxxx 10xx xxxx 10xx xxxx 10xx xxxx)
+
+ // Minimum code point represented by a surrogate pair is 0x10000, 17 bits, four UTF-8
+ // bytes
+ final char low;
+ if (inIx + 1 == inLength || !isSurrogatePair(c, (low = in.charAt(++inIx)))) {
+ throw new UnpairedSurrogateException(inIx, inLength);
+ }
+ // TODO(nathanmittler): Consider using putInt() to improve performance.
+ int codePoint = toCodePoint(c, low);
+ out.put(outIx++, (byte) ((0xF << 4) | (codePoint >>> 18)));
+ out.put(outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 12))));
+ out.put(outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 6))));
+ out.put(outIx, (byte) (0x80 | (0x3F & codePoint)));
+ }
+ }
+
+ // Successfully encoded the entire string.
+ out.position(outIx);
+ } catch (IndexOutOfBoundsException e) {
+ // TODO(nathanmittler): Consider making the API throw IndexOutOfBoundsException instead.
+
+ // If we failed in the outer ASCII loop, outIx will not have been updated. In this case,
+ // use inIx to determine the bad write index.
+ int badWriteIndex = out.position() + Math.max(inIx, outIx - out.position() + 1);
+ throw new ArrayIndexOutOfBoundsException(
+ "Failed writing " + in.charAt(inIx) + " at index " + badWriteIndex);
+ }
+ }
+
+ private static int encodeUtf8Array(CharSequence in, byte[] out,
+ int offset, int length) {
+ int utf16Length = in.length();
+ int j = offset;
+ int i = 0;
+ int limit = offset + length;
+ // Designed to take advantage of
+ // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination
+ for (char c; i < utf16Length && i + j < limit && (c = in.charAt(i)) < 0x80; i++) {
+ out[j + i] = (byte) c;
+ }
+ if (i == utf16Length) {
+ return j + utf16Length;
+ }
+ j += i;
+ for (char c; i < utf16Length; i++) {
+ c = in.charAt(i);
+ if (c < 0x80 && j < limit) {
+ out[j++] = (byte) c;
+ } else if (c < 0x800 && j <= limit - 2) { // 11 bits, two UTF-8 bytes
+ out[j++] = (byte) ((0xF << 6) | (c >>> 6));
+ out[j++] = (byte) (0x80 | (0x3F & c));
+ } else if ((c < Character.MIN_SURROGATE || Character.MAX_SURROGATE < c) && j <= limit - 3) {
+ // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes
+ out[j++] = (byte) ((0xF << 5) | (c >>> 12));
+ out[j++] = (byte) (0x80 | (0x3F & (c >>> 6)));
+ out[j++] = (byte) (0x80 | (0x3F & c));
+ } else if (j <= limit - 4) {
+ // Minimum code point represented by a surrogate pair is 0x10000, 17 bits,
+ // four UTF-8 bytes
+ final char low;
+ if (i + 1 == in.length()
+ || !Character.isSurrogatePair(c, (low = in.charAt(++i)))) {
+ throw new UnpairedSurrogateException((i - 1), utf16Length);
+ }
+ int codePoint = Character.toCodePoint(c, low);
+ out[j++] = (byte) ((0xF << 4) | (codePoint >>> 18));
+ out[j++] = (byte) (0x80 | (0x3F & (codePoint >>> 12)));
+ out[j++] = (byte) (0x80 | (0x3F & (codePoint >>> 6)));
+ out[j++] = (byte) (0x80 | (0x3F & codePoint));
+ } else {
+ // If we are surrogates and we're not a surrogate pair, always throw an
+ // UnpairedSurrogateException instead of an ArrayOutOfBoundsException.
+ if ((Character.MIN_SURROGATE <= c && c <= Character.MAX_SURROGATE)
+ && (i + 1 == in.length()
+ || !Character.isSurrogatePair(c, in.charAt(i + 1)))) {
+ throw new UnpairedSurrogateException(i, utf16Length);
+ }
+ throw new ArrayIndexOutOfBoundsException("Failed writing " + c + " at index " + j);
+ }
+ }
+ return j;
+ }
+
+ /**
+ * Encodes the given characters to the target {@link ByteBuffer} using UTF-8 encoding.
+ *
+ * <p>Selects an optimal algorithm based on the type of {@link ByteBuffer} (i.e. heap or direct)
+ * and the capabilities of the platform.
+ *
+ * @param in the source string to be encoded
+ * @param out the target buffer to receive the encoded string.
+ */
+ @Override
+ public void encodeUtf8(CharSequence in, ByteBuffer out) {
+ if (out.hasArray()) {
+ int start = out.arrayOffset();
+ int end = encodeUtf8Array(in, out.array(), start + out.position(),
+ out.remaining());
+ out.position(end - start);
+ } else {
+ encodeUtf8Buffer(in, out);
+ }
+ }
+
+ // These UTF-8 handling methods are copied from Guava's Utf8Unsafe class with
+ // a modification to throw a local exception. This exception can be caught
+ // to fallback to more lenient behavior.
+ static class UnpairedSurrogateException extends IllegalArgumentException {
+ UnpairedSurrogateException(int index, int length) {
+ super("Unpaired surrogate at index " + index + " of " + length);
+ }
+ }
+}