| /* |
| * Copyright 2021 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. |
| */ |
| @file:Suppress("NOTHING_TO_INLINE") |
| |
| package com.google.flatbuffers.kotlin |
| |
| @ExperimentalUnsignedTypes |
| public class FlexBuffersBuilder( |
| public val buffer: ReadWriteBuffer, |
| private val shareFlag: Int = SHARE_KEYS |
| ) { |
| |
| public constructor(initialCapacity: Int = 1024, shareFlag: Int = SHARE_KEYS) : |
| this(ArrayReadWriteBuffer(initialCapacity), shareFlag) |
| |
| private val stringValuePool: HashMap<String, Value> = HashMap() |
| private val stringKeyPool: HashMap<String, Int> = HashMap() |
| private val stack: MutableList<Value> = mutableListOf() |
| private var finished: Boolean = false |
| |
| /** |
| * Reset the FlexBuffersBuilder by purging all data that it holds. Buffer might |
| * keep its capacity after a reset. |
| */ |
| public fun clear() { |
| buffer.clear() |
| stringValuePool.clear() |
| stringKeyPool.clear() |
| stack.clear() |
| finished = false |
| } |
| |
| /** |
| * Finish writing the message into the buffer. After that no other element must |
| * be inserted into the buffer. Also, you must call this function before start using the |
| * FlexBuffer message |
| * @return [ReadBuffer] containing the FlexBuffer message |
| */ |
| public fun finish(): ReadBuffer { |
| // If you hit this, you likely have objects that were never included |
| // in a parent. You need to have exactly one root to finish a buffer. |
| // Check your Start/End calls are matched, and all objects are inside |
| // some other object. |
| if (stack.size != 1) error("There is must be only on object as root. Current ${stack.size}.") |
| // Write root value. |
| val byteWidth = align(stack[0].elemWidth(buffer.writePosition, 0)) |
| buffer.requestAdditionalCapacity(byteWidth.value + 2) |
| writeAny(stack[0], byteWidth) |
| // Write root type. |
| buffer.put(stack[0].storedPackedType()) |
| // Write root size. Normally determined by parent, but root has no parent :) |
| buffer.put(byteWidth.value.toByte()) |
| this.finished = true |
| return buffer // TODO: make a read-only shallow copy |
| } |
| |
| /** |
| * Insert a single [Boolean] into the buffer |
| * @param value true or false |
| */ |
| public fun put(value: Boolean): Unit = run { this[null] = value } |
| |
| /** |
| * Insert a null reference into the buffer. A key must be present if element is inserted into a map. |
| */ |
| public fun putNull(key: String? = null): Unit = |
| run { stack.add(Value(T_NULL, putKey(key), W_8, 0UL)) } |
| |
| /** |
| * Insert a single [Boolean] into the buffer. A key must be present if element is inserted into a map. |
| */ |
| public operator fun set(key: String? = null, value: Boolean): Unit = |
| run { stack.add(Value(T_BOOL, putKey(key), W_8, if (value) 1UL else 0UL)) } |
| |
| /** |
| * Insert a single [Byte] into the buffer |
| */ |
| public fun put(value: Byte): Unit = set(null, value.toLong()) |
| |
| /** |
| * Insert a single [Byte] into the buffer. A key must be present if element is inserted into a map. |
| */ |
| public operator fun set(key: String? = null, value: Byte): Unit = set(key, value.toLong()) |
| |
| /** |
| * Insert a single [Short] into the buffer. |
| */ |
| public fun put(value: Short): Unit = set(null, value.toLong()) |
| |
| /** |
| * Insert a single [Short] into the buffer. A key must be present if element is inserted into a map. |
| */ |
| public inline operator fun set(key: String? = null, value: Short): Unit = set(key, value.toLong()) |
| |
| /** |
| * Insert a single [Int] into the buffer. |
| */ |
| public fun put(value: Int): Unit = set(null, value.toLong()) |
| |
| /** |
| * Insert a single [Int] into the buffer. A key must be present if element is inserted into a map. |
| */ |
| public inline operator fun set(key: String? = null, value: Int): Unit = set(key, value.toLong()) |
| |
| /** |
| * Insert a single [Long] into the buffer. |
| */ |
| public fun put(value: Long): Unit = set(null, value) |
| |
| /** |
| * Insert a single [Long] into the buffer. A key must be present if element is inserted into a map. |
| */ |
| public operator fun set(key: String? = null, value: Long): Unit = |
| run { stack.add(Value(T_INT, putKey(key), value.toULong().widthInUBits(), value.toULong())) } |
| |
| /** |
| * Insert a single [UByte] into the buffer |
| */ |
| public fun put(value: UByte): Unit = set(null, value.toULong()) |
| |
| /** |
| * Insert a single [UByte] into the buffer. A key must be present if element is inserted into a map. |
| */ |
| public inline operator fun set(key: String? = null, value: UByte): Unit = set(key, value.toULong()) |
| |
| /** |
| * Insert a single [UShort] into the buffer. |
| */ |
| public fun put(value: UShort): Unit = set(null, value.toULong()) |
| |
| /** |
| * Insert a single [UShort] into the buffer. A key must be present if element is inserted into a map. |
| */ |
| private inline operator fun set(key: String? = null, value: UShort): Unit = set(key, value.toULong()) |
| |
| /** |
| * Insert a single [UInt] into the buffer. |
| */ |
| public fun put(value: UInt): Unit = set(null, value.toULong()) |
| |
| /** |
| * Insert a single [UInt] into the buffer. A key must be present if element is inserted into a map. |
| */ |
| private inline operator fun set(key: String? = null, value: UInt): Unit = set(key, value.toULong()) |
| |
| /** |
| * Insert a single [ULong] into the buffer. |
| */ |
| public fun put(value: ULong): Unit = set(null, value) |
| |
| /** |
| * Insert a single [ULong] into the buffer. A key must be present if element is inserted into a map. |
| */ |
| public operator fun set(key: String? = null, value: ULong): Unit = |
| run { stack.add(Value(T_UINT, putKey(key), value.widthInUBits(), value)) } |
| |
| /** |
| * Insert a single [Float] into the buffer. |
| */ |
| public fun put(value: Float): Unit = run { this[null] = value } |
| |
| /** |
| * Insert a single [Float] into the buffer. A key must be present if element is inserted into a map. |
| */ |
| public operator fun set(key: String? = null, value: Float): Unit = |
| run { stack.add(Value(T_FLOAT, putKey(key), W_32, dValue = value.toDouble())) } |
| |
| /** |
| * Insert a single [Double] into the buffer. |
| */ |
| public fun put(value: Double): Unit = run { this[null] = value } |
| |
| /** |
| * Insert a single [Double] into the buffer. A key must be present if element is inserted into a map. |
| */ |
| public operator fun set(key: String? = null, value: Double): Unit = |
| run { stack.add(Value(T_FLOAT, putKey(key), W_64, dValue = value)) } |
| |
| /** |
| * Insert a single [String] into the buffer. |
| */ |
| public fun put(value: String): Int = set(null, value) |
| |
| /** |
| * Insert a single [String] into the buffer. A key must be present if element is inserted into a map. |
| */ |
| public operator fun set(key: String? = null, value: String): Int { |
| val iKey = putKey(key) |
| val holder = if (shareFlag and SHARE_STRINGS != 0) { |
| stringValuePool.getOrPut(value) { |
| writeString(iKey, value).also { stringValuePool[value] = it } |
| }.copy(key = iKey) |
| } else { |
| writeString(iKey, value) |
| } |
| stack.add(holder) |
| return holder.iValue.toInt() |
| } |
| |
| /** |
| * Adds a [ByteArray] into the message as a [Blob]. |
| * @param value byte array |
| * @return position in buffer as the start of byte array |
| */ |
| public fun put(value: ByteArray): Int = set(null, value) |
| |
| /** |
| * Adds a [ByteArray] into the message as a [Blob]. A key must be present if element is inserted into a map. |
| * @param value byte array |
| * @return position in buffer as the start of byte array |
| */ |
| public operator fun set(key: String? = null, value: ByteArray): Int { |
| val element = writeBlob(putKey(key), value, T_BLOB, false) |
| stack.add(element) |
| return element.iValue.toInt() |
| } |
| |
| /** |
| * Adds a [IntArray] into the message as a typed vector of fixed size. |
| * @param value [IntArray] |
| * @return position in buffer as the start of byte array |
| */ |
| public fun put(value: IntArray): Int = set(null, value) |
| |
| /** |
| * Adds a [IntArray] into the message as a typed vector of fixed size. |
| * A key must be present if element is inserted into a map. |
| * @param value [IntArray] |
| * @return position in buffer as the start of byte array |
| */ |
| public operator fun set(key: String? = null, value: IntArray): Int = |
| setTypedVector(key, value.size, T_VECTOR_INT, value.widthInUBits()) { writeIntArray(value, it) } |
| |
| /** |
| * Adds a [ShortArray] into the message as a typed vector of fixed size. |
| * @param value [ShortArray] |
| * @return position in buffer as the start of byte array |
| */ |
| public fun put(value: ShortArray): Int = set(null, value) |
| |
| /** |
| * Adds a [ShortArray] into the message as a typed vector of fixed size. |
| * A key must be present if element is inserted into a map. |
| * @param value [ShortArray] |
| * @return position in buffer as the start of byte array |
| */ |
| public operator fun set(key: String? = null, value: ShortArray): Int = |
| setTypedVector(key, value.size, T_VECTOR_INT, value.widthInUBits()) { writeIntArray(value, it) } |
| |
| /** |
| * Adds a [LongArray] into the message as a typed vector of fixed size. |
| * @param value [LongArray] |
| * @return position in buffer as the start of byte array |
| */ |
| public fun put(value: LongArray): Int = set(null, value) |
| |
| /** |
| * Adds a [LongArray] into the message as a typed vector of fixed size. |
| * A key must be present if element is inserted into a map. |
| * @param value [LongArray] |
| * @return position in buffer as the start of byte array |
| */ |
| public operator fun set(key: String? = null, value: LongArray): Int = |
| setTypedVector(key, value.size, T_VECTOR_INT, value.widthInUBits()) { writeIntArray(value, it) } |
| |
| /** |
| * Adds a [FloatArray] into the message as a typed vector of fixed size. |
| * @param value [FloatArray] |
| * @return position in buffer as the start of byte array |
| */ |
| public fun put(value: FloatArray): Int = set(null, value) |
| |
| /** |
| * Adds a [FloatArray] into the message as a typed vector of fixed size. |
| * A key must be present if element is inserted into a map. |
| * @param value [FloatArray] |
| * @return position in buffer as the start of byte array |
| */ |
| public operator fun set(key: String? = null, value: FloatArray): Int = |
| setTypedVector(key, value.size, T_VECTOR_FLOAT, W_32) { writeFloatArray(value) } |
| |
| /** |
| * Adds a [DoubleArray] into the message as a typed vector of fixed size. |
| * @param value [DoubleArray] |
| * @return position in buffer as the start of byte array |
| */ |
| public fun put(value: DoubleArray): Int = set(null, value) |
| |
| /** |
| * Adds a [DoubleArray] into the message as a typed vector of fixed size. |
| * A key must be present if element is inserted into a map. |
| * @param value [DoubleArray] |
| * @return position in buffer as the start of byte array |
| */ |
| public operator fun set(key: String? = null, value: DoubleArray): Int = |
| setTypedVector(key, value.size, T_VECTOR_FLOAT, W_64) { writeFloatArray(value) } |
| |
| /** |
| * Adds a [UByteArray] into the message as a typed vector of fixed size. |
| * @param value [UByteArray] |
| * @return position in buffer as the start of byte array |
| */ |
| public fun put(value: UByteArray): Int = set(null, value) |
| |
| /** |
| * Adds a [UByteArray] into the message as a typed vector of fixed size. |
| * A key must be present if element is inserted into a map. |
| * @param value [UByteArray] |
| * @return position in buffer as the start of byte array |
| */ |
| public operator fun set(key: String? = null, value: UByteArray): Int = |
| setTypedVec(key) { value.forEach { put(it) } } |
| |
| /** |
| * Adds a [UShortArray] into the message as a typed vector of fixed size. |
| * @param value [UShortArray] |
| * @return position in buffer as the start of byte array |
| */ |
| public fun put(value: UShortArray): Int = set(null, value) |
| |
| /** |
| * Adds a [UShortArray] into the message as a typed vector of fixed size. |
| * A key must be present if element is inserted into a map. |
| * @param value [UShortArray] |
| * @return position in buffer as the start of byte array |
| */ |
| public operator fun set(key: String? = null, value: UShortArray): Int = |
| setTypedVec(key) { value.forEach { put(it) } } |
| |
| /** |
| * Adds a [UIntArray] into the message as a typed vector of fixed size. |
| * @param value [UIntArray] |
| * @return position in buffer as the start of byte array |
| */ |
| public fun put(value: UIntArray): Int = set(null, value) |
| |
| /** |
| * Adds a [UIntArray] into the message as a typed vector of fixed size. |
| * A key must be present if element is inserted into a map. |
| * @param value [UIntArray] |
| * @return position in buffer as the start of byte array |
| */ |
| public fun set(key: String? = null, value: UIntArray): Int = |
| setTypedVec(key) { value.forEach { put(it) } } |
| |
| /** |
| * Adds a [ULongArray] into the message as a typed vector of fixed size. |
| * @param value [ULongArray] |
| * @return position in buffer as the start of byte array |
| */ |
| public fun put(value: ULongArray): Int = set(null, value) |
| |
| /** |
| * Adds a [ULongArray] into the message as a typed vector of fixed size. |
| * A key must be present if element is inserted into a map. |
| * @param value [ULongArray] |
| * @return position in buffer as the start of byte array |
| */ |
| public operator fun set(key: String? = null, value: ULongArray): Int = |
| setTypedVec(key) { value.forEach { put(it) } } |
| |
| /** |
| * Creates a new vector will all elements inserted in [block]. |
| * @param block where elements will be inserted |
| * @return position in buffer as the start of byte array |
| */ |
| public inline fun putVector(crossinline block: FlexBuffersBuilder.() -> Unit): Int { |
| val pos = startVector() |
| this.block() |
| return endVector(pos) |
| } |
| |
| /** |
| * Creates a new typed vector will all elements inserted in [block]. |
| * @param block where elements will be inserted |
| * @return position in buffer as the start of byte array |
| */ |
| public inline fun putTypedVector(crossinline block: FlexBuffersBuilder.() -> Unit): Int { |
| val pos = startVector() |
| this.block() |
| return endTypedVector(pos) |
| } |
| |
| /** |
| * Helper function to return position for starting a new vector. |
| */ |
| public fun startVector(): Int = stack.size |
| |
| /** |
| * Finishes a vector element. The initial position of the vector must be passed |
| * @param position position at the start of the vector |
| */ |
| public fun endVector(position: Int): Int = endVector(null, position) |
| |
| /** |
| * Finishes a vector element. The initial position of the vector must be passed |
| * @param position position at the start of the vector |
| */ |
| public fun endVector(key: String? = null, position: Int): Int = |
| endAnyVector(position) { createVector(putKey(key), position, stack.size - position) } |
| /** |
| * Finishes a typed vector element. The initial position of the vector must be passed |
| * @param position position at the start of the vector |
| */ |
| public fun endTypedVector(position: Int): Int = endTypedVector(position, null) |
| |
| /** |
| * Helper function to return position for starting a new vector. |
| */ |
| public fun startMap(): Int = stack.size |
| |
| /** |
| * Creates a new map will all elements inserted in [block]. |
| * @param block where elements will be inserted |
| * @return position in buffer as the start of byte array |
| */ |
| public inline fun putMap(key: String? = null, crossinline block: FlexBuffersBuilder.() -> Unit): Int { |
| val pos = startMap() |
| this.block() |
| return endMap(pos, key) |
| } |
| |
| /** |
| * Finishes a map, but writing the information in the buffer |
| * @param key key used to store element in map |
| * @return Reference to the map |
| */ |
| public fun endMap(start: Int, key: String? = null): Int { |
| stack.subList(start, stack.size).sortWith(keyComparator) |
| val length = stack.size - start |
| val keys = createKeyVector(start, length) |
| val vec = putMap(putKey(key), start, length, keys) |
| // Remove temp elements and return map. |
| while (stack.size > start) { |
| stack.removeAt(stack.size - 1) |
| } |
| stack.add(vec) |
| return vec.iValue.toInt() |
| } |
| |
| private inline fun setTypedVector( |
| key: String? = null, |
| length: Int, |
| vecType: FlexBufferType, |
| bitWidth: BitWidth, |
| crossinline writeBlock: (ByteWidth) -> Unit |
| ): Int { |
| val keyPos = putKey(key) |
| val byteWidth = align(bitWidth) |
| // Write vector. First the keys width/offset if available, and size. |
| // write the size |
| writeInt(length, byteWidth) |
| |
| // Then the actual data. |
| val vloc: Int = buffer.writePosition |
| writeBlock(byteWidth) |
| stack.add(Value(vecType, keyPos, bitWidth, vloc.toULong())) |
| return vloc |
| } |
| |
| private inline fun setTypedVec(key: String? = null, crossinline block: FlexBuffersBuilder.() -> Unit): Int { |
| val pos = startVector() |
| this.block() |
| return endTypedVector(pos, key) |
| } |
| |
| public fun endTypedVector(position: Int, key: String? = null): Int = |
| endAnyVector(position) { createTypedVector(putKey(key), position, stack.size - position) } |
| |
| private inline fun endAnyVector(start: Int, crossinline creationBlock: () -> Value): Int { |
| val vec = creationBlock() |
| // Remove temp elements and return vector. |
| while (stack.size > start) { |
| stack.removeLast() |
| } |
| stack.add(vec) |
| return vec.iValue.toInt() |
| } |
| |
| private inline fun putKey(key: String? = null): Int { |
| if (key == null) return -1 |
| return if ((shareFlag and SHARE_KEYS) != 0) { |
| stringKeyPool.getOrPut(key) { |
| val pos: Int = buffer.writePosition |
| val encodedKeySize = Utf8.encodedLength(key) |
| buffer.requestAdditionalCapacity(encodedKeySize + 1) |
| buffer.put(key, encodedKeySize) |
| buffer.put(ZeroByte) |
| pos |
| } |
| } else { |
| val pos: Int = buffer.writePosition |
| val encodedKeySize = Utf8.encodedLength(key) |
| buffer.requestAdditionalCapacity(encodedKeySize + 1) |
| buffer.put(key, encodedKeySize) |
| buffer.put(ZeroByte) |
| pos |
| } |
| } |
| |
| private fun writeAny(toWrite: Value, byteWidth: ByteWidth) = when (toWrite.type) { |
| T_NULL, T_BOOL, T_INT, T_UINT -> writeInt(toWrite.iValue, byteWidth) |
| T_FLOAT -> writeDouble(toWrite.dValue, byteWidth) |
| else -> writeOffset(toWrite.iValue.toInt(), byteWidth) |
| } |
| |
| private fun writeString(key: Int, s: String): Value { |
| val encodedSize = Utf8.encodedLength(s) |
| val bitWidth = encodedSize.toULong().widthInUBits() |
| val byteWidth = align(bitWidth) |
| |
| writeInt(encodedSize, byteWidth) |
| |
| buffer.requestAdditionalCapacity(encodedSize + 1) |
| val sloc: Int = buffer.writePosition |
| if (encodedSize > 0) |
| buffer.put(s, encodedSize) |
| buffer.put(ZeroByte) |
| return Value(T_STRING, key, bitWidth, sloc.toULong()) |
| } |
| |
| private fun writeDouble(toWrite: Double, byteWidth: ByteWidth) { |
| buffer.requestAdditionalCapacity(byteWidth.value) |
| when (byteWidth.value) { |
| 4 -> buffer.put(toWrite.toFloat()) |
| 8 -> buffer.put(toWrite) |
| else -> Unit |
| } |
| } |
| |
| private fun writeOffset(toWrite: Int, byteWidth: ByteWidth) { |
| buffer.requestAdditionalCapacity(byteWidth.value) |
| val relativeOffset = (buffer.writePosition - toWrite) |
| if (byteWidth.value != 8 && relativeOffset >= 1L shl byteWidth.value * 8) error("invalid offset $relativeOffset, writer pos ${buffer.writePosition}") |
| writeInt(relativeOffset, byteWidth) |
| } |
| |
| private inline fun writeBlob(key: Int, blob: ByteArray, type: FlexBufferType, trailing: Boolean): Value { |
| val bitWidth = blob.size.toULong().widthInUBits() |
| val byteWidth = align(bitWidth) |
| |
| writeInt(blob.size, byteWidth) |
| |
| val sloc: Int = buffer.writePosition |
| buffer.requestAdditionalCapacity(blob.size + trailing.compareTo(false)) |
| buffer.put(blob, 0, blob.size) |
| if (trailing) { |
| buffer.put(ZeroByte) |
| } |
| return Value(type, key, bitWidth, sloc.toULong()) |
| } |
| |
| private fun writeIntArray(value: IntArray, byteWidth: ByteWidth) = |
| writeIntegerArray(0, value.size, byteWidth) { value[it].toULong() } |
| |
| private fun writeIntArray(value: ShortArray, byteWidth: ByteWidth) = |
| writeIntegerArray(0, value.size, byteWidth) { value[it].toULong() } |
| |
| private fun writeIntArray(value: LongArray, byteWidth: ByteWidth) = |
| writeIntegerArray(0, value.size, byteWidth) { value[it].toULong() } |
| |
| private fun writeFloatArray(value: FloatArray) { |
| buffer.requestAdditionalCapacity(Float.SIZE_BYTES * value.size) |
| value.forEach { buffer.put(it) } |
| } |
| |
| private fun writeFloatArray(value: DoubleArray) { |
| buffer.requestAdditionalCapacity(Double.SIZE_BYTES * value.size) |
| value.forEach { buffer.put(it) } |
| } |
| |
| private inline fun writeIntegerArray( |
| start: Int, |
| size: Int, |
| byteWidth: ByteWidth, |
| crossinline valueBlock: (Int) -> ULong |
| ) { |
| buffer.requestAdditionalCapacity(size * byteWidth.value) |
| return when (byteWidth.value) { |
| 1 -> for (i in start until start + size) { |
| buffer.put(valueBlock(i).toUByte()) |
| } |
| 2 -> for (i in start until start + size) { |
| buffer.put(valueBlock(i).toUShort()) |
| } |
| 4 -> for (i in start until start + size) { |
| buffer.put(valueBlock(i).toUInt()) |
| } |
| 8 -> for (i in start until start + size) { |
| buffer.put(valueBlock(i)) |
| } |
| else -> Unit |
| } |
| } |
| |
| private fun writeInt(value: Int, byteWidth: ByteWidth) { |
| buffer.requestAdditionalCapacity(byteWidth.value) |
| when (byteWidth.value) { |
| 1 -> buffer.put(value.toUByte()) |
| 2 -> buffer.put(value.toUShort()) |
| 4 -> buffer.put(value.toUInt()) |
| 8 -> buffer.put(value.toULong()) |
| else -> Unit |
| } |
| } |
| |
| private fun writeInt(value: ULong, byteWidth: ByteWidth) { |
| buffer.requestAdditionalCapacity(byteWidth.value) |
| when(byteWidth.value) { |
| 1 -> buffer.put(value.toUByte()) |
| 2 -> buffer.put(value.toUShort()) |
| 4 -> buffer.put(value.toUInt()) |
| 8 -> buffer.put(value) |
| else -> Unit |
| } |
| } |
| |
| // Align to prepare for writing a scalar with a certain size. |
| // returns the amounts of bytes needed to be written. |
| private fun align(alignment: BitWidth): ByteWidth { |
| val byteWidth = 1 shl alignment.value |
| var padBytes = paddingBytes(buffer.writePosition, byteWidth) |
| buffer.requestCapacity(buffer.capacity + padBytes) |
| while (padBytes-- != 0) { |
| buffer.put(ZeroByte) |
| } |
| return ByteWidth(byteWidth) |
| } |
| |
| private fun calculateKeyVectorBitWidth(start: Int, length: Int): BitWidth { |
| val bitWidth = length.toULong().widthInUBits() |
| var width = bitWidth |
| val prefixElems = 1 |
| // Check bit widths and types for all elements. |
| for (i in start until stack.size) { |
| val elemWidth = elemWidth(T_KEY, W_8, stack[i].key.toLong(), buffer.writePosition, i + prefixElems) |
| width = width.max(elemWidth) |
| } |
| return width |
| } |
| |
| private fun createKeyVector(start: Int, length: Int): Value { |
| // Figure out smallest bit width we can store this vector with. |
| val bitWidth = calculateKeyVectorBitWidth(start, length) |
| val byteWidth = align(bitWidth) |
| // Write vector. First the keys width/offset if available, and size. |
| writeInt(length, byteWidth) |
| // Then the actual data. |
| val vloc = buffer.writePosition.toULong() |
| for (i in start until stack.size) { |
| val pos = stack[i].key |
| if (pos == -1) error("invalid position $pos for key") |
| writeOffset(stack[i].key, byteWidth) |
| } |
| // Then the types. |
| return Value(T_VECTOR_KEY, -1, bitWidth, vloc) |
| } |
| |
| private inline fun createVector(key: Int, start: Int, length: Int, keys: Value? = null): Value { |
| return createAnyVector(key, start, length, T_VECTOR, keys) { |
| // add types since we are not creating a typed vector. |
| buffer.requestAdditionalCapacity(stack.size) |
| for (i in start until stack.size) { |
| buffer.put(stack[i].storedPackedType(it)) |
| } |
| } |
| } |
| |
| private fun putMap(key: Int, start: Int, length: Int, keys: Value? = null): Value { |
| return createAnyVector(key, start, length, T_MAP, keys) { |
| // add types since we are not creating a typed vector. |
| buffer.requestAdditionalCapacity(stack.size) |
| for (i in start until stack.size) { |
| buffer.put(stack[i].storedPackedType(it)) |
| } |
| } |
| } |
| |
| private inline fun createTypedVector(key: Int, start: Int, length: Int, keys: Value? = null): Value { |
| // We assume the callers of this method guarantees all elements are of the same type. |
| val elementType: FlexBufferType = stack[start].type |
| for (i in start + 1 until length) { |
| if (elementType != stack[i].type) error("TypedVector does not support array of different element types") |
| } |
| if (!elementType.isTypedVectorElementType()) error("TypedVector does not support this element type") |
| return createAnyVector(key, start, length, elementType.toTypedVector(), keys) |
| } |
| |
| private inline fun createAnyVector( |
| key: Int, |
| start: Int, |
| length: Int, |
| type: FlexBufferType, |
| keys: Value? = null, |
| crossinline typeBlock: (BitWidth) -> Unit = {} |
| ): Value { |
| // Figure out the smallest bit width we can store this vector with. |
| var bitWidth = W_8.max(length.toULong().widthInUBits()) |
| var prefixElems = 1 |
| if (keys != null) { |
| // If this vector is part of a map, we will pre-fix an offset to the keys |
| // to this vector. |
| bitWidth = bitWidth.max(keys.elemWidth(buffer.writePosition, 0)) |
| prefixElems += 2 |
| } |
| // Check bit widths and types for all elements. |
| for (i in start until stack.size) { |
| val elemWidth = stack[i].elemWidth(buffer.writePosition, i + prefixElems) |
| bitWidth = bitWidth.max(elemWidth) |
| } |
| val byteWidth = align(bitWidth) |
| // Write vector. First the keys width/offset if available, and size. |
| if (keys != null) { |
| writeOffset(keys.iValue.toInt(), byteWidth) |
| writeInt(1 shl keys.minBitWidth.value, byteWidth) |
| } |
| // write the size |
| writeInt(length, byteWidth) |
| |
| // Then the actual data. |
| val vloc: Int = buffer.writePosition |
| for (i in start until stack.size) { |
| writeAny(stack[i], byteWidth) |
| } |
| |
| // Optionally you can introduce the types for non-typed vector |
| typeBlock(bitWidth) |
| return Value(type, key, bitWidth, vloc.toULong()) |
| } |
| |
| // A lambda to sort map keys |
| internal val keyComparator = object : Comparator<Value> { |
| override fun compare(a: Value, b: Value): Int { |
| var ia: Int = a.key |
| var io: Int = b.key |
| var c1: Byte |
| var c2: Byte |
| do { |
| c1 = buffer[ia] |
| c2 = buffer[io] |
| if (c1.toInt() == 0) return c1 - c2 |
| ia++ |
| io++ |
| } while (c1 == c2) |
| return c1 - c2 |
| } |
| } |
| |
| public companion object { |
| /** |
| * No keys or strings will be shared |
| */ |
| public const val SHARE_NONE: Int = 0 |
| |
| /** |
| * Keys will be shared between elements. Identical keys will only be serialized once, thus possibly saving space. |
| * But serialization performance might be slower and consumes more memory. |
| */ |
| public const val SHARE_KEYS: Int = 1 |
| |
| /** |
| * Strings will be shared between elements. Identical strings will only be serialized once, thus possibly saving space. |
| * But serialization performance might be slower and consumes more memory. This is ideal if you expect many repeated |
| * strings on the message. |
| */ |
| public const val SHARE_STRINGS: Int = 2 |
| |
| /** |
| * Strings and keys will be shared between elements. |
| */ |
| public const val SHARE_KEYS_AND_STRINGS: Int = 3 |
| } |
| } |