Merge commit '0f6aab9da6fe982218a01f4a5b896e65fcced437' as 'third_party/flatbuffers'

This commit is contained in:
Siarhei Fedartsou
2024-06-22 13:33:34 +02:00
1814 changed files with 326902 additions and 0 deletions
@@ -0,0 +1,152 @@
import groovy.xml.XmlParser
plugins {
kotlin("multiplatform")
id("org.jetbrains.kotlinx.benchmark")
id("io.morethan.jmhreport")
id("de.undercouch.download")
}
group = "com.google.flatbuffers.jmh"
version = "2.0.0-SNAPSHOT"
// Reads latest version from Java's runtime pom.xml,
// so we can use it for benchmarking against Kotlin's
// runtime
fun readJavaFlatBufferVersion(): String {
val pom = XmlParser().parse(File("../java/pom.xml"))
val versionTag = pom.children().find {
val node = it as groovy.util.Node
node.name().toString().contains("version")
} as groovy.util.Node
return versionTag.value().toString()
}
// This plugin generates a static html page with the aggregation
// of all benchmarks ran. very useful visualization tool.
jmhReport {
val baseFolder = project.file("build/reports/benchmarks/main").absolutePath
val lastFolder = project.file(baseFolder).list()?.sortedArray()?.lastOrNull() ?: ""
jmhResultPath = "$baseFolder/$lastFolder/jvm.json"
jmhReportOutput = "$baseFolder/$lastFolder"
}
// For now we benchmark on JVM only
benchmark {
configurations {
this.getByName("main") {
iterations = 5
iterationTime = 300
iterationTimeUnit = "ms"
// uncomment for benchmarking JSON op only
include(".*FlatbufferBenchmark.*")
}
}
targets {
register("jvm")
}
}
kotlin {
jvm {
compilations {
val main by getting { }
// custom benchmark compilation
val benchmarks by compilations.creating {
defaultSourceSet {
dependencies {
// Compile against the main compilation's compile classpath and outputs:
implementation(main.compileDependencyFiles + main.output.classesDirs)
}
}
}
}
}
sourceSets {
val jvmMain by getting {
dependencies {
implementation(kotlin("stdlib-common"))
implementation(project(":flatbuffers-kotlin"))
implementation(libs.kotlinx.benchmark.runtime)
// json serializers
implementation(libs.moshi.kotlin)
implementation(libs.gson)
}
kotlin.srcDir("src/jvmMain/generated/kotlin/")
kotlin.srcDir("src/jvmMain/generated/java/")
kotlin.srcDir("../../java/src/main/java")
}
}
}
// This task download all JSON files used for benchmarking
tasks.register<de.undercouch.gradle.tasks.download.Download>("downloadMultipleFiles") {
// We are downloading json benchmark samples from serdes-rs project.
// see: https://github.com/serde-rs/json-benchmark/blob/master/data
val baseUrl = "https://github.com/serde-rs/json-benchmark/raw/master/data/"
src(listOf("$baseUrl/canada.json", "$baseUrl/twitter.json", "$baseUrl/citm_catalog.json"))
dest(File("${project.projectDir.absolutePath}/src/jvmMain/resources"))
overwrite(false)
}
abstract class GenerateFBTestClasses : DefaultTask() {
@get:InputFiles
abstract val inputFiles: ConfigurableFileCollection
@get:Input
abstract val includeFolder: Property<String>
@get:Input
abstract val outputFolder: Property<String>
@get:Input
abstract val variants: ListProperty<String>
@Inject
protected open fun getExecActionFactory(): org.gradle.process.internal.ExecActionFactory? {
throw UnsupportedOperationException()
}
init {
includeFolder.set("")
}
@TaskAction
fun compile() {
val execAction = getExecActionFactory()!!.newExecAction()
val sources = inputFiles.asPath.split(":")
val langs = variants.get().map { "--$it" }
val args = mutableListOf("flatc","-o", outputFolder.get(), *langs.toTypedArray())
if (includeFolder.get().isNotEmpty()) {
args.add("-I")
args.add(includeFolder.get())
}
args.addAll(sources)
println(args)
execAction.commandLine = args
print(execAction.execute())
}
}
// Use the default greeting
tasks.register<GenerateFBTestClasses>("generateFBTestClassesKt") {
inputFiles.setFrom("$projectDir/monster_test_kotlin.fbs")
includeFolder.set("$rootDir/../tests/include_test")
outputFolder.set("${projectDir}/src/jvmMain/generated/kotlin/")
variants.addAll("kotlin-kmp")
}
tasks.register<GenerateFBTestClasses>("generateFBTestClassesJava") {
inputFiles.setFrom("$projectDir/monster_test_java.fbs")
includeFolder.set("$rootDir/../tests/include_test")
outputFolder.set("${projectDir}/src/jvmMain/generated/java/")
variants.addAll("kotlin")
}
project.tasks.forEach {
if (it.name.contains("compileKotlin")) {
it.dependsOn("generateFBTestClassesKt")
it.dependsOn("generateFBTestClassesJava")
}
}
@@ -0,0 +1,37 @@
// Example IDL file for our monster's schema.
namespace jmonster;
enum JColor:byte { Red = 0, Green, Blue = 2 }
union JEquipment { JWeapon } // Optionally add more tables.
struct JVec3 {
x:float;
y:float;
z:float;
}
table JMonster {
pos:JVec3;
mana:short = 150;
hp:short = 100;
name:string;
friendly:bool = false (deprecated);
inventory:[ubyte];
color:JColor = Blue;
weapons:[JWeapon];
equipped:JEquipment;
path:[JVec3];
}
table JWeapon {
name:string;
damage:short;
}
table JAllMonsters {
monsters: [JMonster];
}
root_type JAllMonsters;
@@ -0,0 +1,37 @@
// Example IDL file for our monster's schema.
namespace monster;
enum Color:byte { Red = 0, Green, Blue = 2 }
union Equipment { Weapon } // Optionally add more tables.
struct Vec3 {
x:float;
y:float;
z:float;
}
table Monster {
pos:Vec3;
mana:short = 150;
hp:short = 100;
name:string;
friendly:bool = false (deprecated);
inventory:[ubyte];
color:Color = Blue;
weapons:[Weapon];
equipped:Equipment;
path:[Vec3];
}
table Weapon {
name:string;
damage:short;
}
table AllMonsters {
monsters: [Monster];
}
root_type AllMonsters;
@@ -0,0 +1,129 @@
@file:OptIn(ExperimentalUnsignedTypes::class)
package com.google.flatbuffers.kotlin.benchmark
import com.google.flatbuffers.kotlin.FlatBufferBuilder
import jmonster.JAllMonsters
import jmonster.JColor
import jmonster.JMonster
import jmonster.JVec3
import monster.AllMonsters
import monster.AllMonsters.Companion.createAllMonsters
import monster.AllMonsters.Companion.createMonstersVector
import monster.Monster
import monster.Monster.Companion.createInventoryVector
import monster.MonsterOffsetArray
import monster.Vec3
import org.openjdk.jmh.annotations.*
import org.openjdk.jmh.infra.Blackhole
import java.util.concurrent.TimeUnit
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Measurement(iterations = 20, time = 1, timeUnit = TimeUnit.NANOSECONDS)
open class FlatbufferBenchmark {
val repetition = 1000000
val fbKotlin = FlatBufferBuilder(1024 * repetition)
val fbDeserializationKotlin = FlatBufferBuilder(1024 * repetition)
val fbJava = com.google.flatbuffers.FlatBufferBuilder(1024 * repetition)
val fbDeserializationJava = com.google.flatbuffers.FlatBufferBuilder(1024 * repetition)
init {
populateMosterKotlin(fbDeserializationKotlin)
populateMosterJava(fbDeserializationJava)
}
@OptIn(ExperimentalUnsignedTypes::class)
private fun populateMosterKotlin(fb: FlatBufferBuilder) {
fb.clear()
val monsterName = fb.createString("MonsterName");
val items = ubyteArrayOf(0u, 1u, 2u, 3u, 4u)
val inv = createInventoryVector(fb, items)
val monsterOffsets: MonsterOffsetArray = MonsterOffsetArray(repetition) {
Monster.startMonster(fb)
Monster.addName(fb, monsterName)
Monster.addPos(fb, Vec3.createVec3(fb, 1.0f, 2.0f, 3.0f))
Monster.addHp(fb, 80)
Monster.addMana(fb, 150)
Monster.addInventory(fb, inv)
Monster.addColor(fb, monster.Color.Red)
Monster.endMonster(fb)
}
val monsters = createMonstersVector(fb, monsterOffsets)
val allMonsters = createAllMonsters(fb, monsters)
fb.finish(allMonsters)
}
@OptIn(ExperimentalUnsignedTypes::class)
private fun populateMosterJava(fb: com.google.flatbuffers.FlatBufferBuilder){
fb.clear()
val monsterName = fb.createString("MonsterName");
val inv = JMonster.createInventoryVector(fb, ubyteArrayOf(0u, 1u, 2u, 3u, 4u))
val monsters = JAllMonsters.createMonstersVector(fb, IntArray(repetition) {
JMonster.startJMonster(fb)
JMonster.addName(fb, monsterName)
JMonster.addPos(fb, JVec3.createJVec3(fb, 1.0f, 2.0f, 3.0f))
JMonster.addHp(fb, 80)
JMonster.addMana(fb, 150)
JMonster.addInventory(fb, inv)
JMonster.addColor(fb, JColor.Red)
JMonster.endJMonster(fb)
})
val allMonsters = JAllMonsters.createJAllMonsters(fb, monsters)
fb.finish(allMonsters)
}
@Benchmark
fun monstersSerializationKotlin() {
populateMosterKotlin(fbKotlin)
}
@OptIn(ExperimentalUnsignedTypes::class)
@Benchmark
fun monstersDeserializationKotlin(hole: Blackhole) {
val monstersRef = AllMonsters.asRoot(fbDeserializationKotlin.dataBuffer())
for (i in 0 until monstersRef.monstersLength) {
val monster = monstersRef.monsters(i)!!
val pos = monster.pos!!
hole.consume(monster.name)
hole.consume(pos.x)
hole.consume(pos.y)
hole.consume(pos.z)
hole.consume(monster.hp)
hole.consume(monster.mana)
hole.consume(monster.color)
hole.consume(monster.inventory(0).toByte())
hole.consume(monster.inventory(1))
hole.consume(monster.inventory(2))
hole.consume(monster.inventory(3))
}
}
@Benchmark
fun monstersSerializationJava() {
populateMosterJava(fbJava)
}
@Benchmark
fun monstersDeserializationJava(hole: Blackhole) {
val monstersRef = JAllMonsters.getRootAsJAllMonsters(fbDeserializationJava.dataBuffer())
for (i in 0 until monstersRef.monstersLength) {
val monster = monstersRef.monsters(i)!!
val pos = monster.pos!!
hole.consume(monster.name)
hole.consume(pos.x)
hole.consume(pos.y)
hole.consume(pos.z)
hole.consume(monster.hp)
hole.consume(monster.mana)
hole.consume(monster.color)
hole.consume(monster.inventory(0))
hole.consume(monster.inventory(1))
hole.consume(monster.inventory(2))
hole.consume(monster.inventory(3))
}
}
}
@@ -0,0 +1,199 @@
/*
* 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:OptIn(ExperimentalUnsignedTypes::class)
package com.google.flatbuffers.kotlin.benchmark
import com.google.flatbuffers.ArrayReadWriteBuf
import com.google.flatbuffers.FlexBuffers
import com.google.flatbuffers.FlexBuffersBuilder.BUILDER_FLAG_SHARE_ALL
import com.google.flatbuffers.kotlin.FlexBuffersBuilder
import com.google.flatbuffers.kotlin.getRoot
import kotlinx.benchmark.Blackhole
import org.openjdk.jmh.annotations.Benchmark
import org.openjdk.jmh.annotations.BenchmarkMode
import org.openjdk.jmh.annotations.Measurement
import org.openjdk.jmh.annotations.Mode
import org.openjdk.jmh.annotations.OutputTimeUnit
import org.openjdk.jmh.annotations.Scope
import org.openjdk.jmh.annotations.Setup
import org.openjdk.jmh.annotations.State
import java.util.concurrent.TimeUnit
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Measurement(iterations = 20, time = 1, timeUnit = TimeUnit.NANOSECONDS)
open class FlexBuffersBenchmark {
var initialCapacity = 1024
var value: Double = 0.0
val stringKey = Array(500) { "Ḧ̵̘́ȩ̵̐myFairlyBigKey$it" }
val stringValue = Array(500) { "Ḧ̵̘́ȩ̵̐myFairlyBigValue$it" }
val bigIntArray = IntArray(5000) { it }
@Setup
fun setUp() {
value = 3.0
}
@Benchmark
open fun mapKotlin(blackhole: Blackhole) {
val kBuilder = FlexBuffersBuilder(initialCapacity, FlexBuffersBuilder.SHARE_KEYS_AND_STRINGS)
kBuilder.putMap {
this["hello"] = "world"
this["int"] = 10
this["float"] = 12.3
this["intarray"] = bigIntArray
this.putMap("myMap") {
this["cool"] = "beans"
}
}
val ref = getRoot(kBuilder.finish())
val map = ref.toMap()
blackhole.consume(map.size)
blackhole.consume(map["hello"].toString())
blackhole.consume(map["int"].toInt())
blackhole.consume(map["float"].toDouble())
blackhole.consume(map["intarray"].toIntArray())
blackhole.consume(ref["myMap"]["cool"].toString())
blackhole.consume(ref["invalid_key"].isNull)
}
@Benchmark
open fun mapJava(blackhole: Blackhole) {
val jBuilder = com.google.flatbuffers.FlexBuffersBuilder(ArrayReadWriteBuf(initialCapacity), BUILDER_FLAG_SHARE_ALL)
val startMap = jBuilder.startMap()
jBuilder.putString("hello", "world")
jBuilder.putInt("int", 10)
jBuilder.putFloat("float", 12.3)
val startVec = jBuilder.startVector()
bigIntArray.forEach { jBuilder.putInt(it) }
jBuilder.endVector("intarray", startVec, true, false)
val startInnerMap = jBuilder.startMap()
jBuilder.putString("cool", "beans")
jBuilder.endMap("myMap", startInnerMap)
jBuilder.endMap(null, startMap)
val ref = FlexBuffers.getRoot(jBuilder.finish())
val map = ref.asMap()
blackhole.consume(map.size())
blackhole.consume(map.get("hello").toString())
blackhole.consume(map.get("int").asInt())
blackhole.consume(map.get("float").asFloat())
val vec = map.get("intarray").asVector()
blackhole.consume(IntArray(vec.size()) { vec.get(it).asInt() })
blackhole.consume(ref.asMap()["myMap"].asMap()["cool"].toString())
blackhole.consume(ref.asMap()["invalid_key"].isNull)
}
@Benchmark
open fun intArrayKotlin(blackhole: Blackhole) {
val kBuilder = FlexBuffersBuilder(initialCapacity, FlexBuffersBuilder.SHARE_KEYS_AND_STRINGS)
kBuilder.put(bigIntArray)
val root = getRoot(kBuilder.finish())
blackhole.consume(root.toIntArray())
}
@Benchmark
open fun intArrayJava(blackhole: Blackhole) {
val jBuilder = com.google.flatbuffers.FlexBuffersBuilder(ArrayReadWriteBuf(initialCapacity), BUILDER_FLAG_SHARE_ALL)
val v = jBuilder.startVector()
bigIntArray.forEach { jBuilder.putInt(it) }
jBuilder.endVector(null, v, true, false)
jBuilder.finish()
val root = FlexBuffers.getRoot(jBuilder.buffer)
val vec = root.asVector()
blackhole.consume(
IntArray(vec.size()) {
vec[it].asInt()
}
)
}
@Benchmark
open fun stringArrayKotlin(blackhole: Blackhole) {
val kBuilder = FlexBuffersBuilder(initialCapacity, FlexBuffersBuilder.SHARE_KEYS_AND_STRINGS)
kBuilder.putVector { stringValue.forEach { kBuilder.put(it) } }
kBuilder.finish()
val root = getRoot(kBuilder.buffer)
val vec = root.toVector()
blackhole.consume(Array(vec.size) { vec[it].toString() })
}
@Benchmark
open fun stringArrayJava(blackhole: Blackhole) {
val jBuilder = com.google.flatbuffers.FlexBuffersBuilder(ArrayReadWriteBuf(initialCapacity), BUILDER_FLAG_SHARE_ALL)
val v = jBuilder.startVector()
stringValue.forEach { jBuilder.putString(it) }
jBuilder.endVector(null, v, false, false)
jBuilder.finish()
val root = FlexBuffers.getRoot(jBuilder.buffer)
val vec = root.asVector()
blackhole.consume(Array(vec.size()) { vec[it].toString() })
}
@Benchmark
open fun stringMapKotlin(blackhole: Blackhole) {
val kBuilder = FlexBuffersBuilder(initialCapacity, FlexBuffersBuilder.SHARE_KEYS_AND_STRINGS)
val pos = kBuilder.startMap()
for (i in stringKey.indices) {
kBuilder[stringKey[i]] = stringValue[i]
}
kBuilder.endMap(pos)
val ref = getRoot(kBuilder.finish())
val map = ref.toMap()
val keys = map.keys
for (key in keys) {
blackhole.consume(map[key.toString()].toString())
}
}
@Benchmark
open fun stringMapBytIndexKotlin(blackhole: Blackhole) {
val kBuilder = FlexBuffersBuilder(initialCapacity, FlexBuffersBuilder.SHARE_KEYS_AND_STRINGS)
val pos = kBuilder.startMap()
for (i in stringKey.indices) {
kBuilder[stringKey[i]] = stringValue[i]
}
kBuilder.endMap(pos)
val ref = getRoot(kBuilder.finish())
val map = ref.toMap()
for (index in 0 until map.size) {
blackhole.consume(map[index].toString())
}
}
@Benchmark
open fun stringMapJava(blackhole: Blackhole) {
val jBuilder = com.google.flatbuffers.FlexBuffersBuilder(ArrayReadWriteBuf(initialCapacity), BUILDER_FLAG_SHARE_ALL)
val v = jBuilder.startMap()
for (i in stringKey.indices) {
jBuilder.putString(stringKey[i], stringValue[i])
}
jBuilder.endMap(null, v)
val ref = FlexBuffers.getRoot(jBuilder.finish())
val map = ref.asMap()
val keyVec = map.keys()
for (i in 0 until keyVec.size()) {
val s = keyVec[i].toString()
blackhole.consume(map[s].toString())
}
}
}
@@ -0,0 +1,122 @@
/*
* 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.
*/
package com.google.flatbuffers.kotlin.benchmark
import com.google.flatbuffers.kotlin.ArrayReadBuffer
import com.google.flatbuffers.kotlin.JSONParser
import com.google.flatbuffers.kotlin.Reference
import com.google.flatbuffers.kotlin.toJson
import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import kotlinx.benchmark.Blackhole
import okio.Buffer
import org.openjdk.jmh.annotations.Benchmark
import org.openjdk.jmh.annotations.BenchmarkMode
import org.openjdk.jmh.annotations.Measurement
import org.openjdk.jmh.annotations.Mode
import org.openjdk.jmh.annotations.OutputTimeUnit
import org.openjdk.jmh.annotations.Scope
import org.openjdk.jmh.annotations.State
import java.io.ByteArrayInputStream
import java.io.InputStreamReader
import java.util.concurrent.TimeUnit
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Measurement(iterations = 100, time = 1, timeUnit = TimeUnit.MICROSECONDS)
open class JsonBenchmark {
final val moshi = Moshi.Builder()
.addLast(KotlinJsonAdapterFactory())
.build()
final val moshiAdapter = moshi.adapter(Map::class.java)
final val gson = Gson()
final val gsonParser = JsonParser()
val fbParser = JSONParser()
final val classLoader = this.javaClass.classLoader
final val twitterData = classLoader.getResourceAsStream("twitter.json")!!.readBytes()
final val canadaData = classLoader.getResourceAsStream("canada.json")!!.readBytes()
final val citmData = classLoader.getResourceAsStream("citm_catalog.json")!!.readBytes()
val fbCitmRef = JSONParser().parse(ArrayReadBuffer(citmData))
val moshiCitmRef = moshi.adapter(Map::class.java).fromJson(citmData.decodeToString())
val gsonCitmRef = gsonParser.parse(citmData.decodeToString())
fun readFlexBuffers(data: ByteArray): Reference = fbParser.parse(ArrayReadBuffer(data))
fun readMoshi(data: ByteArray): Map<*, *>? {
val buffer = Buffer().write(data)
return moshiAdapter.fromJson(buffer)
}
fun readGson(data: ByteArray): JsonObject {
val parser = JsonParser()
val jsonReader = InputStreamReader(ByteArrayInputStream(data))
return parser.parse(jsonReader).asJsonObject
}
// TWITTER
@Benchmark
open fun readTwitterFlexBuffers(hole: Blackhole? = null) = hole?.consume(readFlexBuffers(twitterData))
@Benchmark
open fun readTwitterMoshi(hole: Blackhole?) = hole?.consume(readMoshi(twitterData))
@Benchmark
open fun readTwitterGson(hole: Blackhole?) = hole?.consume(readGson(twitterData))
@Benchmark
open fun roundTripTwitterFlexBuffers(hole: Blackhole? = null) = hole?.consume(readFlexBuffers(twitterData).toJson())
@Benchmark
open fun roundTripTwitterMoshi(hole: Blackhole?) = hole?.consume(moshiAdapter.toJson(readMoshi(twitterData)))
@Benchmark
open fun roundTripTwitterGson(hole: Blackhole?) = hole?.consume(gson.toJson(readGson(twitterData)))
// CITM
@Benchmark
open fun readCITMFlexBuffers(hole: Blackhole? = null) = hole?.consume(readFlexBuffers(citmData))
@Benchmark
open fun readCITMMoshi(hole: Blackhole?) = hole?.consume(moshiAdapter.toJson(readMoshi(citmData)))
@Benchmark
open fun readCITMGson(hole: Blackhole?) = hole?.consume(gson.toJson(readGson(citmData)))
@Benchmark
open fun roundTripCITMFlexBuffers(hole: Blackhole? = null) = hole?.consume(readFlexBuffers(citmData).toJson())
@Benchmark
open fun roundTripCITMMoshi(hole: Blackhole?) = hole?.consume(moshiAdapter.toJson(readMoshi(citmData)))
@Benchmark
open fun roundTripCITMGson(hole: Blackhole?) = hole?.consume(gson.toJson(readGson(citmData)))
@Benchmark
open fun writeCITMFlexBuffers(hole: Blackhole? = null) = hole?.consume(fbCitmRef.toJson())
@Benchmark
open fun writeCITMMoshi(hole: Blackhole?) = hole?.consume(moshiAdapter.toJson(moshiCitmRef))
@Benchmark
open fun writeCITMGson(hole: Blackhole?) = hole?.consume(gson.toJson(gsonCitmRef))
// CANADA
@Benchmark
open fun readCanadaFlexBuffers(hole: Blackhole? = null) = hole?.consume(readFlexBuffers(canadaData))
@Benchmark
open fun readCanadaMoshi(hole: Blackhole?) = hole?.consume(readMoshi(canadaData))
@Benchmark
open fun readCanadaGson(hole: Blackhole?) = hole?.consume(readGson(canadaData))
}
@@ -0,0 +1,235 @@
/*
* 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.
*/
package com.google.flatbuffers.kotlin.benchmark
import com.google.flatbuffers.kotlin.ArrayReadWriteBuffer
import com.google.flatbuffers.kotlin.Key
import com.google.flatbuffers.kotlin.Utf8
import kotlinx.benchmark.Blackhole
import org.openjdk.jmh.annotations.Benchmark
import org.openjdk.jmh.annotations.BenchmarkMode
import org.openjdk.jmh.annotations.Measurement
import org.openjdk.jmh.annotations.Mode
import org.openjdk.jmh.annotations.OutputTimeUnit
import org.openjdk.jmh.annotations.Scope
import org.openjdk.jmh.annotations.Setup
import org.openjdk.jmh.annotations.State
import java.nio.ByteBuffer
import java.util.concurrent.TimeUnit
import kotlin.random.Random
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Measurement(iterations = 100, time = 1, timeUnit = TimeUnit.MICROSECONDS)
open class UTF8Benchmark {
private val sampleSize = 5000
private val stringSize = 25
private var sampleSmallUtf8 = (0..sampleSize).map { populateUTF8(stringSize) }.toList()
private var sampleSmallUtf8Decoded = sampleSmallUtf8.map { it.encodeToByteArray() }.toList()
private var sampleSmallAscii = (0..sampleSize).map { populateAscii(stringSize) }.toList()
private var sampleSmallAsciiDecoded = sampleSmallAscii.map { it.encodeToByteArray() }.toList()
@Setup
fun setUp() {
}
@Benchmark
fun encodeUtf8KotlinStandard(blackhole: Blackhole) {
for (i in sampleSmallUtf8) {
blackhole.consume(i.encodeToByteArray())
}
}
@Benchmark
fun encodeUtf8KotlinFlatbuffers(blackhole: Blackhole) {
for (i in sampleSmallUtf8) {
val byteArray = ByteArray((i.length * 4))
blackhole.consume(Utf8.encodeUtf8Array(i, byteArray, 0, byteArray.size))
}
}
@Benchmark
fun encodeUtf8JavaFlatbuffers(blackhole: Blackhole) {
val javaUtf8 = com.google.flatbuffers.Utf8.getDefault()
for (i in sampleSmallUtf8) {
val byteBuffer = ByteBuffer.wrap(ByteArray(i.length * 4))
blackhole.consume(javaUtf8.encodeUtf8(i, byteBuffer))
}
}
@Benchmark
fun decodeUtf8KotlinStandard(blackhole: Blackhole) {
for (ary in sampleSmallUtf8Decoded) {
blackhole.consume(ary.decodeToString())
}
}
@Benchmark
fun decodeUtf8KotlinFlatbuffers(blackhole: Blackhole) {
for (ary in sampleSmallUtf8Decoded) {
blackhole.consume(Utf8.decodeUtf8Array(ary, 0, ary.size))
}
}
@Benchmark
fun decodeUtf8JavaFlatbuffers(blackhole: Blackhole) {
val javaUtf8 = com.google.flatbuffers.Utf8.getDefault()
for (ary in sampleSmallUtf8Decoded) {
val byteBuffer = ByteBuffer.wrap(ary)
blackhole.consume(javaUtf8.decodeUtf8(byteBuffer, 0, ary.size))
}
}
// ASCII TESTS
@Benchmark
fun encodeAsciiKotlinStandard(blackhole: Blackhole) {
for (i in sampleSmallAscii) {
blackhole.consume(i.encodeToByteArray())
}
}
@Benchmark
fun encodeAsciiKotlinFlatbuffers(blackhole: Blackhole) {
for (i in sampleSmallAscii) {
val byteArray = ByteArray(i.length) // Utf8.encodedLength(i))
blackhole.consume(Utf8.encodeUtf8Array(i, byteArray, 0, byteArray.size))
}
}
@Benchmark
fun encodeAsciiJavaFlatbuffers(blackhole: Blackhole) {
val javaUtf8 = com.google.flatbuffers.Utf8.getDefault()
for (i in sampleSmallAscii) {
val byteBuffer = ByteBuffer.wrap(ByteArray(i.length))
blackhole.consume(javaUtf8.encodeUtf8(i, byteBuffer))
}
}
@Benchmark
fun decodeAsciiKotlinStandard(blackhole: Blackhole) {
for (ary in sampleSmallAsciiDecoded) {
String(ary)
blackhole.consume(ary.decodeToString())
}
}
@Benchmark
fun decodeAsciiKotlinFlatbuffers(blackhole: Blackhole) {
for (ary in sampleSmallAsciiDecoded) {
blackhole.consume(Utf8.decodeUtf8Array(ary, 0, ary.size))
}
}
@Benchmark
fun decodeAsciiJavaFlatbuffers(blackhole: Blackhole) {
val javaUtf8 = com.google.flatbuffers.Utf8.getDefault()
for (ary in sampleSmallAsciiDecoded) {
val byteBuffer = ByteBuffer.wrap(ary)
blackhole.consume(javaUtf8.decodeUtf8(byteBuffer, 0, ary.size))
}
}
@Benchmark
fun readAllCharsString(blackhole: Blackhole) {
for (ary in sampleSmallAsciiDecoded) {
val key = Utf8.decodeUtf8Array(ary, 0, ary.size)
for (i in key.indices) {
blackhole.consume(key[i])
}
}
}
@Benchmark
fun readAllCharsCharSequence(blackhole: Blackhole) {
for (ary in sampleSmallAsciiDecoded) {
val key = Key(ArrayReadWriteBuffer(ary), 0, ary.size)
for (i in 0 until key.sizeInChars) {
blackhole.consume(key[i])
}
}
}
fun populateAscii(size: Int): String {
val data = ByteArray(size)
for (i in data.indices) {
data[i] = Random.nextInt(0, 127).toByte()
}
return String(data, 0, data.size)
}
// generate a string having at least length N
// can exceed by up to 3 chars, returns the actual length
fun populateUTF8(size: Int): String {
val data = ByteArray(size + 3)
var i = 0
while (i < size) {
val w = Random.nextInt() and 0xFF
when {
w < 0x80 -> data[i++] = 0x20; // w;
w < 0xE0 -> {
data[i++] = (0xC2 + Random.nextInt() % (0xDF - 0xC2 + 1)).toByte()
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
}
w == 0xE0 -> {
data[i++] = w.toByte()
data[i++] = (0xA0 + Random.nextInt() % (0xBF - 0xA0 + 1)).toByte()
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
}
w <= 0xEC -> {
data[i++] = w.toByte()
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
}
w == 0xED -> {
data[i++] = w.toByte()
data[i++] = (0x80 + Random.nextInt() % (0x9F - 0x80 + 1)).toByte()
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
}
w <= 0xEF -> {
data[i++] = w.toByte()
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
}
w < 0xF0 -> {
data[i++] = (0xF1 + Random.nextInt() % (0xF3 - 0xF1 + 1)).toByte()
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
}
w == 0xF0 -> {
data[i++] = w.toByte()
data[i++] = (0x90 + Random.nextInt() % (0xBF - 0x90 + 1)).toByte()
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
}
w <= 0xF3 -> {
data[i++] = (0xF1 + Random.nextInt() % (0xF3 - 0xF1 + 1)).toByte()
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
}
w == 0xF4 -> {
data[i++] = w.toByte()
data[i++] = (0x80 + Random.nextInt() % (0x8F - 0x80 + 1)).toByte()
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
}
}
}
return String(data, 0, i)
}
}
+44
View File
@@ -0,0 +1,44 @@
import org.gradle.internal.impldep.org.testng.ITestResult.STARTED
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.nio.charset.StandardCharsets
buildscript {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
dependencies {
classpath(libs.plugin.kotlin.gradle)
classpath(libs.plugin.kotlinx.benchmark)
classpath(libs.plugin.jmhreport)
classpath(libs.plugin.download)
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<KotlinCommonOptions>>().configureEach {
kotlinOptions {
freeCompilerArgs += "-progressive" // https://kotlinlang.org/docs/whatsnew13.html#progressive-mode
}
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile>().configureEach {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
freeCompilerArgs += "-Xjvm-default=all"
}
}
tasks.withType<JavaCompile> {
options.encoding = StandardCharsets.UTF_8.toString()
sourceCompatibility = JavaVersion.VERSION_1_8.toString()
targetCompatibility = JavaVersion.VERSION_1_8.toString()
}
@@ -0,0 +1,142 @@
import org.gradle.internal.impldep.org.fusesource.jansi.AnsiRenderer.test
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework
import org.jetbrains.kotlin.cli.common.toBooleanLenient
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFrameworkConfig
plugins {
kotlin("multiplatform")
}
val libName = "Flatbuffers"
group = "com.google.flatbuffers.kotlin"
version = "2.0.0-SNAPSHOT"
kotlin {
explicitApi()
jvm()
js(IR) {
browser {
testTask {
enabled = false
}
}
binaries.executable()
}
macosX64()
macosArm64()
iosArm64()
iosSimulatorArm64()
sourceSets {
val commonMain by getting {
dependencies {
implementation(kotlin("stdlib-common"))
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
kotlin.srcDir("src/commonTest/generated/kotlin/")
}
val jvmTest by getting {
dependencies {
implementation(kotlin("test-junit"))
implementation("com.google.flatbuffers:flatbuffers-java:2.0.3")
}
}
val jvmMain by getting {
}
val macosX64Main by getting
val macosArm64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
val nativeMain by creating {
// this sourceSet will hold common cold for all iOS targets
dependsOn(commonMain)
macosArm64Main.dependsOn(this)
macosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
}
all {
languageSettings.optIn("kotlin.ExperimentalUnsignedTypes")
}
}
}
// Fixes JS issue: https://youtrack.jetbrains.com/issue/KT-49109
rootProject.plugins.withType<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin> {
rootProject.the<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension>().nodeVersion = "16.0.0"
}
// Use the default greeting
tasks.register<GenerateFBTestClasses>("generateFBTestClassesKt") {
inputFiles.setFrom("$rootDir/../tests/monster_test.fbs",
"$rootDir/../tests/dictionary_lookup.fbs",
// @todo Seems like nesting code generation is broken for all generators.
// disabling test for now.
// "$rootDir/../tests/namespace_test/namespace_test1.fbs",
// "$rootDir/../tests/namespace_test/namespace_test2.fbs",
"$rootDir/../tests/union_vector/union_vector.fbs",
"$rootDir/../tests/optional_scalars.fbs")
includeFolder.set("$rootDir/../tests/include_test")
outputFolder.set("${projectDir}/src/commonTest/generated/kotlin/")
variant.set("kotlin-kmp")
}
project.tasks.forEach {
if (it.name.contains("compileKotlin"))
it.dependsOn("generateFBTestClassesKt")
}
fun String.intProperty() = findProperty(this).toString().toInt()
abstract class GenerateFBTestClasses : DefaultTask() {
@get:InputFiles
abstract val inputFiles: ConfigurableFileCollection
@get:Input
abstract val includeFolder: Property<String>
@get:Input
abstract val outputFolder: Property<String>
@get:Input
abstract val variant: Property<String>
@Inject
protected open fun getExecActionFactory(): org.gradle.process.internal.ExecActionFactory? {
throw UnsupportedOperationException()
}
init {
includeFolder.set("")
}
@TaskAction
fun compile() {
val execAction = getExecActionFactory()!!.newExecAction()
val sources = inputFiles.asPath.split(":")
val args = mutableListOf("flatc","-o", outputFolder.get(), "--${variant.get()}")
if (includeFolder.get().isNotEmpty()) {
args.add("-I")
args.add(includeFolder.get())
}
args.addAll(sources)
println(args)
execAction.commandLine = args
print(execAction.execute())
}
}
@@ -0,0 +1,616 @@
/*
* 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.
*/
package com.google.flatbuffers.kotlin
import kotlin.math.max
import kotlin.math.min
/**
* Represent a chunk of data, where FlexBuffers will be read from.
*/
public interface ReadBuffer {
/**
* Scan through the buffer for first byte matching value.
* @param value to be match
* @param start inclusive initial position to start searching
* @param end exclusive final position of the search
* @return position of a match or -1
*/
public fun findFirst(value: Byte, start: Int, end: Int = limit): Int
/**
* Read boolean from the buffer. Booleans as stored as a single byte
* @param index position of the element in [ReadBuffer]
* @return [Boolean] element
*/
public fun getBoolean(index: Int): Boolean
/**
* Read a [Byte] from the buffer.
* @param index position of the element in [ReadBuffer]
* @return a byte
*/
public operator fun get(index: Int): Byte
/**
* Read a [UByte] from the buffer.
* @param index position of the element in [ReadBuffer]
* @return a [UByte]
*/
public fun getUByte(index: Int): UByte
/**
* Read a [Short] from the buffer.
* @param index position of the element in [ReadBuffer]
* @return a [Short]
*/
public fun getShort(index: Int): Short
/**
* Read a [UShort] from the buffer.
* @param index position of the element in [ReadBuffer]
* @return a [UShort]
*/
public fun getUShort(index: Int): UShort
/**
* Read a [Int] from the buffer.
* @param index position of the element in [ReadBuffer]
* @return an [Int]
*/
public fun getInt(index: Int): Int
/**
* Read a [UInt] from the buffer.
* @param index position of the element in [ReadBuffer]
* @return an [UInt]
*/
public fun getUInt(index: Int): UInt
/**
* Read a [Long] from the buffer.
* @param index position of the element in [ReadBuffer]
* @return a [Long]
*/
public fun getLong(index: Int): Long
/**
* Read a [ULong] from the buffer.
* @param index position of the element in [ReadBuffer]
* @return a [ULong]
*/
public fun getULong(index: Int): ULong
/**
* Read a 32-bit float from the buffer.
* @param index position of the element in [ReadBuffer]
* @return a float
*/
public fun getFloat(index: Int): Float
/**
* Read a 64-bit float from the buffer.
* @param index position of the element in [ReadBuffer]
* @return a double
*/
public fun getDouble(index: Int): Double
/**
* Read a UTF-8 string from the buffer.
* @param start initial element of the string
* @param size size of the string in bytes.
* @return a `String`
*/
public fun getString(start: Int = 0, size: Int = limit): String
/**
* Read a ByteArray from the buffer.
* @param start position from the [ReadBuffer] to be read
* @param length maximum number of bytes to be written in the buffer
*/
public fun getBytes(array: ByteArray, start: Int, length: Int = array.size)
/**
* Expose [ReadBuffer] as an array of bytes.
* This method is meant to be as efficient as possible, so for an array-backed [ReadBuffer], it should
* return its own internal data. In case access to internal data is not possible,
* a copy of the data into an array of bytes might occur.
* @return [ReadBuffer] as an array of bytes
*/
public fun data(): ByteArray
/**
* Creates a new [ReadBuffer] point to a region of the current buffer, starting at [start] with size [size].
* @param start starting position of the [ReadBuffer]
* @param size in bytes of the [ReadBuffer]
* @return [ReadBuffer] slice.
*/
public fun slice(start: Int, size: Int): ReadBuffer
/**
* Defines the size of the message in the buffer. It also determines last position that buffer
* can be read. Last byte to be accessed is in position `limit() -1`.
* @return indicate last position
*/
public val limit: Int
}
/**
* Interface to represent a read-write buffers. This interface will be used to access and write FlexBuffer messages.
*/
public interface ReadWriteBuffer : ReadBuffer {
/**
* Clears (resets) the buffer so that it can be reused. Write position will be set to the start.
*/
public fun clear()
/**
* Request capacity of the buffer relative to [writePosition]. In case buffer is already larger
* than the requested, this method will just return true. Otherwise,
* It might try to resize the buffer. In case of being unable to allocate
* enough memory, an exception will be thrown.
* @param additional capacity in bytes to be added on top of [writePosition]
* @param copyAtEnd copy current data at the end of new underlying buffer
* @return new capacity in bytes
*/
public fun requestAdditionalCapacity(additional: Int, copyAtEnd: Boolean = false): Int =
requestCapacity(writePosition + additional, copyAtEnd)
/**
* Request capacity of the buffer in absolute values. In case buffer is already larger
* than the requested the method is a no-op. Otherwise,
* It might try to resize the buffer. In case of being unable to allocate
* enough memory, an exception will be thrown.
* @param capacity new capacity
* @param copyAtEnd copy current data at the end of new underlying buffer
* @return new capacity in bytes
*/
public fun requestCapacity(capacity: Int, copyAtEnd: Boolean = false): Int
/**
* Put a [Boolean] into the buffer at [writePosition] . Booleans as stored as single byte.
* Write position will be incremented.
* @return [Boolean] element
*/
public fun put(value: Boolean)
/**
* Put an array of bytes into the buffer at [writePosition]. Write position will be incremented.
* @param value the data to be copied
* @param start initial position on value to be copied
* @param length amount of bytes to be copied
*/
public fun put(value: ByteArray, start: Int = 0, length: Int = value.size)
/**
* Put an array of bytes into the buffer at [writePosition]. Write position will be incremented.
* @param value [ReadBuffer] the data to be copied
* @param start initial position on value to be copied
* @param length amount of bytes to be copied
*/
public fun put(value: ReadBuffer, start: Int = 0, length: Int = value.limit - start)
/**
* Write a [Byte] into the buffer at [writePosition]. Write position will be incremented.
*/
public fun put(value: Byte)
/**
* Write a [UByte] into the buffer at [writePosition]. Write position will be incremented.
*/
public fun put(value: UByte)
/**
* Write a [Short] into in the buffer at [writePosition]. Write position will be incremented.
*/
public fun put(value: Short)
/**
* Write a [UShort] into in the buffer at [writePosition]. Write position will be incremented.
*/
public fun put(value: UShort)
/**
* Write a [Int] in the buffer at [writePosition]. Write position will be incremented.
*/
public fun put(value: Int)
/**
* Write a [UInt] into in the buffer at [writePosition]. Write position will be incremented.
*/
public fun put(value: UInt)
/**
* Write a [Long] into in the buffer at [writePosition]. Write position will be
* incremented.
*/
public fun put(value: Long)
/**
* Write a [ULong] into in the buffer at [writePosition]. Write position will be
* incremented.
*/
public fun put(value: ULong)
/**
* Write a 32-bit [Float] into the buffer at [writePosition]. Write position will be
* incremented.
*/
public fun put(value: Float)
/**
* Write a 64-bit [Double] into the buffer at [writePosition]. Write position will be
* incremented.
*/
public fun put(value: Double)
/**
* Write a [String] encoded as UTF-8 into the buffer at [writePosition]. Write position will be incremented.
* @return size in bytes of the encoded string
*/
public fun put(value: CharSequence, encodedLength: Int = -1): Int
/**
* Write an array of bytes into the buffer.
* @param dstIndex initial position where [src] will be copied into.
* @param src the data to be copied.
* @param srcStart initial position on [src] that will be copied.
* @param srcLength amount of bytes to be copied
*/
public fun set(dstIndex: Int, src: ByteArray, srcStart: Int = 0, srcLength: Int = src.size)
/**
* Write an array of bytes into the buffer.
* @param dstIndex initial position where [src] will be copied into.
* @param src the data to be copied.
* @param srcStart initial position on [src] that will be copied.
* @param srcLength amount of bytes to be copied
*/
public operator fun set(dstIndex: Int, src: ReadBuffer, srcStart: Int = 0, srcLength: Int)
/**
* Write [Boolean] into a given position [index] on the buffer. Booleans as stored as single byte.
* @param index position of the element in buffer
*/
public operator fun set(index: Int, value: Boolean)
/**
* Write [Byte] into a given position [index] on the buffer.
* @param index position of the element in the buffer
*/
public operator fun set(index: Int, value: Byte)
/**
* Write [UByte] into a given position [index] on the buffer.
* @param index position of the element in the buffer
*/
public operator fun set(index: Int, value: UByte)
/**
Short
* @param index position of the element in [ReadBuffer]
*/
public fun set(index: Int, value: Short)
/**
* Write [UShort] into a given position [index] on the buffer.
* @param index position of the element in [ReadBuffer]
*/
public fun set(index: Int, value: UShort)
/**
* Write [Int] into a given position [index] on the buffer.
* @param index position of the element in [ReadBuffer]
*/
public fun set(index: Int, value: Int)
/**
* Write [UInt] into a given position [index] on the buffer.
* @param index position of the element in [ReadBuffer]
*/
public fun set(index: Int, value: UInt)
/**
* Write [Long] into a given position [index] on the buffer.
* @param index position of the element in [ReadBuffer]
*/
public fun set(index: Int, value: Long)
/**
* Write [ULong] into a given position [index] on the buffer.
* @param index position of the element in [ReadBuffer]
*/
public fun set(index: Int, value: ULong)
/**
* Write [Float] into a given position [index] on the buffer.
* @param index position of the element in [ReadBuffer]
*/
public fun set(index: Int, value: Float)
/**
* Write [Double] into a given position [index] on the buffer.
* @param index position of the element in [ReadBuffer]
*/
public fun set(index: Int, value: Double)
public fun fill(value: Byte, start: Int, end: Int)
/**
* Current position of the buffer to be written. It will be automatically updated on [put] operations.
*/
public var writePosition: Int
/**
* Creates a new [ReadWriteBuffer] point to a region of the current buffer, starting at [offset] with size [size].
* @param offset starting position of the [ReadWriteBuffer]
* @param size in bytes of the [ReadWriteBuffer]
* @return [ReadWriteBuffer] slice.
*/
public fun writeSlice(offset: Int, size: Int): ReadWriteBuffer
/**
* Special operation where we increase the backed buffer size to [capacity]
* and shift all already written data to the end of the buffer.
*
* This function is mostly used when creating a Flatbuffer message, as
* data is written from the end of the buffer towards index 0.
* @param capacity required in bytes
* @return new capacity in bytes
*/
public fun moveWrittenDataToEnd(capacity: Int): Int
/**
* Maximum size in bytes that the backed buffer supports.
*/
public val capacity: Int
/**
* Defines last relative position of the backed buffer that can be written.
* Any addition to the buffer that goes beyond will throw an exception
* instead of regrow the buffer (default behavior).
*/
public val writeLimit: Int
}
public open class ArrayReadBuffer(protected var buffer: ByteArray,
// offsets writePosition against backed buffer e.g. offset = 1, writePosition = 1
// will write first byte at position 2 of the backed buffer
internal val offset: Int = 0,
override val limit: Int = buffer.size - offset) : ReadBuffer {
override fun findFirst(value: Byte, start: Int, end: Int): Int {
val e = min(end, limit)
val s = max(0, this.offset + start)
for (i in s until e) if (buffer[i] == value) return i
return -1
}
override fun getBoolean(index: Int): Boolean = buffer[offset + index] != 0.toByte()
override operator fun get(index: Int): Byte = buffer[offset + index]
override fun getUByte(index: Int): UByte = buffer.getUByte(offset + index)
override fun getShort(index: Int): Short = buffer.getShort(offset + index)
override fun getUShort(index: Int): UShort = buffer.getUShort(offset + index)
override fun getInt(index: Int): Int = buffer.getInt(offset + index)
override fun getUInt(index: Int): UInt = buffer.getUInt(offset + index)
override fun getLong(index: Int): Long = buffer.getLong(offset + index)
override fun getULong(index: Int): ULong = buffer.getULong(offset + index)
override fun getFloat(index: Int): Float = buffer.getFloat(offset + index)
override fun getDouble(index: Int): Double = buffer.getDouble(offset + index)
override fun getString(start: Int, size: Int): String = buffer.decodeToString(this.offset + start,
this.offset + start + size)
override fun getBytes(array: ByteArray, start: Int, length: Int) {
val end = min(this.offset + start + length, buffer.size)
var j = 0
for (i in this.offset + start until end) {
array[j++] = buffer[i]
}
}
override fun data(): ByteArray = buffer
override fun slice(start: Int, size: Int): ReadBuffer = ArrayReadBuffer(buffer, this.offset + start, size)
}
/**
* Implements `[ReadWriteBuffer]` using [ByteArray] as backing buffer. Using array of bytes are
* usually faster than `ByteBuffer`.
*
* This class is not thread-safe, meaning that
* it must operate on a single thread. Operating from
* multiple thread leads into an undefined behavior
*
* All operations assume Little Endian byte order.
*/
public class ArrayReadWriteBuffer(
buffer: ByteArray,
offset: Int = 0,
// Defines last position of the backed buffer that can be written.
// Any addition to the buffer that goes beyond will throw an exception
// instead of regrow the buffer (default behavior).
public override val writeLimit: Int = -1,
override var writePosition: Int = offset
) : ArrayReadBuffer(buffer, offset, writePosition), ReadWriteBuffer {
public constructor(initialCapacity: Int = 10) : this(ByteArray(initialCapacity))
override val limit: Int get() = writePosition
override fun clear(): Unit = run { writePosition = 0 }
override fun put(value: Boolean) {
set(writePosition, value)
writePosition++
}
override fun put(value: ByteArray, start: Int, length: Int) {
set(writePosition, value, start, length)
writePosition += length
}
override fun put(value: ReadBuffer, start: Int, length: Int) {
set(writePosition, value, start, length)
writePosition += length
}
override fun put(value: Byte) {
set(writePosition, value)
writePosition++
}
override fun put(value: UByte) {
set(writePosition, value)
writePosition++
}
override fun put(value: Short) {
set(writePosition, value)
writePosition += 2
}
override fun put(value: UShort) {
set(writePosition, value)
writePosition += 2
}
override fun put(value: Int) {
set(writePosition, value)
writePosition += 4
}
override fun put(value: UInt) {
set(writePosition, value)
writePosition += 4
}
override fun put(value: Long) {
set(writePosition, value)
writePosition += 8
}
override fun put(value: ULong) {
set(writePosition, value)
writePosition += 8
}
override fun put(value: Float) {
set(writePosition, value)
writePosition += 4
}
override fun put(value: Double) {
set(writePosition, value)
writePosition += 8
}
override fun put(value: CharSequence, encodedLength: Int): Int {
val length = if (encodedLength != -1) encodedLength else Utf8.encodedLength(value)
writePosition = buffer.setCharSequence(writePosition, value)
return length
}
override fun set(index: Int, value: Boolean) {
buffer[index] = if (value) 1.toByte() else 0.toByte()
}
override fun set(dstIndex: Int, src: ByteArray, srcStart: Int, srcLength: Int) {
src.copyInto(buffer, dstIndex, srcStart, srcStart + srcLength)
}
override operator fun set(dstIndex: Int, src: ReadBuffer, srcStart: Int, srcLength: Int) {
when(src) {
is ArrayReadBuffer -> {
src.data().copyInto(buffer, dstIndex, src.offset + srcStart, src.offset + srcStart + srcLength)
}
else -> {
for (i in 0 until srcLength) {
buffer[dstIndex + i] = src[srcStart + i]
}
}
}
}
override operator fun set(index: Int, value: Byte) { buffer[index] = value }
override operator fun set(index: Int, value: UByte) { buffer.setUByte(index, value) }
override operator fun set(index: Int, value: Short) { buffer.setShort(index, value) }
override operator fun set(index: Int, value: UShort) { buffer.setUShort(index, value) }
override operator fun set(index: Int, value: Int) { buffer.setInt(index, value) }
override operator fun set(index: Int, value: UInt) { buffer.setUInt(index, value) }
override operator fun set(index: Int, value: Long) { buffer.setLong(index, value) }
override operator fun set(index: Int, value: ULong) { buffer.setULong(index, value) }
override operator fun set(index: Int, value: Float) { buffer.setFloat(index, value) }
override operator fun set(index: Int, value: Double) { buffer.setDouble(index, value) }
override fun fill(value: Byte, start: Int, end: Int) { buffer.fill(value, start, end) }
/**
* Request capacity of the buffer. In case buffer is already larger
* than the requested, it is a no-op. Otherwise,
* It might try to resize the buffer. In case of being unable to allocate
* enough memory, an exception will be thrown.
* @param capacity new capacity
* @param copyAtEnd copy current data at the end of new underlying buffer
*/
override fun requestCapacity(capacity: Int, copyAtEnd: Boolean): Int {
if (capacity < 0) error("Capacity may not be negative (likely a previous int overflow)")
if (buffer.size >= capacity) return buffer.size
if (writeLimit > 0 && writeLimit + offset >= buffer.size) error("Buffer in writeLimit mode. In writeLimit mode" +
" the buffer does not grow automatically and any write beyond writeLimit will throw exception. " +
"(writeLimit: $writeLimit, newCapacity: $capacity")
// implemented in the same growing fashion as ArrayList
val oldCapacity = buffer.size
if (oldCapacity == Int.MAX_VALUE - 8) { // Ensure we don't grow beyond what fits in an int.
error("FlatBuffers: cannot grow buffer beyond 2 gigabytes.")
}
//(old_buf_size & 0xC0000000) != 0 ? MAX_BUFFER_SIZE : old_buf_size << 1;
var newCapacity = 8
while (newCapacity < capacity) { // Note: this also catches newCapacity int overflow
newCapacity = if (newCapacity and -0x40000000 != 0) Int.MAX_VALUE - 8 else newCapacity shl 1
}
val newBuffer = ByteArray(newCapacity)
buffer.copyInto(newBuffer, if (copyAtEnd) newBuffer.size - buffer.size else 0)
buffer = newBuffer
return newCapacity
}
override fun writeSlice(offset: Int, size: Int): ReadWriteBuffer {
return ArrayReadWriteBuffer(this.buffer, offset=offset, writeLimit=size)
}
override fun moveWrittenDataToEnd(capacity: Int): Int = requestCapacity(capacity, true)
override val capacity: Int
get() = buffer.size
}
public val emptyBuffer: ReadWriteBuffer = ArrayReadWriteBuffer(ByteArray(1))
@@ -0,0 +1,124 @@
/*
* 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
import kotlin.experimental.and
internal fun ByteArray.getString(index: Int, size: Int): String = Utf8.decodeUtf8Array(this, index, size)
internal fun ByteArray.setCharSequence(index: Int, value: CharSequence): Int =
Utf8.encodeUtf8Array(value, this, index, this.size - index)
// List of functions that needs to be implemented on all platforms.
internal expect inline fun ByteArray.getUByte(index: Int): UByte
internal expect inline fun ByteArray.getShort(index: Int): Short
internal expect inline fun ByteArray.getUShort(index: Int): UShort
internal expect inline fun ByteArray.getInt(index: Int): Int
internal expect inline fun ByteArray.getUInt(index: Int): UInt
internal expect inline fun ByteArray.getLong(index: Int): Long
internal expect inline fun ByteArray.getULong(index: Int): ULong
internal expect inline fun ByteArray.getFloat(index: Int): Float
internal expect inline fun ByteArray.getDouble(index: Int): Double
internal expect inline fun ByteArray.setUByte(index: Int, value: UByte)
public expect inline fun ByteArray.setShort(index: Int, value: Short)
internal expect inline fun ByteArray.setUShort(index: Int, value: UShort)
internal expect inline fun ByteArray.setInt(index: Int, value: Int)
internal expect inline fun ByteArray.setUInt(index: Int, value: UInt)
internal expect inline fun ByteArray.setLong(index: Int, value: Long)
internal expect inline fun ByteArray.setULong(index: Int, value: ULong)
internal expect inline fun ByteArray.setFloat(index: Int, value: Float)
internal expect inline fun ByteArray.setDouble(index: Int, value: Double)
/**
* This implementation uses Little Endian order.
*/
public object ByteArrayOps {
public inline fun getUByte(ary: ByteArray, index: Int): UByte = ary[index].toUByte()
public inline fun getShort(ary: ByteArray, index: Int): Short {
return (ary[index + 1].toInt() shl 8 or (ary[index].toInt() and 0xff)).toShort()
}
public inline fun getUShort(ary: ByteArray, index: Int): UShort = getShort(ary, index).toUShort()
public inline fun getInt(ary: ByteArray, index: Int): Int {
return (
(ary[index + 3].toInt() shl 24) or
((ary[index + 2].toInt() and 0xff) shl 16) or
((ary[index + 1].toInt() and 0xff) shl 8) or
((ary[index].toInt() and 0xff))
)
}
public inline fun getUInt(ary: ByteArray, index: Int): UInt = getInt(ary, index).toUInt()
public inline fun getLong(ary: ByteArray, index: Int): Long {
var idx = index
return ary[idx++].toLong() and 0xff or
(ary[idx++].toLong() and 0xff shl 8) or
(ary[idx++].toLong() and 0xff shl 16) or
(ary[idx++].toLong() and 0xff shl 24) or
(ary[idx++].toLong() and 0xff shl 32) or
(ary[idx++].toLong() and 0xff shl 40) or
(ary[idx++].toLong() and 0xff shl 48) or
(ary[idx].toLong() shl 56)
}
public inline fun getULong(ary: ByteArray, index: Int): ULong = getLong(ary, index).toULong()
public inline fun setUByte(ary: ByteArray, index: Int, value: UByte) {
ary[index] = value.toByte()
}
public inline fun setShort(ary: ByteArray, index: Int, value: Short) {
var idx = index
ary[idx++] = (value and 0xff).toByte()
ary[idx] = (value.toInt() shr 8 and 0xff).toByte()
}
public inline fun setUShort(ary: ByteArray, index: Int, value: UShort): Unit = setShort(ary, index, value.toShort())
public inline fun setInt(ary: ByteArray, index: Int, value: Int) {
var idx = index
ary[idx++] = (value and 0xff).toByte()
ary[idx++] = (value shr 8 and 0xff).toByte()
ary[idx++] = (value shr 16 and 0xff).toByte()
ary[idx] = (value shr 24 and 0xff).toByte()
}
public inline fun setUInt(ary: ByteArray, index: Int, value: UInt): Unit = setInt(ary, index, value.toInt())
public inline fun setLong(ary: ByteArray, index: Int, value: Long) {
var i = value.toInt()
setInt(ary, index, i)
i = (value shr 32).toInt()
setInt(ary, index + 4, i)
}
public inline fun setULong(ary: ByteArray, index: Int, value: ULong): Unit = setLong(ary, index, value.toLong())
public inline fun setFloat(ary: ByteArray, index: Int, value: Float) {
setInt(ary, index, value.toRawBits())
}
public inline fun setDouble(ary: ByteArray, index: Int, value: Double) {
setLong(ary, index, value.toRawBits())
}
public inline fun getFloat(ary: ByteArray, index: Int): Float = Float.fromBits(getInt(ary, index))
public inline fun getDouble(ary: ByteArray, index: Int): Double = Double.fromBits(getLong(ary, index))
}
@@ -0,0 +1,367 @@
/*
* 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.
*/
package com.google.flatbuffers.kotlin
import kotlin.jvm.JvmInline
import kotlin.math.min
// For now a typealias to guarantee type safety.
public typealias UnionOffset = Offset<Any>
public typealias UnionOffsetArray = OffsetArray<Any>
public typealias StringOffsetArray = OffsetArray<String>
public inline fun UnionOffsetArray(size: Int, crossinline call: (Int) -> Offset<Any>): UnionOffsetArray =
UnionOffsetArray(IntArray(size) { call(it).value })
public inline fun StringOffsetArray(size: Int, crossinline call: (Int) -> Offset<String>): StringOffsetArray =
StringOffsetArray(IntArray(size) { call(it).value })
/**
* Represents a "pointer" to a pointer types (table, string, struct) within the buffer
*/
@JvmInline
public value class Offset<T>(public val value: Int) {
public fun toUnion(): UnionOffset = UnionOffset(value)
}
/**
* Represents an array of offsets. Used to avoid boxing
* offset types.
*/
@JvmInline
public value class OffsetArray<T>(public val value: IntArray) {
public inline val size: Int
get() = value.size
public inline operator fun get(index: Int): Offset<T> = Offset(value[index])
}
public inline fun <T> OffsetArray(size: Int, crossinline call: (Int) -> Offset<T>): OffsetArray<T> {
return OffsetArray(IntArray(size) { call(it).value })
}
/**
* Represents a "pointer" to a vector type with elements T
*/
@JvmInline
public value class VectorOffset<T>(public val value: Int)
public fun <T> Int.toOffset(): Offset<T> = Offset(this)
public operator fun <T> Offset<T>.minus(other: Int): Offset<T> = Offset(this.value - other)
public operator fun <T> Int.minus(other: Offset<T>): Int {
return this - other.value
}
/**
* All tables in the generated code derive from this class, and add their own accessors.
*/
public open class Table {
/** Used to hold the position of the `bb` buffer. */
public var bufferPos: Int = 0
/** The underlying ReadWriteBuffer to hold the data of the Table. */
public var bb: ReadWriteBuffer = emptyBuffer
/** Used to hold the vtable position. */
public var vtableStart: Int = 0
/** Used to hold the vtable size. */
public var vtableSize: Int = 0
protected inline fun <reified T> Int.invalid(default: T, crossinline valid: (Int) -> T) : T =
if (this != 0) valid(this) else default
protected inline fun <reified T> lookupField(i: Int, default: T, crossinline found: (Int) -> T) : T =
offset(i).invalid(default) { found(it) }
/**
* Look up a field in the vtable.
*
* @param vtableOffset An `int` offset to the vtable in the Table's ReadWriteBuffer.
* @return Returns an offset into the object, or `0` if the field is not present.
*/
public fun offset(vtableOffset: Int): Int =
if (vtableOffset < vtableSize) bb.getShort(vtableStart + vtableOffset).toInt() else 0
/**
* Retrieve a relative offset.
*
* @param offset An `int` index into the Table's ReadWriteBuffer containing the relative offset.
* @return Returns the relative offset stored at `offset`.
*/
public fun indirect(offset: Int): Int = 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_ReadWriteBuffer 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 ReadWriteBuffer.
* @return Returns a `String` from the data stored inside the FlatBuffer at `offset`.
*/
public fun string(offset: Int): String = string(offset, bb)
/**
* Get the length of a vector.
*
* @param offset An `int` index into the Table's ReadWriteBuffer.
* @return Returns the length of the vector whose offset is stored at `offset`.
*/
public fun vectorLength(offset: Int): Int {
var newOffset = offset
newOffset += bufferPos
newOffset += bb.getInt(newOffset)
return bb.getInt(newOffset)
}
/**
* Get the start data of a vector.
*
* @param offset An `int` index into the Table's ReadWriteBuffer.
* @return Returns the start of the vector data whose offset is stored at `offset`.
*/
public fun vector(offset: Int): Int {
var newOffset = offset
newOffset += bufferPos
return newOffset + bb.getInt(newOffset) + Int.SIZE_BYTES // data starts after the length
}
/**
* Initialize vector as a ReadWriteBuffer.
*
* This is more efficient than using duplicate, since it doesn't copy the data
* nor allocates a new [ReadBuffer], creating no garbage to be collected.
*
* @param buffer The [ReadBuffer] for the array
* @param vectorOffset The position of the vector in the byte buffer
* @param elemSize The size of each element in the array
* @return The [ReadBuffer] for the array
*/
public fun vectorAsBuffer(buffer: ReadWriteBuffer, vectorOffset: Int, elemSize: Int): ReadBuffer {
val o = offset(vectorOffset)
if (o == 0) return emptyBuffer
val vectorStart = vector(o)
return buffer.slice(vectorStart, vectorLength(o) * elemSize)
}
/**
* 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 ReadWriteBuffer.
* @return Returns the Table that points to the union at `offset`.
*/
public fun union(t: Table, offset: Int): Table = union(t, offset, bb)
/**
* Sort tables by the key.
*
* @param offsets An 'int' indexes of the tables into the bb.
* @param bb A `ReadWriteBuffer` to get the tables.
*/
public fun <T> sortTables(offsets: Array<Offset<T>>, bb: ReadWriteBuffer) {
val off = offsets.sortedWith { o1, o2 -> keysCompare(o1, o2, bb) }
for (i in offsets.indices) 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 buffer A `ReadWriteBuffer` to get the keys.
*/
public open fun keysCompare(o1: Offset<*>, o2: Offset<*>, buffer: ReadWriteBuffer): Int = 0
/**
* Re-init the internal state with an external buffer `ReadWriteBuffer` and an offset within.
*
* This method exists primarily to allow recycling Table instances without risking memory leaks
* due to `ReadWriteBuffer` references.
*/
public inline fun <reified T: Table> reset(i: Int, reuseBuffer: ReadWriteBuffer): T {
bb = reuseBuffer
if (bb != emptyBuffer) {
bufferPos = i
vtableStart = bufferPos - bb.getInt(bufferPos)
vtableSize = bb.getShort(vtableStart).toInt()
} else {
bufferPos = 0
vtableStart = 0
vtableSize = 0
}
return this as T
}
/**
* Resets the internal state with a null `ReadWriteBuffer` and a zero position.
*
* This method exists primarily to allow recycling Table instances without risking memory leaks
* due to `ReadWriteBuffer` references. The instance will be unusable until it is assigned
* again to a `ReadWriteBuffer`.
*/
public inline fun <reified T: Table> reset(): T = reset(0, emptyBuffer)
public companion object {
public fun offset(vtableOffset: Int, offset: Offset<*>, bb: ReadWriteBuffer): Int {
val vtable: Int = bb.capacity - offset.value
return bb.getShort(vtable + vtableOffset - bb.getInt(vtable)) + vtable
}
/**
* Retrieve a relative offset.
*
* @param offset An `int` index into a ReadWriteBuffer containing the relative offset.
* @param bb from which the relative offset will be retrieved.
* @return Returns the relative offset stored at `offset`.
*/
public fun indirect(offset: Int, bb: ReadWriteBuffer): Int {
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_ReadWriteBuffer 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 ReadWriteBuffer.
* @param bb Table ReadWriteBuffer used to read a string at given offset.
* @return Returns a `String` from the data stored inside the FlatBuffer at `offset`.
*/
public fun string(offset: Int, bb: ReadWriteBuffer): String {
var newOffset = offset
newOffset += bb.getInt(newOffset)
val length: Int = bb.getInt(newOffset)
return bb.getString(newOffset + Int.SIZE_BYTES, length)
}
/**
* 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 ReadWriteBuffer.
* @param bb Table ReadWriteBuffer used to initialize the object Table-derived type.
* @return Returns the Table that points to the union at `offset`.
*/
public fun union(t: Table, offset: Int, bb: ReadWriteBuffer): Table =
t.reset(indirect(offset, bb), bb)
/**
* Check if a [ReadWriteBuffer] contains a file identifier.
*
* @param bb A `ReadWriteBuffer` 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
*/
public fun hasIdentifier(bb: ReadWriteBuffer?, ident: String): Boolean {
val identifierLength = 4
if (ident.length != identifierLength)
throw AssertionError("FlatBuffers: file identifier must be length $identifierLength")
for (i in 0 until identifierLength) {
if (ident[i].code.toByte() != bb!![bb.limit + Int.SIZE_BYTES + i]) return false
}
return true
}
/**
* Compare two strings in the buffer.
*
* @param offsetA An 'int' index of the first string into the bb.
* @param offsetB An 'int' index of the second string into the bb.
* @param bb A `ReadWriteBuffer` to get the strings.
*/
public fun compareStrings(offsetA: Int, offsetB: Int, bb: ReadWriteBuffer): Int {
var offset1 = offsetA
var offset2 = offsetB
offset1 += bb.getInt(offset1)
offset2 += bb.getInt(offset2)
val len1: Int = bb.getInt(offset1)
val len2: Int = bb.getInt(offset2)
val startPos1: Int = offset1 + Int.SIZE_BYTES
val startPos2: Int = offset2 + Int.SIZE_BYTES
val len: Int = min(len1, len2)
for (i in 0 until len) {
if (bb[i + startPos1] != bb[i + startPos2]) {
return bb[i + startPos1] - bb[i + startPos2]
}
}
return len1 - len2
}
/**
* Compare string from the buffer with the 'String' object.
*
* @param offset An 'int' index of the first string into the bb.
* @param key Second string as a byte array.
* @param bb A `ReadWriteBuffer` to get the first string.
*/
public fun compareStrings(offset: Int, key: ByteArray, bb: ReadWriteBuffer): Int {
var offset1 = offset
offset1 += bb.getInt(offset1)
val len1: Int = bb.getInt(offset1)
val len2 = key.size
val startPos: Int = offset1 + Int.SIZE_BYTES
val len: Int = min(len1, len2)
for (i in 0 until len) {
if (bb[i + startPos] != key[i]) return bb[i + startPos] - key[i]
}
return len1 - len2
}
}
}
/**
* All structs in the generated code derive from this class, and add their own accessors.
*/
public open class Struct {
/** Used to hold the position of the `bb` buffer. */
protected var bufferPos: Int = 0
/** The underlying ByteBuffer to hold the data of the Struct. */
protected var bb: ReadWriteBuffer = emptyBuffer
/**
* Re-init the internal state with an external buffer `ByteBuffer` and an offset within.
*
* This method exists primarily to allow recycling Table instances without risking memory leaks
* due to `ByteBuffer` references.
*/
protected inline fun <reified T: Struct> reset(i: Int, reuseBuffer: ReadWriteBuffer): T {
bb = reuseBuffer
bufferPos = if (bb != emptyBuffer) i else 0
return this as T
}
/**
* Resets internal state with a null `ByteBuffer` and a zero position.
*
* This method exists primarily to allow recycling Struct instances without risking memory leaks
* due to `ByteBuffer` references. The instance will be unusable until it is assigned
* again to a `ByteBuffer`.
*/
private inline fun <reified T: Struct> reset(): T = reset(0, emptyBuffer)
}
public inline val <T> T.value: T get() = this
public const val VERSION_2_0_8: Int = 1
@@ -0,0 +1,915 @@
/*
* 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")
@file:JvmName("FlexBuffers")
package com.google.flatbuffers.kotlin
import kotlin.jvm.JvmName
/**
* Reads a FlexBuffer message in ReadBuf and returns [Reference] to
* the root element.
* @param buffer ReadBuf containing FlexBuffer message
* @return [Reference] to the root object
*/
public fun getRoot(buffer: ReadBuffer): Reference {
var end: Int = buffer.limit
val byteWidth = buffer[--end].toInt()
val packetType = buffer[--end].toInt()
end -= byteWidth // The root data item.
return Reference(buffer, end, ByteWidth(byteWidth), packetType)
}
/**
* Represents an generic element in the buffer. It can be specialized into scalar types, using for example,
* [Reference.toInt], or casted into Flexbuffer object types, like [Reference.toMap] or [Reference.toBlob].
*/
@Suppress("NOTHING_TO_INLINE")
public class Reference internal constructor(
internal val buffer: ReadBuffer,
internal val end: Int,
internal val parentWidth: ByteWidth,
internal val byteWidth: ByteWidth,
public val type: FlexBufferType
) {
internal constructor(bb: ReadBuffer, end: Int, parentWidth: ByteWidth, packedType: Int) :
this(bb, end, parentWidth, ByteWidth(1 shl (packedType and 3)), FlexBufferType((packedType shr 2)))
/**
* Checks whether the element is null type
* @return true if null type
*/
public val isNull: Boolean get() = type == T_NULL
/**
* Checks whether the element is boolean type
* @return true if boolean type
*/
public val isBoolean: Boolean get() = type == T_BOOL
/**
* Checks whether the element type is numeric (signed/unsigned integers and floats)
* @return true if numeric type
*/
public val isNumeric: Boolean get() = isIntOrUInt || isFloat
/**
* Checks whether the element type is signed or unsigned integers
* @return true if an integer type
*/
public val isIntOrUInt: Boolean get() = isInt || isUInt
/**
* Checks whether the element type is float
* @return true if a float type
*/
public val isFloat: Boolean get() = type == T_FLOAT || type == T_INDIRECT_FLOAT
/**
* Checks whether the element type is signed integer
* @return true if a signed integer type
*/
public val isInt: Boolean get() = type == T_INT || type == T_INDIRECT_INT
/**
* Checks whether the element type is signed integer
* @return true if a signed integer type
*/
public val isUInt: Boolean get() = type == T_UINT || type == T_INDIRECT_UINT
/**
* Checks whether the element type is string
* @return true if a string type
*/
public val isString: Boolean get() = type == T_STRING
/**
* Checks whether the element type is key
* @return true if a key type
*/
public val isKey: Boolean get() = type == T_KEY
/**
* Checks whether the element type is vector or a map. [TypedVector] are considered different types and will return
* false.
* @return true if a vector type
*/
public val isVector: Boolean get() = type == T_VECTOR || type == T_MAP
/**
* Checks whether the element type is typed vector
* @return true if a typed vector type
*/
public val isTypedVector: Boolean get() = type.isTypedVector()
/**
* Checks whether the element type is a map
* @return true if a map type
*/
public val isMap: Boolean get() = type == T_MAP
/**
* Checks whether the element type is a blob
* @return true if a blob type
*/
public val isBlob: Boolean get() = type == T_BLOB
/**
* Assumes [Reference] as a [Vector] and returns a [Reference] at index [index].
*/
public operator fun get(index: Int): Reference = toVector()[index]
/**
* Assumes [Reference] as a [Map] and returns a [Reference] for the value at key [key].
*/
public operator fun get(key: String): Reference = toMap()[key]
/**
* Returns element as a [Boolean].
* If element type is not boolean, it will be casted to integer and compared against 0
* @return element as [Boolean]
*/
public fun toBoolean(): Boolean = if (isBoolean) buffer.getBoolean(end) else toUInt() != 0u
/**
* Returns element as [Byte].
* For vector types, it will return size of the vector.
* For String type, it will be parsed as integer.
* Unsigned elements will become signed (with possible overflow).
* Float elements will be casted to [Byte].
* @return [Byte] or 0 if fail to convert element to integer.
*/
public fun toByte(): Byte = toULong().toByte()
/**
* Returns element as [Short].
* For vector types, it will return size of the vector.
* For String type, it will type to be parsed as integer.
* Unsigned elements will become signed (with possible overflow).
* Float elements will be casted to [Short]
* @return [Short] or 0 if fail to convert element to integer.
*/
public fun toShort(): Short = toULong().toShort()
/**
* Returns element as [Int].
* For vector types, it will return size of the vector.
* For String type, it will type to be parsed as integer.
* Unsigned elements will become signed (with possible overflow).
* Float elements will be casted to [Int]
* @return [Int] or 0 if fail to convert element to integer.
*/
public fun toInt(): Int = toULong().toInt()
/**
* Returns element as [Long].
* For vector types, it will return size of the vector
* For String type, it will type to be parsed as integer
* Unsigned elements will become negative
* Float elements will be casted to integer
* @return [Long] integer or 0 if fail to convert element to long.
*/
public fun toLong(): Long = toULong().toLong()
/**
* Returns element as [UByte].
* For vector types, it will return size of the vector.
* For String type, it will type to be parsed as integer.
* Negative elements will become unsigned counterpart.
* Float elements will be casted to [UByte]
* @return [UByte] or 0 if fail to convert element to integer.
*/
public fun toUByte(): UByte = toULong().toUByte()
/**
* Returns element as [UShort].
* For vector types, it will return size of the vector.
* For String type, it will type to be parsed as integer.
* Negative elements will become unsigned counterpart.
* Float elements will be casted to [UShort]
* @return [UShort] or 0 if fail to convert element to integer.
*/
public fun toUShort(): UShort = toULong().toUShort()
/**
* Returns element as [UInt].
* For vector types, it will return size of the vector.
* For String type, it will type to be parsed as integer.
* Negative elements will become unsigned counterpart.
* Float elements will be casted to [UInt]
* @return [UInt] or 0 if fail to convert element to integer.
*/
public fun toUInt(): UInt = toULong().toUInt()
/**
* Returns element as [ULong] integer.
* For vector types, it will return size of the vector
* For String type, it will type to be parsed as integer
* Negative elements will become unsigned counterpart.
* Float elements will be casted to integer
* @return [ULong] integer or 0 if fail to convert element to long.
*/
public fun toULong(): ULong = resolve { pos: Int, width: ByteWidth ->
when (type) {
T_INDIRECT_INT, T_INDIRECT_UINT, T_INT, T_BOOL, T_UINT -> buffer.readULong(pos, width)
T_FLOAT, T_INDIRECT_FLOAT -> buffer.readFloat(pos, width).toULong()
T_STRING -> toString().toULong()
T_VECTOR -> toVector().size.toULong()
else -> 0UL
}
}
/**
* Returns element as [Float].
* For vector types, it will return size of the vector
* For String type, it will type to be parsed as [Float]
* Float elements will be casted to integer
* @return [Float] integer or 0 if fail to convert element to long.
*/
public fun toFloat(): Float = resolve { pos: Int, width: ByteWidth ->
when (type) {
T_INDIRECT_FLOAT, T_FLOAT -> buffer.readFloat(pos, width).toFloat()
T_INT -> buffer.readInt(end, parentWidth).toFloat()
T_UINT, T_BOOL -> buffer.readUInt(end, parentWidth).toFloat()
T_INDIRECT_INT -> buffer.readInt(pos, width).toFloat()
T_INDIRECT_UINT -> buffer.readUInt(pos, width).toFloat()
T_NULL -> 0.0f
T_STRING -> toString().toFloat()
T_VECTOR -> toVector().size.toFloat()
else -> 0f
}
}
/**
* Returns element as [Double].
* For vector types, it will return size of the vector
* For String type, it will type to be parsed as [Double]
* @return [Float] integer or 0 if fail to convert element to long.
*/
public fun toDouble(): Double = resolve { pos: Int, width: ByteWidth ->
when (type) {
T_INDIRECT_FLOAT, T_FLOAT -> buffer.readFloat(pos, width)
T_INT -> buffer.readInt(pos, width).toDouble()
T_UINT, T_BOOL -> buffer.readUInt(pos, width).toDouble()
T_INDIRECT_INT -> buffer.readInt(pos, width).toDouble()
T_INDIRECT_UINT -> buffer.readUInt(pos, width).toDouble()
T_NULL -> 0.0
T_STRING -> toString().toDouble()
T_VECTOR -> toVector().size.toDouble()
else -> 0.0
}
}
/**
* Returns element as [Key] or invalid key.
*/
public fun toKey(): Key = when (type) {
T_KEY -> Key(buffer, buffer.indirect(end, parentWidth))
else -> nullKey()
}
/**
* Returns element as a [String]
* @return element as [String] or empty [String] if fail
*/
override fun toString(): String = when (type) {
T_STRING -> {
val start = buffer.indirect(end, parentWidth)
val size = buffer.readULong(start - byteWidth, byteWidth).toInt()
buffer.getString(start, size)
}
T_KEY -> buffer.getKeyString(buffer.indirect(end, parentWidth))
T_MAP -> "{ ${toMap().entries.joinToString(", ") { "${it.key}: ${it.value}"}} }"
T_VECTOR, T_VECTOR_BOOL, T_VECTOR_FLOAT, T_VECTOR_INT,
T_VECTOR_UINT, T_VECTOR_KEY, T_VECTOR_STRING_DEPRECATED ->
"[ ${toVector().joinToString(", ") { it.toString() }} ]"
T_INT -> toLong().toString()
T_UINT -> toULong().toString()
T_FLOAT -> toDouble().toString()
else -> "${type.typeToString()}(end=$end)"
}
/**
* Returns element as a [ByteArray], converting scalar types when possible.
* @return element as [ByteArray] or empty [ByteArray] if fail.
*/
public fun toByteArray(): ByteArray {
val vec = TypedVector(type.toElementTypedVector(), buffer, buffer.indirect(end, parentWidth), byteWidth)
return when (type) {
T_VECTOR_INT -> ByteArray(vec.size) { vec.getInt(it).toByte() }
T_VECTOR_UINT -> ByteArray(vec.size) { vec.getUInt(it).toByte() }
T_VECTOR -> ByteArray(vec.size) { vec[it].toByte() }
T_VECTOR_FLOAT -> ByteArray(vec.size) { vec.getFloat(it).toInt().toByte() }
else -> ByteArray(0)
}
}
/**
* Returns element as a [ByteArray], converting scalar types when possible.
* @return element as [ByteArray] or empty [ByteArray] if fail.
*/
public fun toShortArray(): ShortArray {
val vec = TypedVector(type.toElementTypedVector(), buffer, buffer.indirect(end, parentWidth), byteWidth)
return when (type) {
T_VECTOR_INT -> ShortArray(vec.size) { vec.getInt(it).toShort() }
T_VECTOR_UINT -> ShortArray(vec.size) { vec.getUInt(it).toShort() }
T_VECTOR -> ShortArray(vec.size) { vec[it].toShort() }
T_VECTOR_FLOAT -> ShortArray(vec.size) { vec.getFloat(it).toInt().toShort() }
else -> ShortArray(0)
}
}
/**
* Returns element as a [IntArray], converting scalar types when possible.
* @return element as [IntArray] or empty [IntArray] if fail.
*/
public fun toIntArray(): IntArray {
val vec = TypedVector(type.toElementTypedVector(), buffer, buffer.indirect(end, parentWidth), byteWidth)
return when (type) {
T_VECTOR_INT -> IntArray(vec.size) { vec.getInt(it).toInt() }
T_VECTOR_UINT -> IntArray(vec.size) { vec.getUInt(it).toInt() }
T_VECTOR -> IntArray(vec.size) { vec[it].toInt() }
T_VECTOR_FLOAT -> IntArray(vec.size) { vec.getFloat(it).toInt() }
else -> IntArray(0)
}
}
/**
* Returns element as a [LongArray], converting scalar types when possible.
* @return element as [LongArray] or empty [LongArray] if fail.
*/
public fun toLongArray(): LongArray {
val vec = TypedVector(type.toElementTypedVector(), buffer, buffer.indirect(end, parentWidth), byteWidth)
return when (type) {
T_VECTOR_INT -> LongArray(vec.size) { vec.getInt(it) }
T_VECTOR_UINT -> LongArray(vec.size) { vec.getInt(it) }
T_VECTOR -> LongArray(vec.size) { vec[it].toLong() }
T_VECTOR_FLOAT -> LongArray(vec.size) { vec.getFloat(it).toLong() }
else -> LongArray(0)
}
}
/**
* Returns element as a [UByteArray], converting scalar types when possible.
* @return element as [UByteArray] or empty [UByteArray] if fail.
*/
public fun toUByteArray(): UByteArray {
val vec = TypedVector(type.toElementTypedVector(), buffer, buffer.indirect(end, parentWidth), byteWidth)
return when (type) {
T_VECTOR_INT -> UByteArray(vec.size) { vec.getInt(it).toUByte() }
T_VECTOR_UINT -> UByteArray(vec.size) { vec.getUInt(it).toUByte() }
T_VECTOR -> UByteArray(vec.size) { vec[it].toUByte() }
T_VECTOR_FLOAT -> UByteArray(vec.size) { vec.getFloat(it).toInt().toUByte() }
else -> UByteArray(0)
}
}
/**
* Returns element as a [UIntArray], converting scalar types when possible.
* @return element as [UIntArray] or empty [UIntArray] if fail.
*/
public fun toUShortArray(): UShortArray {
val vec = TypedVector(type.toElementTypedVector(), buffer, buffer.indirect(end, parentWidth), byteWidth)
return when (type) {
T_VECTOR_INT -> UShortArray(vec.size) { vec.getInt(it).toUShort() }
T_VECTOR_UINT -> UShortArray(vec.size) { vec.getUInt(it).toUShort() }
T_VECTOR -> UShortArray(vec.size) { vec[it].toUShort() }
T_VECTOR_FLOAT -> UShortArray(vec.size) { vec.getFloat(it).toUInt().toUShort() }
else -> UShortArray(0)
}
}
/**
* Returns element as a [UIntArray], converting scalar types when possible.
* @return element as [UIntArray] or empty [UIntArray] if fail.
*/
public fun toUIntArray(): UIntArray {
val vec = TypedVector(type.toElementTypedVector(), buffer, buffer.indirect(end, parentWidth), byteWidth)
return when (type) {
T_VECTOR_INT -> UIntArray(vec.size) { vec.getInt(it).toUInt() }
T_VECTOR_UINT -> UIntArray(vec.size) { vec.getUInt(it).toUInt() }
T_VECTOR -> UIntArray(vec.size) { vec[it].toUInt() }
T_VECTOR_FLOAT -> UIntArray(vec.size) { vec.getFloat(it).toUInt() }
else -> UIntArray(0)
}
}
/**
* Returns element as a [ULongArray], converting scalar types when possible.
* @return element as [ULongArray] or empty [ULongArray] if fail.
*/
public fun toULongArray(): ULongArray {
val vec = TypedVector(type.toElementTypedVector(), buffer, buffer.indirect(end, parentWidth), byteWidth)
return when (type) {
T_VECTOR_INT -> ULongArray(vec.size) { vec.getUInt(it) }
T_VECTOR_UINT -> ULongArray(vec.size) { vec.getUInt(it) }
T_VECTOR -> ULongArray(vec.size) { vec[it].toULong() }
T_VECTOR_FLOAT -> ULongArray(vec.size) { vec.getFloat(it).toULong() }
else -> ULongArray(0)
}
}
/**
* Returns element as a [FloatArray], converting scalar types when possible.
* @return element as [FloatArray] or empty [FloatArray] if fail.
*/
public fun toFloatArray(): FloatArray {
val vec = TypedVector(type.toElementTypedVector(), buffer, buffer.indirect(end, parentWidth), byteWidth)
return when (type) {
T_VECTOR_FLOAT -> FloatArray(vec.size) { vec.getFloat(it).toFloat() }
T_VECTOR_INT -> FloatArray(vec.size) { vec.getInt(it).toFloat() }
T_VECTOR_UINT -> FloatArray(vec.size) { vec.getUInt(it).toFloat() }
T_VECTOR -> FloatArray(vec.size) { vec[it].toFloat() }
else -> FloatArray(0)
}
}
/**
* Returns element as a [DoubleArray], converting scalar types when possible.
* @return element as [DoubleArray] or empty [DoubleArray] if fail.
*/
public fun toDoubleArray(): DoubleArray {
val vec = TypedVector(type.toElementTypedVector(), buffer, buffer.indirect(end, parentWidth), byteWidth)
return when (type) {
T_VECTOR_FLOAT -> DoubleArray(vec.size) { vec[it].toDouble() }
T_VECTOR_INT -> DoubleArray(vec.size) { vec[it].toDouble() }
T_VECTOR_UINT -> DoubleArray(vec.size) { vec[it].toDouble() }
T_VECTOR -> DoubleArray(vec.size) { vec[it].toDouble() }
else -> DoubleArray(0)
}
}
/**
* Returns element as a [Vector]
* @return element as [Vector] or empty [Vector] if fail
*/
public fun toVector(): Vector {
return when {
isVector -> Vector(buffer, buffer.indirect(end, parentWidth), byteWidth)
isTypedVector -> TypedVector(type.toElementTypedVector(), buffer, buffer.indirect(end, parentWidth), byteWidth)
else -> emptyVector()
}
}
/**
* Returns element as a [Blob]
* @return element as [Blob] or empty [Blob] if fail
*/
public fun toBlob(): Blob {
return when (type) {
T_BLOB, T_STRING -> Blob(buffer, buffer.indirect(end, parentWidth), byteWidth)
else -> emptyBlob()
}
}
/**
* Returns element as a [Map].
* @return element as [Map] or empty [Map] if fail
*/
public fun toMap(): Map = when (type) {
T_MAP -> Map(buffer, buffer.indirect(end, parentWidth), byteWidth)
else -> emptyMap()
}
private inline fun <T> resolve(crossinline block: (pos: Int, width: ByteWidth) -> T): T {
return if (type.isIndirectScalar()) {
block(buffer.indirect(end, byteWidth), byteWidth)
} else {
block(end, parentWidth)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as Reference
if (buffer != other.buffer ||
end != other.end ||
parentWidth != other.parentWidth ||
byteWidth != other.byteWidth ||
type != other.type
) return false
return true
}
override fun hashCode(): Int {
var result = buffer.hashCode()
result = 31 * result + end
result = 31 * result + parentWidth.value
result = 31 * result + byteWidth.value
result = 31 * result + type.hashCode()
return result
}
}
/**
* Represents any element that has a size property to it, like: [Map], [Vector] and [TypedVector].
*/
public open class Sized internal constructor(
public val buffer: ReadBuffer,
public val end: Int,
public val byteWidth: ByteWidth
) {
public open val size: Int = buffer.readSize(end, byteWidth)
}
/**
* Represent an array of bytes in the buffer.
*/
public open class Blob internal constructor(
buffer: ReadBuffer,
end: Int,
byteWidth: ByteWidth
) : Sized(buffer, end, byteWidth) {
/**
* Return [Blob] as [ReadBuffer]
* @return blob as [ReadBuffer]
*/
public fun data(): ReadBuffer = buffer.slice(end, size)
/**
* Copy [Blob] into a [ByteArray]
* @return A [ByteArray] containing the blob data.
*/
public fun toByteArray(): ByteArray {
val result = ByteArray(size)
for (i in 0 until size) {
result[i] = buffer[end + i]
}
return result
}
/**
* Return individual byte at a given position
* @param pos position of the byte to be read
*/
public operator fun get(pos: Int): Byte {
if (pos !in 0..size) error("$pos index out of bounds. Should be in range 0..$size")
return buffer[end + pos]
}
override fun toString(): String = buffer.getString(end, size)
}
/**
* [Vector] represents an array of elements in the buffer. The element can be of any type.
*/
public open class Vector internal constructor(
buffer: ReadBuffer,
end: Int,
byteWidth: ByteWidth
) : Collection<Reference>,
Sized(buffer, end, byteWidth) {
/**
* Returns a [Reference] from the [Vector] at position [index]. Returns a null reference
* @param index position in the vector.
* @return [Reference] for a key or a null [Reference] if not found.
*/
public open operator fun get(index: Int): Reference {
if (index >= size) return nullReference()
val packedType = buffer[(end + size * byteWidth.value + index)].toInt()
val objEnd = end + index * byteWidth
return Reference(buffer, objEnd, byteWidth, packedType)
}
// overrides from Collection<Reference>
override fun contains(element: Reference): Boolean = find { it == element } != null
override fun containsAll(elements: Collection<Reference>): Boolean {
elements.forEach { if (!contains(it)) return false }
return true
}
override fun isEmpty(): Boolean = size == 0
override fun iterator(): Iterator<Reference> = object : Iterator<Reference> {
var position = 0
override fun hasNext(): Boolean = position != size
override fun next(): Reference = get(position++)
}
}
/**
* [TypedVector] represents an array of scalar elements of the same type in the buffer.
*/
public open class TypedVector(
private val elementType: FlexBufferType,
buffer: ReadBuffer,
end: Int,
byteWidth: ByteWidth
) : Vector(buffer, end, byteWidth) {
/**
* Returns a [Reference] from the [TypedVector] at position [index]. Returns a null reference
* @param index position in the vector.
* @return [Reference] for a key or a null [Reference] if not found.
*/
override operator fun get(index: Int): Reference {
if (index >= size) return nullReference()
val childPos: Int = end + index * byteWidth
return Reference(buffer, childPos, byteWidth, ByteWidth(1), elementType)
}
private inline fun <T> resolveAt(index: Int, crossinline block: (Int, ByteWidth) -> T): T {
val childPos: Int = end + index * byteWidth
return block(childPos, byteWidth)
}
internal fun getBoolean(index: Int): Boolean = resolveAt(index) {
pos: Int, _: ByteWidth -> buffer.getBoolean(pos)
}
internal fun getInt(index: Int): Long = resolveAt(index) {
pos: Int, width: ByteWidth -> buffer.readLong(pos, width)
}
internal fun getUInt(index: Int): ULong = resolveAt(index) {
pos: Int, width: ByteWidth -> buffer.readULong(pos, width)
}
internal fun getFloat(index: Int): Double = resolveAt(index) {
pos: Int, width: ByteWidth -> buffer.readFloat(pos, width)
}
}
/**
* Represents a key element in the buffer. Keys are
* used to reference objects in a [Map]
*/
public data class Key(
public val buffer: ReadBuffer,
public val start: Int,
public val end: Int = buffer.findFirst(ZeroByte, start)
) {
val sizeInBytes: Int = end - start
private val codePoint = CharArray(2)
val sizeInChars: Int
get() {
var count = 0
var i = start
while (i < end) {
val size = codePointSizeInBytes(i)
i += size
count += if (size == 4) 2 else 1
}
return count
}
public operator fun get(index: Int): Char {
var count = 0
var i = start
var size = 0
// we loop over the bytes to find the right position for the "char" at index i
while (i < end && count < index) {
size = codePointSizeInBytes(i)
i += size
// 4 bytes utf8 are 2 chars wide, the rest is on char.
count += if (size == 4) 2 else 1
}
return when {
count == index -> {
Utf8.decodeUtf8CodePoint(buffer, i, codePoint)
codePoint[0]
}
count == index + 1 && size == 4 -> {
Utf8.decodeUtf8CodePoint(buffer, i - size, codePoint)
codePoint[1]
}
else -> error("Invalid count=$count, index=$index")
}
}
private inline fun codePointSizeInBytes(pos: Int): Int {
val b = buffer[pos]
return when {
Utf8.isOneByte(b) -> 1
Utf8.isTwoBytes(b) -> 2
Utf8.isThreeBytes(b) -> 3
else -> 4
}
}
override fun toString(): String = if (sizeInBytes > 0) buffer.getString(start, sizeInBytes) else ""
/**
* Checks whether Key is invalid or not.
*/
public fun isInvalid(): Boolean = sizeInBytes <= 0
}
/**
* A Map class that provide support to access Key-Value data from Flexbuffers.
*/
public class Map
internal constructor(buffer: ReadBuffer, end: Int, byteWidth: ByteWidth):
Sized(buffer, end, byteWidth),
kotlin.collections.Map<Key, Reference> {
// used for accessing the key vector elements
private var keyVectorEnd: Int
private var keyVectorByteWidth: ByteWidth
init {
val keysOffset = end - (3 * byteWidth) // 3 is number of prefixed fields
keyVectorEnd = buffer.indirect(keysOffset, byteWidth)
keyVectorByteWidth = ByteWidth(buffer.readInt(keysOffset + byteWidth, byteWidth))
}
/**
* Returns a [Reference] from the [Map] at position [index]. Returns a null reference
* @param index position in the map
* @return [Reference] for a key or a null [Reference] if not found.
*/
public operator fun get(index: Int): Reference {
if (index >= size) return nullReference()
val packedPos = end + size * byteWidth + index
val packedType = buffer[packedPos].toInt()
val objEnd = end + index * byteWidth
return Reference(buffer, objEnd, byteWidth, packedType)
}
/**
* Returns a [Reference] from the [Map] for a given [String] [key].
* @param key access key to element on map
* @return [Reference] for a key or a null [Reference] if not found.
*/
public operator fun get(key: String): Reference {
val index: Int = binarySearch(key)
return if (index in 0 until size) {
get(index)
} else nullReference()
}
/**
* Returns a [Reference] from the [Map] for a given [Key] [key].
* @param key access key to element on map
* @return [Reference] for a key or a null [Reference] if not found.
*/
override operator fun get(key: Key): Reference {
val index = binarySearch(key)
return if (index in 0 until size) {
get(index)
} else nullReference()
}
/**
* Checks whether the map contains a [key].
* @param key [String]
* @return true if key is found in the map, otherwise false.
*/
public operator fun contains(key: String): Boolean = binarySearch(key) >= 0
/**
* Returns a [Key] for a given position [index] in the [Map].
* @param index of the key in the map
* @return a Key for the given index. Out of bounds indexes returns invalid keys.
*/
public fun keyAt(index: Int): Key {
val childPos: Int = keyVectorEnd + index * keyVectorByteWidth
return Key(buffer, buffer.indirect(childPos, keyVectorByteWidth))
}
/**
* Returns a [Key] as [String] for a given position [index] in the [Map].
* @param index of the key in the map
* @return a Key for the given index. Out of bounds indexes returns empty string.
*/
public fun keyAsString(index: Int): String {
val childPos: Int = keyVectorEnd + index * keyVectorByteWidth
val start = buffer.indirect(childPos, keyVectorByteWidth)
val end = buffer.findFirst(ZeroByte, start)
return if (end > start) buffer.getString(start, end - start) else ""
}
// Overrides from kotlin.collections.Map<Key, Reference>
public data class Entry(override val key: Key, override val value: Reference) :
kotlin.collections.Map.Entry<Key, Reference>
override val entries: Set<kotlin.collections.Map.Entry<Key, Reference>>
get() = keys.map { Entry(it, get(it.toString())) }.toSet()
override val keys: Set<Key>
get() {
val set = LinkedHashSet<Key>(size)
for (i in 0 until size) {
val key = keyAt(i)
set.add(key)
}
return set
}
/**
* Returns a [Vector] for accessing all values in the [Map].
* @return [Vector] of values.
*/
override val values: Collection<Reference>
get() = Vector(buffer, end, byteWidth)
override fun containsKey(key: Key): Boolean {
for (i in 0 until size) {
if (key == keyAt(i))
return true
}
return false
}
override fun containsValue(value: Reference): Boolean = values.contains(value)
override fun isEmpty(): Boolean = size == 0
// Performs a binary search on a key vector and return index of the key in key vector
private fun binarySearch(searchedKey: String) = binarySearch { compareCharSequence(it, searchedKey) }
// Performs a binary search on a key vector and return index of the key in key vector
private fun binarySearch(key: Key): Int = binarySearch { compareKeys(it, key.start) }
private inline fun binarySearch(crossinline comparisonBlock: (Int) -> Int): Int {
var low = 0
var high = size - 1
while (low <= high) {
val mid = low + high ushr 1
val keyPos: Int = buffer.indirect(keyVectorEnd + mid * keyVectorByteWidth, keyVectorByteWidth)
val cmp: Int = comparisonBlock(keyPos)
if (cmp < 0) low = mid + 1 else if (cmp > 0) high = mid - 1 else return mid // key found
}
return -(low + 1) // key not found
}
// compares a CharSequence against a T_KEY
private fun compareKeys(start: Int, other: Int): Int {
var bufferPos = start
var otherPos = other
val limit: Int = buffer.limit
var c1: Byte = ZeroByte
var c2: Byte = ZeroByte
while (otherPos < limit) {
c1 = buffer[bufferPos++]
c2 = buffer[otherPos++]
when {
c1 == ZeroByte -> return c1 - c2
c1 != c2 -> return c1 - c2
}
}
return c1 - c2
}
// compares a CharSequence against a [CharSequence]
private fun compareCharSequence(start: Int, other: CharSequence): Int {
var bufferPos = start
var otherPos = 0
val limit: Int = buffer.limit
val otherLimit = other.length
// special loop for ASCII characters. Most of keys should be ASCII only, so this
// loop should be optimized for that.
// breaks if a multi-byte character is found
while (otherPos < otherLimit) {
val c2 = other[otherPos]
// not a single byte codepoint
if (c2.code >= 0x80) {
break
}
val b: Byte = buffer[bufferPos]
when {
b == ZeroByte -> return -c2.code
b < 0 -> break
b != c2.code.toByte() -> return b - c2.code.toByte()
}
++bufferPos
++otherPos
}
if (bufferPos < limit)
return 0
val comparisonBuffer = ByteArray(4)
while (bufferPos < limit) {
val sizeInBuff = Utf8.encodeUtf8CodePoint(other, otherPos, comparisonBuffer)
if (sizeInBuff == 0) {
return buffer[bufferPos].toInt()
}
for (i in 0 until sizeInBuff) {
val bufferByte: Byte = buffer[bufferPos++]
val otherByte: Byte = comparisonBuffer[i]
when {
bufferByte == ZeroByte -> return -otherByte
bufferByte != otherByte -> return bufferByte - otherByte
}
}
otherPos += if (sizeInBuff == 4) 2 else 1
}
return 0
}
}
@@ -0,0 +1,786 @@
/*
* 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
}
}
@@ -0,0 +1,257 @@
/*
* 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
import kotlin.jvm.JvmInline
@JvmInline
public value class BitWidth(public val value: Int) {
public inline fun max(other: BitWidth): BitWidth = if (this.value >= other.value) this else other
}
@JvmInline
public value class ByteWidth(public val value: Int)
@JvmInline
public value class FlexBufferType(public val value: Int) {
public operator fun minus(other: FlexBufferType): FlexBufferType = FlexBufferType(this.value - other.value)
public operator fun plus(other: FlexBufferType): FlexBufferType = FlexBufferType(this.value + other.value)
public operator fun compareTo(other: FlexBufferType): Int = this.value - other.value
}
internal operator fun Int.times(width: ByteWidth): Int = this * width.value
internal operator fun Int.minus(width: ByteWidth): Int = this - width.value
internal operator fun Int.plus(width: ByteWidth): Int = this + width.value
internal operator fun Int.minus(type: FlexBufferType): Int = this - type.value
// Returns a Key string from the buffer starting at index [start]. Key Strings are stored as
// C-Strings, ending with '\0'. If zero byte not found returns empty string.
internal inline fun ReadBuffer.getKeyString(start: Int): String {
val i = findFirst(0.toByte(), start)
return if (i >= 0) getString(start, i - start) else ""
}
// read unsigned int with size byteWidth and return as a 64-bit integer
internal inline fun ReadBuffer.readULong(end: Int, byteWidth: ByteWidth): ULong {
return when (byteWidth.value) {
1 -> this.getUByte(end).toULong()
2 -> this.getUShort(end).toULong()
4 -> this.getUInt(end).toULong()
8 -> this.getULong(end)
else -> error("invalid byte width $byteWidth for scalar unsigned integer")
}
}
internal inline fun ReadBuffer.readFloat(end: Int, byteWidth: ByteWidth): Double {
return when (byteWidth.value) {
4 -> this.getFloat(end).toDouble()
8 -> this.getDouble(end)
else -> error("invalid byte width $byteWidth for floating point scalar") // we should never reach here
}
}
// return position on the [ReadBuffer] of the element that the offset is pointing to
// we assume all offset fits on a int, since ReadBuffer operates with that assumption
internal inline fun ReadBuffer.indirect(offset: Int, byteWidth: ByteWidth): Int = offset - readInt(offset, byteWidth)
// returns the size of an array-like element from [ReadBuffer].
internal inline fun ReadBuffer.readSize(end: Int, byteWidth: ByteWidth) = readInt(end - byteWidth, byteWidth)
internal inline fun ReadBuffer.readUInt(end: Int, byteWidth: ByteWidth): UInt = readULong(end, byteWidth).toUInt()
internal inline fun ReadBuffer.readInt(end: Int, byteWidth: ByteWidth): Int = readULong(end, byteWidth).toInt()
internal inline fun ReadBuffer.readLong(end: Int, byteWidth: ByteWidth): Long = readULong(end, byteWidth).toLong()
internal fun IntArray.widthInUBits(): BitWidth = arrayWidthInUBits(this.size) { this[it].toULong().widthInUBits() }
internal fun ShortArray.widthInUBits(): BitWidth = arrayWidthInUBits(this.size) { this[it].toULong().widthInUBits() }
internal fun LongArray.widthInUBits(): BitWidth = arrayWidthInUBits(this.size) { this[it].toULong().widthInUBits() }
private inline fun arrayWidthInUBits(size: Int, crossinline elemWidthBlock: (Int) -> BitWidth): BitWidth {
// Figure out smallest bit width we can store this vector with.
var bitWidth = W_8.max(size.toULong().widthInUBits())
// Check bit widths and types for all elements.
for (i in 0 until size) {
// since we know its inline types we can just assume elmentWidth to be the value width in bits.
bitWidth = bitWidth.max(elemWidthBlock(i))
}
return bitWidth
}
internal fun ULong.widthInUBits(): BitWidth = when {
this <= MAX_UBYTE_ULONG -> W_8
this <= UShort.MAX_VALUE -> W_16
this <= UInt.MAX_VALUE -> W_32
else -> W_64
}
// returns the number of bytes needed for padding the scalar of size scalarSize.
internal inline fun paddingBytes(bufSize: Int, scalarSize: Int): Int = bufSize.inv() + 1 and scalarSize - 1
internal inline fun FlexBufferType.isInline(): Boolean = this.value <= T_FLOAT.value || this == T_BOOL
internal fun FlexBufferType.isScalar(): Boolean = when (this) {
T_INT, T_UINT, T_FLOAT, T_BOOL -> true
else -> false
}
internal fun FlexBufferType.isIndirectScalar(): Boolean = when (this) {
T_INDIRECT_INT, T_INDIRECT_UINT, T_INDIRECT_FLOAT -> true
else -> false
}
internal fun FlexBufferType.isTypedVector(): Boolean =
this >= T_VECTOR_INT && this <= T_VECTOR_STRING_DEPRECATED || this == T_VECTOR_BOOL
internal fun FlexBufferType.isTypedVectorElementType(): Boolean =
(this.value in T_INT.value..T_KEY.value) || this == T_BOOL
// returns the typed vector of a given scalar type.
internal fun FlexBufferType.toTypedVector(): FlexBufferType = (this - T_INT) + T_VECTOR_INT
// returns the element type of given typed vector.
internal fun FlexBufferType.toElementTypedVector(): FlexBufferType = this - T_VECTOR_INT + T_INT
// Holds information about the elements inserted on the buffer.
internal data class Value(
var type: FlexBufferType = T_INT,
var key: Int = -1,
var minBitWidth: BitWidth = W_8,
var iValue: ULong = 0UL, // integer value
var dValue: Double = 0.0 // TODO(paulovap): maybe we can keep floating type on iValue as well.
) { // float value
inline fun storedPackedType(parentBitWidth: BitWidth = W_8): Byte = packedType(storedWidth(parentBitWidth), type)
private inline fun packedType(bitWidth: BitWidth, type: FlexBufferType): Byte =
(bitWidth.value or (type.value shl 2)).toByte()
private inline fun storedWidth(parentBitWidth: BitWidth): BitWidth =
if (type.isInline()) minBitWidth.max(parentBitWidth) else minBitWidth
fun elemWidth(bufSize: Int, elemIndex: Int): BitWidth =
elemWidth(type, minBitWidth, iValue.toLong(), bufSize, elemIndex)
}
internal fun elemWidth(
type: FlexBufferType,
minBitWidth: BitWidth,
iValue: Long,
bufSize: Int,
elemIndex: Int
): BitWidth {
if (type.isInline()) return minBitWidth
// We have an absolute offset, but want to store a relative offset
// elem_index elements beyond the current buffer end. Since whether
// the relative offset fits in a certain byte_width depends on
// the size of the elements before it (and their alignment), we have
// to test for each size in turn.
// Original implementation checks for largest scalar
// which is long unsigned int
var byteWidth = 1
while (byteWidth <= 32) {
// Where are we going to write this offset?
val offsetLoc: Int = bufSize + paddingBytes(bufSize, byteWidth) + elemIndex * byteWidth
// Compute relative offset.
val offset: Int = offsetLoc - iValue.toInt()
// Does it fit?
val bitWidth = offset.toULong().widthInUBits()
if (1 shl bitWidth.value == byteWidth) return bitWidth
byteWidth *= 2
}
return W_64
}
// For debugging purposes, convert type to a human-readable string.
internal fun FlexBufferType.typeToString(): String = when (this) {
T_NULL -> "Null"
T_INT -> "Int"
T_UINT -> "UInt"
T_FLOAT -> "Float"
T_KEY -> "Key"
T_STRING -> "String"
T_INDIRECT_INT -> "IndirectInt"
T_INDIRECT_UINT -> "IndirectUInt"
T_INDIRECT_FLOAT -> "IndirectFloat"
T_MAP -> "Map"
T_VECTOR -> "Vector"
T_VECTOR_INT -> "IntVector"
T_VECTOR_UINT -> "UIntVector"
T_VECTOR_FLOAT -> "FloatVector"
T_VECTOR_KEY -> "KeyVector"
T_VECTOR_STRING_DEPRECATED -> "StringVectorDeprecated"
T_VECTOR_INT2 -> "Int2Vector"
T_VECTOR_UINT2 -> "UInt2Vector"
T_VECTOR_FLOAT2 -> "Float2Vector"
T_VECTOR_INT3 -> "Int3Vector"
T_VECTOR_UINT3 -> "UInt3Vector"
T_VECTOR_FLOAT3 -> "Float3Vector"
T_VECTOR_INT4 -> "Int4Vector"
T_VECTOR_UINT4 -> "UInt4Vector"
T_VECTOR_FLOAT4 -> "Float4Vector"
T_BLOB -> "BlobVector"
T_BOOL -> "BoolVector"
T_VECTOR_BOOL -> "BoolVector"
else -> "UnknownType"
}
// Few repeated values used in hot path is cached here
internal fun emptyBlob() = Blob(emptyBuffer, 1, ByteWidth(1))
internal fun emptyVector() = Vector(emptyBuffer, 1, ByteWidth(1))
internal fun emptyMap() = Map(ArrayReadWriteBuffer(3), 3, ByteWidth(1))
internal fun nullReference() = Reference(emptyBuffer, 1, ByteWidth(0), T_NULL.value)
internal fun nullKey() = Key(emptyBuffer, 1)
internal const val ZeroByte = 0.toByte()
internal const val MAX_UBYTE_ULONG = 255UL
internal const val MAX_UBYTE = 255
internal const val MAX_USHORT = 65535
// value bit width possible sizes
internal val W_8 = BitWidth(0)
internal val W_16 = BitWidth(1)
internal val W_32 = BitWidth(2)
internal val W_64 = BitWidth(3)
// These are used as the upper 6 bits of a type field to indicate the actual type.
internal val T_INVALID = FlexBufferType(-1)
internal val T_NULL = FlexBufferType(0)
internal val T_INT = FlexBufferType(1)
internal val T_UINT = FlexBufferType(2)
internal val T_FLOAT = FlexBufferType(3) // Types above stored inline, types below are stored in an offset.
internal val T_KEY = FlexBufferType(4)
internal val T_STRING = FlexBufferType(5)
internal val T_INDIRECT_INT = FlexBufferType(6)
internal val T_INDIRECT_UINT = FlexBufferType(7)
internal val T_INDIRECT_FLOAT = FlexBufferType(8)
internal val T_MAP = FlexBufferType(9)
internal val T_VECTOR = FlexBufferType(10) // Untyped.
internal val T_VECTOR_INT = FlexBufferType(11) // Typed any size = stores no type table).
internal val T_VECTOR_UINT = FlexBufferType(12)
internal val T_VECTOR_FLOAT = FlexBufferType(13)
internal val T_VECTOR_KEY = FlexBufferType(14)
// DEPRECATED, use FBT_VECTOR or FBT_VECTOR_KEY instead.
// more info on https://github.com/google/flatbuffers/issues/5627.
internal val T_VECTOR_STRING_DEPRECATED = FlexBufferType(15)
internal val T_VECTOR_INT2 = FlexBufferType(16) // Typed tuple = no type table; no size field).
internal val T_VECTOR_UINT2 = FlexBufferType(17)
internal val T_VECTOR_FLOAT2 = FlexBufferType(18)
internal val T_VECTOR_INT3 = FlexBufferType(19) // Typed triple = no type table; no size field).
internal val T_VECTOR_UINT3 = FlexBufferType(20)
internal val T_VECTOR_FLOAT3 = FlexBufferType(21)
internal val T_VECTOR_INT4 = FlexBufferType(22) // Typed quad = no type table; no size field).
internal val T_VECTOR_UINT4 = FlexBufferType(23)
internal val T_VECTOR_FLOAT4 = FlexBufferType(24)
internal val T_BLOB = FlexBufferType(25)
internal val T_BOOL = FlexBufferType(26)
internal val T_VECTOR_BOOL = FlexBufferType(36) // To Allow the same type of conversion of type to vector type
@@ -0,0 +1,420 @@
/*
* 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
public object Utf8 {
/**
* Returns the number of bytes in the UTF-8-encoded form of `sequence`. For a string,
* this method is equivalent to `string.getBytes(UTF_8).length`, but is more efficient in
* both time and space.
*
* @throws IllegalArgumentException if `sequence` contains ill-formed UTF-16 (unpaired
* surrogates)
*/
private fun computeEncodedLength(sequence: CharSequence): Int {
// Warning to maintainers: this implementation is highly optimized.
val utf16Length = sequence.length
var utf8Length = utf16Length
var i = 0
// This loop optimizes for pure ASCII.
while (i < utf16Length && sequence[i].code < 0x80) {
i++
}
// This loop optimizes for chars less than 0x800.
while (i < utf16Length) {
val c = sequence[i]
if (c.code < 0x800) {
utf8Length += 0x7f - c.code ushr 31 // branch free!
} else {
utf8Length += encodedLengthGeneral(sequence, i)
break
}
i++
}
if (utf8Length < utf16Length) {
// Necessary and sufficient condition for overflow because of maximum 3x expansion
error("UTF-8 length does not fit in int: ${(utf8Length + (1L shl 32))}")
}
return utf8Length
}
private fun encodedLengthGeneral(sequence: CharSequence, start: Int): Int {
val utf16Length = sequence.length
var utf8Length = 0
var i = start
while (i < utf16Length) {
val c = sequence[i]
if (c.code < 0x800) {
utf8Length += 0x7f - c.code ushr 31 // branch free!
} else {
utf8Length += 2
if (c.isSurrogate()) {
// Check that we have a well-formed surrogate pair.
val cp: Int = codePointAt(sequence, i)
if (cp < MIN_SUPPLEMENTARY_CODE_POINT) {
errorSurrogate(i, utf16Length)
}
i++
}
}
i++
}
return utf8Length
}
/**
* Returns the number of bytes in the UTF-8-encoded form of `sequence`. For a string,
* this method is equivalent to `string.getBytes(UTF_8).length`, but is more efficient in
* both time and space.
*
* @throws IllegalArgumentException if `sequence` contains ill-formed UTF-16 (unpaired
* surrogates)
*/
public fun encodedLength(sequence: CharSequence): Int = computeEncodedLength(sequence)
/**
* Returns whether this is a single-byte codepoint (i.e., ASCII) with the form '0XXXXXXX'.
*/
public inline fun isOneByte(b: Byte): Boolean = b >= 0
/**
* Returns whether this is a two-byte codepoint with the form 110xxxxx 0xC0..0xDF.
*/
public inline fun isTwoBytes(b: Byte): Boolean = b < 0xE0.toByte()
/**
* Returns whether this is a three-byte codepoint with the form 1110xxxx 0xE0..0xEF.
*/
public inline fun isThreeBytes(b: Byte): Boolean = b < 0xF0.toByte()
/**
* Returns whether this is a four-byte codepoint with the form 11110xxx 0xF0..0xF4.
*/
public inline fun isFourByte(b: Byte): Boolean = b < 0xF8.toByte()
public fun handleOneByte(byte1: Byte, resultArr: CharArray, resultPos: Int) {
resultArr[resultPos] = byte1.toInt().toChar()
}
public fun handleTwoBytes(
byte1: Byte,
byte2: Byte,
resultArr: CharArray,
resultPos: Int
) {
// Simultaneously checks for illegal trailing-byte in leading position (<= '11000000') and
// overlong 2-byte, '11000001'.
if (byte1 < 0xC2.toByte()) {
error("Invalid UTF-8: Illegal leading byte in 2 bytes utf")
}
if (isNotTrailingByte(byte2)) {
error("Invalid UTF-8: Illegal trailing byte in 2 bytes utf")
}
resultArr[resultPos] = (byte1.toInt() and 0x1F shl 6 or trailingByteValue(byte2)).toChar()
}
public fun handleThreeBytes(
byte1: Byte,
byte2: Byte,
byte3: Byte,
resultArr: CharArray,
resultPos: Int
) {
if (isNotTrailingByte(byte2) || // overlong? 5 most significant bits must not all be zero
byte1 == 0xE0.toByte() && byte2 < 0xA0.toByte() || // check for illegal surrogate codepoints
byte1 == 0xED.toByte() && byte2 >= 0xA0.toByte() ||
isNotTrailingByte(byte3)
) {
error("Invalid UTF-8")
}
resultArr[resultPos] =
(byte1.toInt() and 0x0F shl 12 or (trailingByteValue(byte2) shl 6) or trailingByteValue(byte3)).toChar()
}
public fun handleFourBytes(
byte1: Byte,
byte2: Byte,
byte3: Byte,
byte4: Byte,
resultArr: CharArray,
resultPos: Int
) {
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.toInt() shl 28) + (byte2 - 0x90.toByte()) shr 30 != 0 || isNotTrailingByte(byte3) ||
isNotTrailingByte(byte4)
) {
error("Invalid UTF-8")
}
val codepoint: Int = (
byte1.toInt() and 0x07 shl 18
or (trailingByteValue(byte2) shl 12)
or (trailingByteValue(byte3) shl 6)
or trailingByteValue(byte4)
)
resultArr[resultPos] = highSurrogate(codepoint)
resultArr[resultPos + 1] = lowSurrogate(codepoint)
}
/**
* Returns whether the byte is not a valid continuation of the form '10XXXXXX'.
*/
private fun isNotTrailingByte(b: Byte): Boolean = b > 0xBF.toByte()
/**
* Returns the actual value of the trailing byte (removes the prefix '10') for composition.
*/
private fun trailingByteValue(b: Byte): Int = b.toInt() and 0x3F
private fun highSurrogate(codePoint: Int): Char =
(
Char.MIN_HIGH_SURROGATE - (MIN_SUPPLEMENTARY_CODE_POINT ushr 10) +
(codePoint ushr 10)
)
private fun lowSurrogate(codePoint: Int): Char = (Char.MIN_LOW_SURROGATE + (codePoint and 0x3ff))
/**
* Encode a [CharSequence] UTF8 codepoint into a byte array.
* @param `in` CharSequence to be encoded
* @param start start position of the first char in the codepoint
* @param out byte array of 4 bytes to be filled
* @return return the amount of bytes occupied by the codepoint
*/
public fun encodeUtf8CodePoint(input: CharSequence, start: Int, out: ByteArray): Int {
// utf8 codepoint needs at least 4 bytes
val inLength = input.length
if (start >= inLength) {
return 0
}
val c = input[start]
return if (c.code < 0x80) {
// One byte (0xxx xxxx)
out[0] = c.code.toByte()
1
} else if (c.code < 0x800) {
// Two bytes (110x xxxx 10xx xxxx)
out[0] = (0xC0 or (c.code ushr 6)).toByte()
out[1] = (0x80 or (0x3F and c.code)).toByte()
2
} else if (c < Char.MIN_SURROGATE || Char.MAX_SURROGATE < c) {
// Three bytes (1110 xxxx 10xx xxxx 10xx xxxx)
// Maximum single-char code point is 0xFFFF, 16 bits.
out[0] = (0xE0 or (c.code ushr 12)).toByte()
out[1] = (0x80 or (0x3F and (c.code ushr 6))).toByte()
out[2] = (0x80 or (0x3F and c.code)).toByte()
3
} 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
val low: Char = input[start + 1]
if (start + 1 == inLength || !(c.isHighSurrogate() and low.isLowSurrogate())) {
errorSurrogate(start, inLength)
}
val codePoint: Int = toCodePoint(c, low)
out[0] = (0xF shl 4 or (codePoint ushr 18)).toByte()
out[1] = (0x80 or (0x3F and (codePoint ushr 12))).toByte()
out[2] = (0x80 or (0x3F and (codePoint ushr 6))).toByte()
out[3] = (0x80 or (0x3F and codePoint)).toByte()
4
}
}
// Decodes a code point starting at index into out. Out parameter
// should have at least 2 chars.
public fun decodeUtf8CodePoint(bytes: ReadBuffer, index: Int, out: CharArray) {
// Bitwise OR combines the sign bits so any negative value fails the check.
val b1 = bytes[index]
when {
isOneByte(b1) -> handleOneByte(b1, out, 0)
isTwoBytes(b1) -> handleTwoBytes(b1, bytes[index + 1], out, 0)
isThreeBytes(b1) -> handleThreeBytes(b1, bytes[index + 1], bytes[index + 2], out, 0)
else -> handleFourBytes(b1, bytes[index + 1], bytes[index + 2], bytes[index + 3], out, 0)
}
}
public fun decodeUtf8Array(bytes: ByteArray, index: Int = 0, size: Int = bytes.size): String {
// Bitwise OR combines the sign bits so any negative value fails the check.
if (index or size or bytes.size - index - size < 0) {
error("buffer length=${bytes.size}, index=$index, size=$size")
}
var offset = index
val 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.
val resultArr = CharArray(size)
var 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) {
val b = bytes[offset]
if (!isOneByte(b)) {
break
}
offset++
handleOneByte(b, resultArr, resultPos++)
}
while (offset < limit) {
val byte1 = bytes[offset++]
if (isOneByte(byte1)) {
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) {
val b = bytes[offset]
if (!isOneByte(b)) {
break
}
offset++
handleOneByte(b, resultArr, resultPos++)
}
} else if (isTwoBytes(byte1)) {
if (offset >= limit) {
error("Invalid UTF-8")
}
handleTwoBytes(
byte1, /* byte2 */
bytes[offset++], resultArr, resultPos++
)
} else if (isThreeBytes(byte1)) {
if (offset >= limit - 1) {
error("Invalid UTF-8")
}
handleThreeBytes(
byte1, /* byte2 */
bytes[offset++], /* byte3 */
bytes[offset++],
resultArr,
resultPos++
)
} else {
if (offset >= limit - 2) {
error("Invalid UTF-8")
}
handleFourBytes(
byte1, /* byte2 */
bytes[offset++], /* byte3 */
bytes[offset++], /* byte4 */
bytes[offset++],
resultArr,
resultPos++
)
// 4-byte case requires two chars.
resultPos++
}
}
return resultArr.concatToString(0, resultPos)
}
public fun encodeUtf8Array(input: CharSequence,
out: ByteArray,
offset: Int = 0,
length: Int = out.size - offset): Int {
val utf16Length = input.length
var j = offset
var i = 0
val limit = offset + length
// Designed to take advantage of
// https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination
if (utf16Length == 0)
return 0
var cc: Char = input[i]
while (i < utf16Length && i + j < limit && input[i].also { cc = it }.code < 0x80) {
out[j + i] = cc.code.toByte()
i++
}
if (i == utf16Length) {
return j + utf16Length
}
j += i
var c: Char
while (i < utf16Length) {
c = input[i]
if (c.code < 0x80 && j < limit) {
out[j++] = c.code.toByte()
} else if (c.code < 0x800 && j <= limit - 2) { // 11 bits, two UTF-8 bytes
out[j++] = (0xF shl 6 or (c.code ushr 6)).toByte()
out[j++] = (0x80 or (0x3F and c.code)).toByte()
} else if ((c < Char.MIN_SURROGATE || Char.MAX_SURROGATE < c) && j <= limit - 3) {
// Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes
out[j++] = (0xF shl 5 or (c.code ushr 12)).toByte()
out[j++] = (0x80 or (0x3F and (c.code ushr 6))).toByte()
out[j++] = (0x80 or (0x3F and c.code)).toByte()
} else if (j <= limit - 4) {
// Minimum code point represented by a surrogate pair is 0x10000, 17 bits,
// four UTF-8 bytes
var low: Char = Char.MIN_VALUE
if (i + 1 == input.length ||
!isSurrogatePair(c, input[++i].also { low = it })
) {
errorSurrogate(i - 1, utf16Length)
}
val codePoint: Int = toCodePoint(c, low)
out[j++] = (0xF shl 4 or (codePoint ushr 18)).toByte()
out[j++] = (0x80 or (0x3F and (codePoint ushr 12))).toByte()
out[j++] = (0x80 or (0x3F and (codePoint ushr 6))).toByte()
out[j++] = (0x80 or (0x3F and codePoint)).toByte()
} else {
// If we are surrogates and we're not a surrogate pair, always throw an
// UnpairedSurrogateException instead of an ArrayOutOfBoundsException.
if (Char.MIN_SURROGATE <= c && c <= Char.MAX_SURROGATE &&
(i + 1 == input.length || !isSurrogatePair(c, input[i + 1]))
) {
errorSurrogate(i, utf16Length)
}
error("Failed writing character ${c.code.toShort().toString(radix = 16)} at index $j")
}
i++
}
return j
}
public fun codePointAt(seq: CharSequence, position: Int): Int {
var index = position
val c1 = seq[index]
if (c1.isHighSurrogate() && ++index < seq.length) {
val c2 = seq[index]
if (c2.isLowSurrogate()) {
return toCodePoint(c1, c2)
}
}
return c1.code
}
private fun isSurrogatePair(high: Char, low: Char) = high.isHighSurrogate() and low.isLowSurrogate()
private fun toCodePoint(high: Char, low: Char): Int = (high.code shl 10) + low.code +
(MIN_SUPPLEMENTARY_CODE_POINT - (Char.MIN_HIGH_SURROGATE.code shl 10) - Char.MIN_LOW_SURROGATE.code)
private fun errorSurrogate(i: Int, utf16Length: Int): Unit =
error("Unpaired surrogate at index $i of $utf16Length length")
// The minimum value of Unicode supplementary code point, constant `U+10000`.
private const val MIN_SUPPLEMENTARY_CODE_POINT = 0x010000
}
@@ -0,0 +1,832 @@
/*
* 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
import com.google.flatbuffers.kotlin.FlexBuffersBuilder.Companion.SHARE_KEYS_AND_STRINGS
import kotlin.experimental.and
import kotlin.jvm.JvmInline
import kotlin.math.pow
/**
* Returns a minified version of this FlexBuffer as a JSON.
*/
public fun Reference.toJson(): String = ArrayReadWriteBuffer(1024).let {
toJson(it)
val data = it.data() // it.getString(0, it.writePosition)
return data.decodeToString(0, it.writePosition)
}
/**
* Returns a minified version of this FlexBuffer as a JSON.
* @param out [ReadWriteBuffer] the JSON will be written.
*/
public fun Reference.toJson(out: ReadWriteBuffer) {
when (type) {
T_STRING -> {
val start = buffer.indirect(end, parentWidth)
val size = buffer.readULong(start - byteWidth, byteWidth).toInt()
out.jsonEscape(buffer, start, size)
}
T_KEY -> {
val start = buffer.indirect(end, parentWidth)
val end = buffer.findFirst(0.toByte(), start)
out.jsonEscape(buffer, start, end - start)
}
T_BLOB -> {
val blob = toBlob()
out.jsonEscape(out, blob.end, blob.size)
}
T_INT -> out.put(toLong().toString())
T_UINT -> out.put(toULong().toString())
T_FLOAT -> out.put(toDouble().toString())
T_NULL -> out.put("null")
T_BOOL -> out.put(toBoolean().toString())
T_MAP -> toMap().toJson(out)
T_VECTOR, T_VECTOR_BOOL, T_VECTOR_FLOAT, T_VECTOR_INT,
T_VECTOR_UINT, T_VECTOR_KEY, T_VECTOR_STRING_DEPRECATED -> toVector().toJson(out)
else -> error("Unable to convert type ${type.typeToString()} to JSON")
}
}
/**
* Returns a minified version of this FlexBuffer as a JSON.
*/
public fun Map.toJson(): String = ArrayReadWriteBuffer(1024).let { toJson(it); it.toString() }
/**
* Returns a minified version of this FlexBuffer as a JSON.
* @param out [ReadWriteBuffer] the JSON will be written.
*/
public fun Map.toJson(out: ReadWriteBuffer) {
out.put('{'.code.toByte())
// key values pairs
for (i in 0 until size) {
val key = keyAt(i)
out.jsonEscape(buffer, key.start, key.sizeInBytes)
out.put(':'.code.toByte())
get(i).toJson(out)
if (i != size - 1) {
out.put(','.code.toByte())
}
}
// close bracket
out.put('}'.code.toByte())
}
/**
* Returns a minified version of this FlexBuffer as a JSON.
*/
public fun Vector.toJson(): String = ArrayReadWriteBuffer(1024).let { toJson(it); it.toString() }
/**
* Returns a minified version of this FlexBuffer as a JSON.
* @param out that the JSON is being concatenated.
*/
public fun Vector.toJson(out: ReadWriteBuffer) {
out.put('['.code.toByte())
for (i in indices) {
get(i).toJson(out)
if (i != size - 1) {
out.put(','.code.toByte())
}
}
out.put(']'.code.toByte())
}
/**
* JSONParser class is used to parse a JSON as FlexBuffers. Calling [JSONParser.parse] fiils [output]
* and returns a [Reference] ready to be used.
*/
@ExperimentalUnsignedTypes
public class JSONParser(public var output: FlexBuffersBuilder = FlexBuffersBuilder(1024, SHARE_KEYS_AND_STRINGS)) {
private var readPos = 0
private var scopes = ScopeStack()
/**
* Parse a json as [String] and returns a [Reference] to a FlexBuffer.
*/
public fun parse(data: String): Reference = parse(ArrayReadBuffer(data.encodeToByteArray()))
/**
* Parse a json as [ByteArray] and returns a [Reference] to a FlexBuffer.
*/
public fun parse(data: ByteArray): Reference = parse(ArrayReadBuffer(data))
/**
* Parse a json as [ReadBuffer] and returns a [Reference] to a FlexBuffer.
*/
public fun parse(data: ReadBuffer): Reference {
reset()
parseValue(data, nextToken(data), null)
if (readPos < data.limit) {
val tok = skipWhitespace(data)
if (tok != CHAR_EOF) {
makeError(data, "Extraneous charaters after parse has finished", tok)
}
}
output.finish()
return getRoot(output.buffer)
}
private fun parseValue(data: ReadBuffer, token: Token, key: String? = null): FlexBufferType {
return when (token) {
TOK_BEGIN_OBJECT -> parseObject(data, key)
TOK_BEGIN_ARRAY -> parseArray(data, key)
TOK_TRUE -> T_BOOL.also { output[key] = true }
TOK_FALSE -> T_BOOL.also { output[key] = false }
TOK_NULL -> T_NULL.also { output.putNull(key) }
TOK_BEGIN_QUOTE -> parseString(data, key)
TOK_NUMBER -> parseNumber(data, data.data(), key)
else -> makeError(data, "Unexpected Character while parsing", 'x'.code.toByte())
}
}
private fun parseObject(data: ReadBuffer, key: String? = null): FlexBufferType {
this.scopes.push(SCOPE_OBJ_EMPTY)
val fPos = output.startMap()
val limit = data.limit
while (readPos <= limit) {
when (val tok = nextToken(data)) {
TOK_END_OBJECT -> {
this.scopes.pop()
output.endMap(fPos, key); return T_MAP
}
TOK_BEGIN_QUOTE -> {
val childKey = readString(data)
parseValue(data, nextToken(data), childKey)
}
else -> makeError(data, "Expecting start of object key", tok)
}
}
makeError(data, "Unable to parse the object", "x".toByte())
}
private fun parseArray(data: ReadBuffer, key: String? = null): FlexBufferType {
this.scopes.push(SCOPE_ARRAY_EMPTY)
val fPos = output.startVector()
var elementType = T_INVALID
var multiType = false
val limit = data.limit
while (readPos <= limit) {
when (val tok = nextToken(data)) {
TOK_END_ARRAY -> {
this.scopes.pop()
return if (!multiType && elementType.isScalar()) {
output.endTypedVector(fPos, key)
elementType.toElementTypedVector()
} else {
output.endVector(key, fPos)
T_VECTOR
}
}
else -> {
val newType = parseValue(data, tok, null)
if (elementType == T_INVALID) {
elementType = newType
} else if (newType != elementType) {
multiType = true
}
}
}
}
makeError(data, "Unable to parse the array")
}
private fun parseNumber(data: ReadBuffer, array: ByteArray, key: String?): FlexBufferType {
val ary = array
var cursor = readPos
var c = data[readPos++]
var useDouble = false
val limit = ary.size
var sign = 1
var double: Double
var long = 0L
var digits = 0
if (c == CHAR_MINUS) {
cursor++
checkEOF(data, cursor)
c = ary[cursor]
sign = -1
}
// peek first byte
when (c) {
CHAR_0 -> {
cursor++
if (cursor != limit) {
c = ary[cursor]
}
}
!in CHAR_0..CHAR_9 -> makeError(data, "Invalid Number", c)
else -> {
do {
val digit = c - CHAR_0
// double = 10.0 * double + digit
long = 10 * long + digit
digits++
cursor++
if (cursor == limit) break
c = ary[cursor]
} while (c in CHAR_0..CHAR_9)
}
}
var exponent = 0
// If we find '.' we need to convert to double
if (c == CHAR_DOT) {
useDouble = true
checkEOF(data, cursor)
c = ary[++cursor]
if (c < CHAR_0 || c > CHAR_9) {
makeError(data, "Invalid Number", c)
}
do {
// double = double * 10 + (tok - CHAR_0)
long = 10 * long + (c - CHAR_0)
digits++
--exponent
cursor++
if (cursor == limit) break
c = ary[cursor]
} while (c in CHAR_0..CHAR_9)
}
// If we find 'e' we need to convert to double
if (c == CHAR_e || c == CHAR_E) {
useDouble = true
++cursor
checkEOF(data, cursor)
c = ary[cursor]
var negativeExponent = false
if (c == CHAR_MINUS) {
++cursor
checkEOF(data, cursor)
negativeExponent = true
c = ary[cursor]
} else if (c == CHAR_PLUS) {
++cursor
checkEOF(data, cursor)
c = ary[cursor]
}
if (c < CHAR_0 || c > CHAR_9) {
makeError(data, "Missing exponent", c)
}
var exp = 0
do {
val digit = c - CHAR_0
exp = 10 * exp + digit
++cursor
if (cursor == limit) break
c = ary[cursor]
} while (c in CHAR_0..CHAR_9)
exponent += if (negativeExponent) -exp else exp
}
if (digits > 17 || exponent < -19 || exponent > 19) {
// if the float number is not simple enough
// we use language's Double parsing, which is slower but
// produce more expected results for extreme numbers.
val firstPos = readPos - 1
val str = data.getString(firstPos, cursor - firstPos)
if (useDouble) {
double = str.toDouble()
output[key] = double
} else {
long = str.toLong()
output[key] = long
}
} else {
// this happens on single numbers outside any object
// or array
if (useDouble || exponent != 0) {
double = if (long == 0L) 0.0 else long.toDouble() * 10.0.pow(exponent)
double *= sign
output[key] = double
} else {
long *= sign
output[key] = long
}
}
readPos = cursor
return if (useDouble) T_FLOAT else T_INT
}
private fun parseString(data: ReadBuffer, key: String?): FlexBufferType {
output[key] = readString(data)
return T_STRING
}
private fun readString(data: ReadBuffer): String {
val limit = data.limit
if (data is ArrayReadBuffer) {
val ary = data.data()
// enables range check elimination
return readString(data, limit) { ary[it] }
}
return readString(data, limit) { data[it] }
}
private inline fun readString(data: ReadBuffer, limit: Int, crossinline fetch: (Int) -> Byte): String {
var cursorPos = readPos
var foundEscape = false
var currentChar: Byte = 0
// we loop over every 4 bytes until find any non-plain char
while (limit - cursorPos >= 4) {
currentChar = fetch(cursorPos)
if (!isPlainStringChar(currentChar)) {
foundEscape = true
break
}
currentChar = fetch(cursorPos + 1)
if (!isPlainStringChar(currentChar)) {
cursorPos += 1
foundEscape = true
break
}
currentChar = fetch(cursorPos + 2)
if (!isPlainStringChar(currentChar)) {
cursorPos += 2
foundEscape = true
break
}
currentChar = fetch(cursorPos + 3)
if (!isPlainStringChar(currentChar)) {
cursorPos += 3
foundEscape = true
break
}
cursorPos += 4
}
if (!foundEscape) {
// if non-plain string char is not found we loop over
// the remaining bytes
while (true) {
if (cursorPos >= limit) {
error("Unexpected end of string")
}
currentChar = fetch(cursorPos)
if (!isPlainStringChar(currentChar)) {
break
}
++cursorPos
}
}
if (currentChar == CHAR_DOUBLE_QUOTE) {
val str = data.getString(readPos, cursorPos - readPos)
readPos = cursorPos + 1
return str
}
if (currentChar in 0..0x1f) {
error("Illegal Codepoint")
} else {
// backslash or >0x7f
return readStringSlow(data, currentChar, cursorPos)
}
}
private fun readStringSlow(data: ReadBuffer, first: Byte, lastPos: Int): String {
var cursorPos = lastPos
var endOfString = lastPos
while (true) {
val pos = data.findFirst(CHAR_DOUBLE_QUOTE, endOfString)
when {
pos == -1 -> makeError(data, "Unexpected EOF, missing end of string '\"'", first)
data[pos - 1] == CHAR_BACKSLASH && data[pos - 2] != CHAR_BACKSLASH -> {
// here we are checking for double quotes preceded by backslash. eg \"
// we have to look past pos -2 to make sure that the backlash is not
// part of a previous escape, eg "\\"
endOfString = pos + 1
}
else -> {
endOfString = pos; break
}
}
}
// copy everything before the escape
val builder = StringBuilder(data.getString(readPos, lastPos - readPos))
while (true) {
when (val pos = data.findFirst(CHAR_BACKSLASH, cursorPos, endOfString)) {
-1 -> {
val doubleQuotePos = data.findFirst(CHAR_DOUBLE_QUOTE, cursorPos)
if (doubleQuotePos == -1) makeError(data, "Reached EOF before enclosing string", first)
val rest = data.getString(cursorPos, doubleQuotePos - cursorPos)
builder.append(rest)
readPos = doubleQuotePos + 1
return builder.toString()
}
else -> {
// we write everything up to \
builder.append(data.getString(cursorPos, pos - cursorPos))
val c = data[pos + 1]
builder.append(readEscapedChar(data, c, pos))
cursorPos = pos + if (c == CHAR_u) 6 else 2
}
}
}
}
private inline fun isPlainStringChar(c: Byte): Boolean {
val flags = parseFlags
// return c in 0x20..0x7f && c != 0x22.toByte() && c != 0x5c.toByte()
return (flags[c.toInt() and 0xFF] and 1) != 0.toByte()
}
private inline fun isWhitespace(c: Byte): Boolean {
val flags = parseFlags
// return c == '\r'.toByte() || c == '\n'.toByte() || c == '\t'.toByte() || c == ' '.toByte()
return (flags[c.toInt() and 0xFF] and 2) != 0.toByte()
}
private fun reset() {
readPos = 0
output.clear()
scopes.reset()
}
private fun nextToken(data: ReadBuffer): Token {
val scope = this.scopes.last
when (scope) {
SCOPE_ARRAY_EMPTY -> this.scopes.last = SCOPE_ARRAY_FILLED
SCOPE_ARRAY_FILLED -> {
when (val c = skipWhitespace(data)) {
CHAR_CLOSE_ARRAY -> return TOK_END_ARRAY
CHAR_COMMA -> Unit
else -> makeError(data, "Unfinished Array", c)
}
}
SCOPE_OBJ_EMPTY, SCOPE_OBJ_FILLED -> {
this.scopes.last = SCOPE_OBJ_KEY
// Look for a comma before the next element.
if (scope == SCOPE_OBJ_FILLED) {
when (val c = skipWhitespace(data)) {
CHAR_CLOSE_OBJECT -> return TOK_END_OBJECT
CHAR_COMMA -> Unit
else -> makeError(data, "Unfinished Object", c)
}
}
return when (val c = skipWhitespace(data)) {
CHAR_DOUBLE_QUOTE -> TOK_BEGIN_QUOTE
CHAR_CLOSE_OBJECT -> if (scope != SCOPE_OBJ_FILLED) {
TOK_END_OBJECT
} else {
makeError(data, "Expected Key", c)
}
else -> {
makeError(data, "Expected Key/Value", c)
}
}
}
SCOPE_OBJ_KEY -> {
this.scopes.last = SCOPE_OBJ_FILLED
when (val c = skipWhitespace(data)) {
CHAR_COLON -> Unit
else -> makeError(data, "Expect ${CHAR_COLON.print()}", c)
}
}
SCOPE_DOC_EMPTY -> this.scopes.last = SCOPE_DOC_FILLED
SCOPE_DOC_FILLED -> {
val c = skipWhitespace(data)
if (c != CHAR_EOF)
makeError(data, "Root object already finished", c)
return TOK_EOF
}
}
val c = skipWhitespace(data)
when (c) {
CHAR_CLOSE_ARRAY -> if (scope == SCOPE_ARRAY_EMPTY) return TOK_END_ARRAY
CHAR_COLON -> makeError(data, "Unexpected character", c)
CHAR_DOUBLE_QUOTE -> return TOK_BEGIN_QUOTE
CHAR_OPEN_ARRAY -> return TOK_BEGIN_ARRAY
CHAR_OPEN_OBJECT -> return TOK_BEGIN_OBJECT
CHAR_t -> {
checkEOF(data, readPos + 2)
// 0x65757274 is equivalent to ['t', 'r', 'u', 'e' ] as a 4 byte Int
if (data.getInt(readPos - 1) != 0x65757274) {
makeError(data, "Expecting keyword \"true\"", c)
}
readPos += 3
return TOK_TRUE
}
CHAR_n -> {
checkEOF(data, readPos + 2)
// 0x6c6c756e is equivalent to ['n', 'u', 'l', 'l' ] as a 4 byte Int
if (data.getInt(readPos - 1) != 0x6c6c756e) {
makeError(data, "Expecting keyword \"null\"", c)
}
readPos += 3
return TOK_NULL
}
CHAR_f -> {
checkEOF(data, readPos + 3)
// 0x65736c61 is equivalent to ['a', 'l', 's', 'e' ] as a 4 byte Int
if (data.getInt(readPos) != 0x65736c61) {
makeError(data, "Expecting keyword \"false\"", c)
}
readPos += 4
return TOK_FALSE
}
CHAR_0, CHAR_1, CHAR_2, CHAR_3, CHAR_4, CHAR_5,
CHAR_6, CHAR_7, CHAR_8, CHAR_9, CHAR_MINUS -> return TOK_NUMBER.also {
readPos-- // rewind one position so we don't lose first digit
}
}
makeError(data, "Expecting element", c)
}
// keeps increasing [readPos] until finds a non-whitespace byte
private inline fun skipWhitespace(data: ReadBuffer): Byte {
val limit = data.limit
if (data is ArrayReadBuffer) {
// enables range check elimination
val ary = data.data()
return skipWhitespace(limit) { ary[it] }
}
return skipWhitespace(limit) { data[it] }
}
private inline fun skipWhitespace(limit: Int, crossinline fetch: (Int) -> Byte): Byte {
var pos = readPos
while (pos < limit) {
val d = fetch(pos++)
if (!isWhitespace(d)) {
readPos = pos
return d
}
}
readPos = limit
return CHAR_EOF
}
// byte1 is expected to be first char before `\`
private fun readEscapedChar(data: ReadBuffer, byte1: Byte, cursorPos: Int): Char {
return when (byte1) {
CHAR_u -> {
checkEOF(data, cursorPos + 1 + 4)
var result: Char = 0.toChar()
var i = cursorPos + 2 // cursorPos is on '\\', cursorPos + 1 is 'u'
val end = i + 4
while (i < end) {
val part: Byte = data[i]
result = (result.code shl 4).toChar()
result += when (part) {
in CHAR_0..CHAR_9 -> part - CHAR_0
in CHAR_a..CHAR_f -> part - CHAR_a + 10
in CHAR_A..CHAR_F -> part - CHAR_A + 10
else -> makeError(data, "Invalid utf8 escaped character", -1)
}
i++
}
result
}
CHAR_b -> '\b'
CHAR_t -> '\t'
CHAR_r -> '\r'
CHAR_n -> '\n'
CHAR_f -> 12.toChar() // '\f'
CHAR_DOUBLE_QUOTE, CHAR_BACKSLASH, CHAR_FORWARDSLASH -> byte1.toInt().toChar()
else -> makeError(data, "Invalid escape sequence.", byte1)
}
}
private fun Byte.print(): String = when (this) {
in 0x21..0x7E -> "'${this.toInt().toChar()}'" // visible ascii chars
CHAR_EOF -> "EOF"
else -> "'0x${this.toString(16)}'"
}
private inline fun makeError(data: ReadBuffer, msg: String, tok: Byte? = null): Nothing {
val (line, column) = calculateErrorPosition(data, readPos)
if (tok != null) {
error("Error At ($line, $column): $msg, got ${tok.print()}")
} else {
error("Error At ($line, $column): $msg")
}
}
private inline fun makeError(data: ReadBuffer, msg: String, tok: Token): Nothing {
val (line, column) = calculateErrorPosition(data, readPos)
error("Error At ($line, $column): $msg, got ${tok.print()}")
}
private inline fun checkEOF(data: ReadBuffer, pos: Int) {
if (pos >= data.limit)
makeError(data, "Unexpected end of file", -1)
}
private fun calculateErrorPosition(data: ReadBuffer, endPos: Int): Pair<Int, Int> {
var line = 1
var column = 1
var current = 0
while (current < endPos - 1) {
if (data[current++] == CHAR_NEWLINE) {
++line
column = 1
} else {
++column
}
}
return Pair(line, column)
}
}
internal inline fun Int.toPaddedHex(): String = "\\u${this.toString(16).padStart(4, '0')}"
private inline fun ReadWriteBuffer.jsonEscape(data: ReadBuffer, start: Int, size: Int) {
val replacements = JSON_ESCAPE_CHARS
put(CHAR_DOUBLE_QUOTE)
var last = start
val length: Int = size
val ary = data.data()
for (i in start until start + length) {
val c = ary[i].toUByte()
var replacement: ByteArray?
if (c.toInt() < 128) {
replacement = replacements[c.toInt()]
if (replacement == null) {
continue
}
} else {
continue
}
if (last < i) {
put(ary, last, i - last)
}
put(replacement, 0, replacement.size)
last = i + 1
}
if (last < (last + length)) {
put(ary, last, (start + length) - last)
}
put(CHAR_DOUBLE_QUOTE)
}
// Following escape strategy defined in RFC7159.
private val JSON_ESCAPE_CHARS: Array<ByteArray?> = arrayOfNulls<ByteArray>(128).apply {
this['\n'.code] = "\\n".encodeToByteArray()
this['\t'.code] = "\\t".encodeToByteArray()
this['\r'.code] = "\\r".encodeToByteArray()
this['\b'.code] = "\\b".encodeToByteArray()
this[0x0c] = "\\f".encodeToByteArray()
this['"'.code] = "\\\"".encodeToByteArray()
this['\\'.code] = "\\\\".encodeToByteArray()
for (i in 0..0x1f) {
this[i] = "\\u${i.toPaddedHex()}".encodeToByteArray()
}
}
// Scope is used to the define current space that the scanner is operating.
@JvmInline
private value class Scope(val id: Int)
private val SCOPE_DOC_EMPTY = Scope(0)
private val SCOPE_DOC_FILLED = Scope(1)
private val SCOPE_OBJ_EMPTY = Scope(2)
private val SCOPE_OBJ_KEY = Scope(3)
private val SCOPE_OBJ_FILLED = Scope(4)
private val SCOPE_ARRAY_EMPTY = Scope(5)
private val SCOPE_ARRAY_FILLED = Scope(6)
// Keeps the stack state of the scopes being scanned. Currently defined to have a
// max stack size of 22, as per tests cases defined in http://json.org/JSON_checker/
private class ScopeStack(
private val ary: IntArray = IntArray(22) { SCOPE_DOC_EMPTY.id },
var lastPos: Int = 0
) {
var last: Scope
get() = Scope(ary[lastPos])
set(x) {
ary[lastPos] = x.id
}
fun reset() {
lastPos = 0
ary[0] = SCOPE_DOC_EMPTY.id
}
fun pop(): Scope {
// println("Popping: ${last.print()}")
return Scope(ary[lastPos--])
}
fun push(scope: Scope): Scope {
if (lastPos == ary.size - 1)
error("Too much nesting reached. Max nesting is ${ary.size} levels")
// println("PUSHING : ${scope.print()}")
ary[++lastPos] = scope.id
return scope
}
}
@JvmInline
private value class Token(val id: Int) {
fun print(): String = when (this) {
TOK_EOF -> "TOK_EOF"
TOK_NONE -> "TOK_NONE"
TOK_BEGIN_OBJECT -> "TOK_BEGIN_OBJECT"
TOK_END_OBJECT -> "TOK_END_OBJECT"
TOK_BEGIN_ARRAY -> "TOK_BEGIN_ARRAY"
TOK_END_ARRAY -> "TOK_END_ARRAY"
TOK_NUMBER -> "TOK_NUMBER"
TOK_TRUE -> "TOK_TRUE"
TOK_FALSE -> "TOK_FALSE"
TOK_NULL -> "TOK_NULL"
TOK_BEGIN_QUOTE -> "TOK_BEGIN_QUOTE"
else -> this.toString()
}
}
private val TOK_EOF = Token(-1)
private val TOK_NONE = Token(0)
private val TOK_BEGIN_OBJECT = Token(1)
private val TOK_END_OBJECT = Token(2)
private val TOK_BEGIN_ARRAY = Token(3)
private val TOK_END_ARRAY = Token(4)
private val TOK_NUMBER = Token(5)
private val TOK_TRUE = Token(6)
private val TOK_FALSE = Token(7)
private val TOK_NULL = Token(8)
private val TOK_BEGIN_QUOTE = Token(9)
private const val CHAR_NEWLINE = '\n'.code.toByte()
private const val CHAR_OPEN_OBJECT = '{'.code.toByte()
private const val CHAR_COLON = ':'.code.toByte()
private const val CHAR_CLOSE_OBJECT = '}'.code.toByte()
private const val CHAR_OPEN_ARRAY = '['.code.toByte()
private const val CHAR_CLOSE_ARRAY = ']'.code.toByte()
private const val CHAR_DOUBLE_QUOTE = '"'.code.toByte()
private const val CHAR_BACKSLASH = '\\'.code.toByte()
private const val CHAR_FORWARDSLASH = '/'.code.toByte()
private const val CHAR_f = 'f'.code.toByte()
private const val CHAR_a = 'a'.code.toByte()
private const val CHAR_r = 'r'.code.toByte()
private const val CHAR_t = 't'.code.toByte()
private const val CHAR_n = 'n'.code.toByte()
private const val CHAR_b = 'b'.code.toByte()
private const val CHAR_e = 'e'.code.toByte()
private const val CHAR_E = 'E'.code.toByte()
private const val CHAR_u = 'u'.code.toByte()
private const val CHAR_A = 'A'.code.toByte()
private const val CHAR_F = 'F'.code.toByte()
private const val CHAR_EOF = (-1).toByte()
private const val CHAR_COMMA = ','.code.toByte()
private const val CHAR_0 = '0'.code.toByte()
private const val CHAR_1 = '1'.code.toByte()
private const val CHAR_2 = '2'.code.toByte()
private const val CHAR_3 = '3'.code.toByte()
private const val CHAR_4 = '4'.code.toByte()
private const val CHAR_5 = '5'.code.toByte()
private const val CHAR_6 = '6'.code.toByte()
private const val CHAR_7 = '7'.code.toByte()
private const val CHAR_8 = '8'.code.toByte()
private const val CHAR_9 = '9'.code.toByte()
private const val CHAR_MINUS = '-'.code.toByte()
private const val CHAR_PLUS = '+'.code.toByte()
private const val CHAR_DOT = '.'.code.toByte()
// This template utilizes the One Definition Rule to create global arrays in a
// header. As seen in:
// https://github.com/chadaustin/sajson/blob/master/include/sajson.h
// bit 0 (1) - set if: plain ASCII string character
// bit 1 (2) - set if: whitespace
// bit 4 (0x10) - set if: 0-9 e E .
private val parseFlags = byteArrayOf(
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 2, 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
3, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0x11, 1, // 2
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 1, 1, 1, 1, 1, 1, // 3
1, 1, 1, 1, 1, 0x11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 5
1, 1, 1, 1, 1, 0x11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
// 128-255
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
)
@@ -0,0 +1,55 @@
package com.google.flatbuffers.kotlin
import kotlin.test.assertTrue
fun <T> assertArrayEquals(expected: Array<out T>, actual: Array<out T>) =
assertTrue(expected contentEquals actual, arrayFailMessage(expected, actual))
fun assertArrayEquals(expected: IntArray, actual: IntArray) =
assertTrue(expected contentEquals actual, arrayFailMessage(expected, actual))
fun assertArrayEquals(expected: ShortArray, actual: ShortArray) =
assertTrue(expected contentEquals actual, arrayFailMessage(expected, actual))
fun assertArrayEquals(expected: LongArray, actual: LongArray) =
assertTrue(expected contentEquals actual, arrayFailMessage(expected, actual))
fun assertArrayEquals(expected: ByteArray, actual: ByteArray) =
assertTrue(expected contentEquals actual, arrayFailMessage(expected, actual))
fun assertArrayEquals(expected: DoubleArray, actual: DoubleArray) =
assertTrue(expected contentEquals actual, arrayFailMessage(expected, actual))
fun assertArrayEquals(expected: FloatArray, actual: FloatArray) =
assertTrue(expected contentEquals actual, arrayFailMessage(expected, actual))
fun <T> arrayFailMessage(expected: Array<out T>, actual: Array<out T>): String =
failMessage(expected.contentToString(), actual.contentToString())
fun arrayFailMessage(expected: IntArray, actual: IntArray): String =
failMessage(expected.contentToString(), actual.contentToString())
fun arrayFailMessage(expected: ShortArray, actual: ShortArray): String =
failMessage(expected.contentToString(), actual.contentToString())
fun arrayFailMessage(expected: LongArray, actual: LongArray): String =
failMessage(expected.contentToString(), actual.contentToString())
fun failMessage(expected: String, actual: String): String =
"Expected: $expected\nActual: $actual"
fun arrayFailMessage(expected: FloatArray, actual: FloatArray): String {
return "Expected: ${expected.contentToString()}\nActual: ${actual.contentToString()}"
}
fun arrayFailMessage(expected: DoubleArray, actual: DoubleArray): String {
return "Expected: ${expected.contentToString()}\nActual: ${actual.contentToString()}"
}
fun arrayFailMessage(expected: BooleanArray, actual: BooleanArray): String {
return "Expected: ${expected.contentToString()}\nActual: ${actual.contentToString()}"
}
fun arrayFailMessage(expected: ByteArray, actual: ByteArray): String {
return "Expected: ${expected.contentToString()}\nActual: ${actual.contentToString()}"
}
@@ -0,0 +1,78 @@
package com.google.flatbuffers.kotlin
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class BuffersTest {
@Test
fun readBufferStringTest() {
val text = "Hello world!"
val bytes = text.encodeToByteArray()
val fullRead = ArrayReadBuffer(bytes)
val helloRead = ArrayReadBuffer(bytes, limit = 5)
val worldRead = fullRead.slice(6, 6)
assertEquals(bytes.size, fullRead.limit)
assertEquals(text, fullRead.getString(0, fullRead.limit))
assertEquals("Hello" , helloRead.getString(0, helloRead.limit))
assertEquals("world!" , worldRead.getString())
assertEquals(fullRead.getString(0, 5) , helloRead.getString(0, helloRead.limit))
assertEquals(fullRead.getString(6, 6) , worldRead.getString(0, worldRead.limit))
for (i in 0 until helloRead.limit) {
assertEquals(fullRead[i], helloRead[i])
}
for (i in 0 until worldRead.limit) {
assertEquals(fullRead[6 + i], worldRead[i])
}
}
@Test
fun readWriteBufferPrimitivesTest() {
val text = "Hello world!"
val bytes = text.encodeToByteArray()
val wt = ArrayReadWriteBuffer(bytes)
wt.requestCapacity(4096)
wt.put("Tests")
val str1 = wt.writePosition
assertEquals("Tests world!", wt.getString(0, bytes.size))
assertEquals("Tests", wt.getString(0, str1))
wt.put(Int.MAX_VALUE)
assertEquals(Int.MAX_VALUE, wt.getInt(str1))
val pos = wt.writePosition
wt.put(Double.NEGATIVE_INFINITY)
assertEquals(Double.NEGATIVE_INFINITY, wt.getDouble(pos))
val jap = " are really すごい!".encodeToByteArray()
wt.writePosition = str1
wt.put(jap)
assertEquals("Tests are really すごい!", wt.getString())
}
@Test
fun readWriteBufferGrowthTest() {
val a = ArrayReadWriteBuffer(1)
assertEquals(1, a.capacity)
a.put(0.toByte())
assertEquals(1, a.capacity)
assertFailsWith(IndexOutOfBoundsException::class) { a.put(0xFF.toShort()) }
a.requestCapacity(8)
a.writePosition = 0
a.put(0xFF.toShort())
assertEquals(8, a.capacity)
assertEquals(0xFF, a.getShort(0))
a.requestCapacity(8 + 12)
a.put(ByteArray(12) { it.toByte() })
// we grow as power or two, so 20 jumps to 32
assertEquals(32, a.capacity)
a.requestCapacity(513, false)
assertEquals(1024, a.capacity)
a.requestCapacity(234, false)
assertEquals(1024, a.capacity)
}
}
@@ -0,0 +1,147 @@
/*
* 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.
*/
package com.google.flatbuffers.kotlin
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class ByteArrayTest {
@Test
fun testByte() {
val testSet = arrayOf(
67.toByte() to byteArrayOf(67),
Byte.MIN_VALUE to byteArrayOf(-128),
Byte.MAX_VALUE to byteArrayOf(127),
0.toByte() to byteArrayOf(0)
)
val data = ByteArray(1)
testSet.forEach {
data[0] = it.first
assertArrayEquals(data, it.second)
assertEquals(it.first, data[0])
}
}
@Test
fun testShort() {
val testSet = arrayOf(
6712.toShort() to byteArrayOf(56, 26),
Short.MIN_VALUE to byteArrayOf(0, -128),
Short.MAX_VALUE to byteArrayOf(-1, 127),
0.toShort() to byteArrayOf(0, 0,)
)
val data = ByteArray(Short.SIZE_BYTES)
testSet.forEach {
data.setShort(0, it.first)
assertArrayEquals(data, it.second)
assertEquals(it.first, data.getShort(0))
}
}
@Test
fun testInt() {
val testSet = arrayOf(
33333500 to byteArrayOf(-4, -96, -4, 1),
Int.MIN_VALUE to byteArrayOf(0, 0, 0, -128),
Int.MAX_VALUE to byteArrayOf(-1, -1, -1, 127),
0 to byteArrayOf(0, 0, 0, 0)
)
val data = ByteArray(Int.SIZE_BYTES)
testSet.forEach {
data.setInt(0, it.first)
assertArrayEquals(data, it.second)
assertEquals(it.first, data.getInt(0))
}
}
@Test
fun testLong() {
val testSet = arrayOf(
1234567123122890123L to byteArrayOf(-117, -91, 29, -23, 65, 16, 34, 17),
-1L to byteArrayOf(-1, -1, -1, -1, -1, -1, -1, -1),
Long.MIN_VALUE to byteArrayOf(0, 0, 0, 0, 0, 0, 0, -128),
Long.MAX_VALUE to byteArrayOf(-1, -1, -1, -1, -1, -1, -1, 127),
0L to byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0)
)
val data = ByteArray(Long.SIZE_BYTES)
testSet.forEach {
data.setLong(0, it.first)
assertArrayEquals(data, it.second)
assertEquals(it.first, data.getLong(0))
}
}
@Test
fun testULong() {
val testSet = arrayOf(
1234567123122890123UL to byteArrayOf(-117, -91, 29, -23, 65, 16, 34, 17),
ULong.MIN_VALUE to byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0),
(-1L).toULong() to byteArrayOf(-1, -1, -1, -1, -1, -1, -1, -1),
0UL to byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0)
)
val data = ByteArray(ULong.SIZE_BYTES)
testSet.forEach {
data.setULong(0, it.first)
assertArrayEquals(it.second, data)
assertEquals(it.first, data.getULong(0))
}
}
@Test
fun testFloat() {
val testSet = arrayOf(
3545.56337f to byteArrayOf(4, -103, 93, 69),
Float.MIN_VALUE to byteArrayOf(1, 0, 0, 0),
Float.MAX_VALUE to byteArrayOf(-1, -1, 127, 127),
0f to byteArrayOf(0, 0, 0, 0)
)
val data = ByteArray(Float.SIZE_BYTES)
testSet.forEach {
data.setFloat(0, it.first)
assertArrayEquals(data, it.second)
}
}
@Test
fun testDouble() {
val testSet = arrayOf(
123456.523423423412 to byteArrayOf(88, 61, -15, 95, 8, 36, -2, 64),
Double.MIN_VALUE to byteArrayOf(1, 0, 0, 0, 0, 0, 0, 0),
Double.MAX_VALUE to byteArrayOf(-1, -1, -1, -1, -1, -1, -17, 127),
0.0 to byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0)
)
val data = ByteArray(Long.SIZE_BYTES)
testSet.forEach {
data.setDouble(0, it.first)
assertArrayEquals(data, it.second)
assertEquals(it.first, data.getDouble(0))
}
}
@Test
fun testString() {
val testSet = "∮ E⋅da = Q"
val encoded = testSet.encodeToByteArray()
val data = ByteArray(encoded.size)
data.setCharSequence(0, testSet)
assertArrayEquals(encoded, data)
assertEquals(testSet, data.getString(0, encoded.size))
}
}
@@ -0,0 +1,575 @@
/*
* 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("UNCHECKED_CAST")
package com.google.flatbuffers.kotlin
import Attacker
import AttackerOffsetArray
import CharacterEArray
import dictionaryLookup.LongFloatEntry
import dictionaryLookup.LongFloatMap
import Movie
import dictionaryLookup.LongFloatEntryOffsetArray
import myGame.example.*
import myGame.example.Test.Companion.createTest
import optionalScalars.OptionalByte
import optionalScalars.ScalarStuff
import kotlin.test.Test
import kotlin.test.assertEquals
@ExperimentalUnsignedTypes
class FlatBufferBuilderTest {
@Test
fun testSingleTable() {
val fbb = FlatBufferBuilder()
val name = fbb.createString("Frodo")
val invValues = ubyteArrayOf(10u, 11u, 12u, 13u, 14u)
val inv = Monster.createInventoryVector(fbb, invValues)
Monster.startMonster(fbb)
Monster.addPos(
fbb, Vec3.createVec3(
fbb, 1.0f, 2.0f, 3.0f, 3.0,
Color.Green, 5.toShort(), 6.toByte()
)
)
Monster.addHp(fbb, 80.toShort())
Monster.addName(fbb, name)
Monster.addMana(fbb, 150)
Monster.addInventory(fbb, inv)
Monster.addTestType(fbb, AnyE.Monster)
Monster.addTestbool(fbb, true)
Monster.addTesthashu32Fnv1(fbb, (Int.MAX_VALUE + 1L).toUInt())
val root = Monster.endMonster(fbb)
fbb.finish(root)
val monster = Monster.asRoot(fbb.dataBuffer())
assertEquals(monster.name, "Frodo")
assertEquals(monster.mana, 150.toShort())
assertEquals(monster.hp, 80)
val pos = monster.pos!!
assertEquals(monster.inventory(0), invValues[0])
assertEquals(monster.inventory(1), invValues[1])
assertEquals(monster.inventory(2), invValues[2])
assertEquals(monster.inventory(3), invValues[3])
assertEquals(pos.x, 1.0f)
assertEquals(pos.y, 2.0f)
assertEquals(pos.z, 3.0f)
assertEquals(pos.test1, 3.0)
assertEquals(pos.test2, Color.Green)
assertEquals(pos.test3!!.a, 5.toShort())
assertEquals(pos.test3!!.b, 6.toByte())
val inventoryBuffer = monster.inventoryAsBuffer()
assertEquals(invValues.size, inventoryBuffer.limit)
for (i in invValues.indices) {
assertEquals(invValues[i], inventoryBuffer.getUByte(i))
}
}
@Test
fun testSortedVector() {
val fbb = FlatBufferBuilder()
val names = arrayOf(fbb.createString("Frodo"), fbb.createString("Barney"), fbb.createString("Wilma"))
val monsters = MonsterOffsetArray(3) {
Monster.startMonster(fbb)
Monster.addName(fbb, names[it])
Monster.endMonster(fbb)
}
val ary = Monster.createTestarrayoftablesVector(fbb, monsters)
Monster.startMonster(fbb)
Monster.addName(fbb, names[0])
Monster.addTestarrayoftables(fbb, ary)
val root = Monster.endMonster(fbb)
fbb.finish(root)
val a = Monster.asRoot(fbb.dataBuffer())
assertEquals(a.name, "Frodo")
assertEquals(a.testarrayoftablesLength, 3)
val monster0 = a.testarrayoftables(0)!!
val monster1 = a.testarrayoftables(1)!!
val monster2 = a.testarrayoftables(2)!!
assertEquals(monster0.name, "Frodo")
assertEquals(monster1.name, "Barney")
assertEquals(monster2.name, "Wilma")
// test AsBuffer feature
}
@Test
fun testCreateBufferVector() {
val fbb = FlatBufferBuilder(16)
val str = fbb.createString("MyMonster")
val inventory = ubyteArrayOf(0u, 1u, 2u, 3u, 4u, 5u, 6u, 88u, 99u, 122u, 1u)
val vec = Monster.createInventoryVector(fbb, inventory)
Monster.startMonster(fbb)
Monster.addInventory(fbb, vec)
Monster.addName(fbb, str)
val monster1 = Monster.endMonster(fbb)
Monster.finishMonsterBuffer(fbb, monster1)
val monsterObject: Monster = Monster.asRoot(fbb.dataBuffer())
val iBuffer = monsterObject.inventoryAsBuffer()
assertEquals(monsterObject.inventoryLength, inventory.size)
assertEquals(iBuffer.limit, inventory.size)
for (i in inventory.indices) {
assertEquals(inventory[i], monsterObject.inventory(i))
assertEquals(inventory[i], iBuffer.getUByte(i))
}
}
@Test
fun testCreateUninitializedVector() {
val fbb = FlatBufferBuilder(16)
val str = fbb.createString("MyMonster")
val inventory = byteArrayOf(10, 11, 12, 13, 14)
val uninitializedBuffer = fbb.createUnintializedVector(1, inventory.size, 1)
for (i in inventory) {
uninitializedBuffer.put(i)
}
val vec = fbb.endVector<UByte>()
Monster.startMonster(fbb)
Monster.addInventory(fbb, vec)
Monster.addName(fbb, str)
val monster1 = Monster.endMonster(fbb)
Monster.finishMonsterBuffer(fbb, monster1)
val monsterObject: Monster = Monster.asRoot(fbb.dataBuffer())
assertEquals(inventory[1].toUByte(), monsterObject.inventory(1))
assertEquals(inventory.size, monsterObject.inventoryLength)
val inventoryBuffer = monsterObject.inventoryAsBuffer()
assertEquals(inventory[1].toInt().toUByte(), inventoryBuffer.getUByte(1))
assertEquals(inventory.size, inventoryBuffer.limit)
}
@Test
fun testBuilderBasics() {
val fbb = FlatBufferBuilder()
val names = arrayOf(fbb.createString("Frodo"), fbb.createString("Barney"), fbb.createString("Wilma"))
val off = Array<Offset<Monster>>(3) { Offset(0) }
Monster.startMonster(fbb)
Monster.addName(fbb, names[0])
off[0] = Monster.endMonster(fbb)
Monster.startMonster(fbb)
Monster.addName(fbb, names[1])
off[1] = Monster.endMonster(fbb)
Monster.startMonster(fbb)
Monster.addName(fbb, names[2])
off[2] = Monster.endMonster(fbb)
val sortMons = fbb.createSortedVectorOfTables(Monster(), off)
// We set up the same values as monsterdata.json:
val inv = Monster.createInventoryVector(fbb, byteArrayOf(0,1,2,3,4).toUByteArray())
val fred = fbb.createString("Fred")
Monster.startMonster(fbb)
Monster.addName(fbb, fred)
val mon2 = Monster.endMonster(fbb)
Monster.startTest4Vector(fbb, 2)
createTest(fbb, 10.toShort(), 20.toByte())
createTest(fbb, 30.toShort(), 40.toByte())
val test4 = fbb.endVector<myGame.example.Test>()
val strings = StringOffsetArray(2) { fbb.createString("test$it") }
val testArrayOfString =
Monster.createTestarrayofstringVector(fbb, strings)
Monster.startMonster(fbb)
Monster.addName(fbb, names[0])
Monster.addPos(fbb, Vec3.createVec3(
fbb, 1.0f, 2.0f, 3.0f, 3.0,
Color.Green, 5.toShort(), 6.toByte()
))
Monster.addHp(fbb, 80)
Monster.addMana(fbb, 150)
Monster.addInventory(fbb, inv)
Monster.addTestType(fbb, AnyE.Monster)
Monster.addTest(fbb, mon2.toUnion())
Monster.addTest4(fbb, test4)
Monster.addTestarrayofstring(fbb, testArrayOfString)
Monster.addTestbool(fbb, true)
Monster.addTesthashu32Fnv1(fbb, (Int.MAX_VALUE + 1L).toUInt())
Monster.addTestarrayoftables(fbb, sortMons)
val mon = Monster.endMonster(fbb)
Monster.finishMonsterBuffer(fbb, mon)
//Attempt to mutate Monster fields and check whether the buffer has been mutated properly
// revert to original values after testing
val monster = Monster.asRoot(fbb.dataBuffer())
// mana is optional and does not exist in the buffer so the mutation should fail
// the mana field should retain its default value
assertEquals(monster.mana, 150.toShort())
assertEquals(monster.hp, 80)
// Accessing a vector of sorted by the key tables
assertEquals(monster.testarrayoftables(0)!!.name, "Barney")
assertEquals(monster.testarrayoftables(1)!!.name, "Frodo")
assertEquals(monster.testarrayoftables(2)!!.name, "Wilma")
// Example of searching for a table by the key
assertEquals(monster.testarrayoftablesByKey("Frodo")!!.name, "Frodo")
assertEquals(monster.testarrayoftablesByKey("Barney")!!.name, "Barney")
assertEquals(monster.testarrayoftablesByKey("Wilma")!!.name, "Wilma")
for (i in 0 until monster.inventoryLength) {
assertEquals(monster.inventory(i), (i).toUByte())
}
// get a struct field and edit one of its fields
val pos2 = monster.pos!!
assertEquals(pos2.x, 1.0f)
assertEquals(pos2.test2, Color.Green)
}
@Test
fun testVectorOfUnions() {
val fbb = FlatBufferBuilder()
val swordAttackDamage = 1
val attacker = Attacker.createAttacker(fbb, swordAttackDamage).toUnion()
val attackers = UnionOffsetArray(1) { attacker }
val characters = CharacterEArray(1)
characters[0] = CharacterE.MuLan.value
Movie.finishMovieBuffer(
fbb,
Movie.createMovie(
fbb,
CharacterE.MuLan,
attacker,
Movie.createCharactersTypeVector(fbb, characters),
Movie.createCharactersVector(fbb, attackers)
)
)
val movie: Movie = Movie.asRoot(fbb.dataBuffer())
assertEquals(movie.charactersTypeLength, 1)
assertEquals(movie.charactersLength, 1)
assertEquals(movie.charactersType(0), CharacterE.MuLan)
assertEquals((movie.characters(Attacker(), 0) as Attacker).swordAttackDamage, swordAttackDamage)
}
@Test
fun TestVectorOfBytes() {
val fbb = FlatBufferBuilder(16)
var str = fbb.createString("ByteMonster")
val data = ubyteArrayOf(0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u, 8u, 9u)
var offset = Monster.createInventoryVector(fbb, data)
Monster.startMonster(fbb)
Monster.addName(fbb, str)
Monster.addInventory(fbb, offset)
var monster1 = Monster.endMonster(fbb)
Monster.finishMonsterBuffer(fbb, monster1)
val monsterObject = Monster.asRoot(fbb.dataBuffer())
assertEquals("ByteMonster", monsterObject.name)
assertEquals(data.size, monsterObject.inventoryLength)
assertEquals(monsterObject.inventory(4), data[4])
offset = fbb.createByteVector(data.toByteArray()) as VectorOffset<UByte> // TODO: fix me
str = fbb.createString("ByteMonster")
Monster.startMonster(fbb)
Monster.addName(fbb, str)
Monster.addInventory(fbb, offset)
monster1 = Monster.endMonster(fbb)
Monster.finishMonsterBuffer(fbb, monster1)
val monsterObject2 = Monster.asRoot(fbb.dataBuffer())
assertEquals(monsterObject2.inventoryLength, data.size)
for (i in data.indices) {
assertEquals(monsterObject2.inventory(i), data[i])
}
fbb.clear()
offset = fbb.createByteVector(data.toByteArray(), 3, 4) as VectorOffset<UByte>
str = fbb.createString("ByteMonster")
Monster.startMonster(fbb)
Monster.addName(fbb, str)
Monster.addInventory(fbb, offset)
monster1 = Monster.endMonster(fbb)
Monster.finishMonsterBuffer(fbb, monster1)
val monsterObject3 = Monster.asRoot(fbb.dataBuffer())
assertEquals(monsterObject3.inventoryLength, 4)
assertEquals(monsterObject3.inventory(0), data[3])
fbb.clear()
offset = Monster.createInventoryVector(fbb, data)
str = fbb.createString("ByteMonster")
Monster.startMonster(fbb)
Monster.addName(fbb, str)
Monster.addInventory(fbb, offset)
monster1 = Monster.endMonster(fbb)
Monster.finishMonsterBuffer(fbb, monster1)
val monsterObject4 = Monster.asRoot(fbb.dataBuffer())
assertEquals(monsterObject4.inventoryLength, data.size)
assertEquals(monsterObject4.inventory(8), 8u)
fbb.clear()
val largeData = ByteArray(1024)
offset = fbb.createByteVector(largeData) as VectorOffset<UByte> //TODO: fix me
str = fbb.createString("ByteMonster")
Monster.startMonster(fbb)
Monster.addName(fbb, str)
Monster.addInventory(fbb, offset)
monster1 = Monster.endMonster(fbb)
Monster.finishMonsterBuffer(fbb, monster1)
val monsterObject5 = Monster.asRoot(fbb.dataBuffer())
assertEquals(monsterObject5.inventoryLength, largeData.size)
assertEquals(monsterObject5.inventory(25), largeData[25].toUByte())
fbb.clear()
var bb = ArrayReadBuffer(largeData, 512)
offset = fbb.createByteVector(bb) as VectorOffset<UByte> //TODO: fix me
str = fbb.createString("ByteMonster")
Monster.startMonster(fbb)
Monster.addName(fbb, str)
Monster.addInventory(fbb, offset)
monster1 = Monster.endMonster(fbb)
Monster.finishMonsterBuffer(fbb, monster1)
val monsterObject6 = Monster.asRoot(fbb.dataBuffer())
assertEquals(monsterObject6.inventoryLength, 512)
assertEquals(monsterObject6.inventory(0), largeData[0].toUByte())
fbb.clear()
bb = ArrayReadBuffer(largeData, largeData.size - 216)
val stringBuffer = ArrayReadBuffer("AlreadyBufferedString".encodeToByteArray())
offset = fbb.createByteVector(bb) as VectorOffset<UByte> //TODO: fix me
str = fbb.createString(stringBuffer)
Monster.startMonster(fbb)
Monster.addName(fbb, str)
Monster.addInventory(fbb, offset)
monster1 = Monster.endMonster(fbb)
Monster.finishMonsterBuffer(fbb, monster1)
val monsterObject7 = Monster.asRoot(fbb.dataBuffer())
assertEquals(monsterObject7.inventoryLength, 216)
assertEquals("AlreadyBufferedString", monsterObject7.name)
}
@Test
fun testEnums() {
assertEquals(Color.name(Color.Red), "Red")
assertEquals(Color.name(Color.Blue), "Blue")
assertEquals(AnyE.name(AnyE.None), "NONE")
assertEquals(AnyE.name(AnyE.Monster), "Monster")
}
@Test
fun testSharedStringPool() {
val fb = FlatBufferBuilder(1)
val testString = "My string"
val offset = fb.createSharedString(testString)
for (i in 0..9) {
assertEquals(offset, fb.createSharedString(testString))
}
}
@Test
fun testScalarOptional() {
val fbb = FlatBufferBuilder(1)
ScalarStuff.startScalarStuff(fbb)
var pos = ScalarStuff.endScalarStuff(fbb)
fbb.finish(pos)
var scalarStuff: ScalarStuff = ScalarStuff.asRoot(fbb.dataBuffer())
assertEquals(scalarStuff.justI8, 0.toByte())
assertEquals(scalarStuff.maybeI8, null)
assertEquals(scalarStuff.defaultI8, 42.toByte())
assertEquals(scalarStuff.justU8, 0u)
assertEquals(scalarStuff.maybeU8, null)
assertEquals(scalarStuff.defaultU8, 42u)
assertEquals(scalarStuff.justI16, 0.toShort())
assertEquals(scalarStuff.maybeI16, null)
assertEquals(scalarStuff.defaultI16, 42.toShort())
assertEquals(scalarStuff.justU16, 0u)
assertEquals(scalarStuff.maybeU16, null)
assertEquals(scalarStuff.defaultU16, 42u)
assertEquals(scalarStuff.justI32, 0)
assertEquals(scalarStuff.maybeI32, null)
assertEquals(scalarStuff.defaultI32, 42)
assertEquals(scalarStuff.justU32, 0u)
assertEquals(scalarStuff.maybeU32, null)
assertEquals(scalarStuff.defaultU32, 42u)
assertEquals(scalarStuff.justI64, 0L)
assertEquals(scalarStuff.maybeI64, null)
assertEquals(scalarStuff.defaultI64, 42L)
assertEquals(scalarStuff.justU64, 0UL)
assertEquals(scalarStuff.maybeU64, null)
assertEquals(scalarStuff.defaultU64, 42UL)
assertEquals(scalarStuff.justF32, 0.0f)
assertEquals(scalarStuff.maybeF32, null)
assertEquals(scalarStuff.defaultF32, 42.0f)
assertEquals(scalarStuff.justF64, 0.0)
assertEquals(scalarStuff.maybeF64, null)
assertEquals(scalarStuff.defaultF64, 42.0)
assertEquals(scalarStuff.justBool, false)
assertEquals(scalarStuff.maybeBool, null)
assertEquals(scalarStuff.defaultBool, true)
assertEquals(scalarStuff.justEnum, OptionalByte.None)
assertEquals(scalarStuff.maybeEnum, null)
assertEquals(scalarStuff.defaultEnum, OptionalByte.One)
fbb.clear()
ScalarStuff.startScalarStuff(fbb)
ScalarStuff.addJustI8(fbb, 5.toByte())
ScalarStuff.addMaybeI8(fbb, 5.toByte())
ScalarStuff.addDefaultI8(fbb, 5.toByte())
ScalarStuff.addJustU8(fbb, 6u)
ScalarStuff.addMaybeU8(fbb, 6u)
ScalarStuff.addDefaultU8(fbb, 6u)
ScalarStuff.addJustI16(fbb, 7.toShort())
ScalarStuff.addMaybeI16(fbb, 7.toShort())
ScalarStuff.addDefaultI16(fbb, 7.toShort())
ScalarStuff.addJustU16(fbb, 8u)
ScalarStuff.addMaybeU16(fbb, 8u)
ScalarStuff.addDefaultU16(fbb, 8u)
ScalarStuff.addJustI32(fbb, 9)
ScalarStuff.addMaybeI32(fbb, 9)
ScalarStuff.addDefaultI32(fbb, 9)
ScalarStuff.addJustU32(fbb, 10u)
ScalarStuff.addMaybeU32(fbb, 10u)
ScalarStuff.addDefaultU32(fbb, 10u)
ScalarStuff.addJustI64(fbb, 11L)
ScalarStuff.addMaybeI64(fbb, 11L)
ScalarStuff.addDefaultI64(fbb, 11L)
ScalarStuff.addJustU64(fbb, 12UL)
ScalarStuff.addMaybeU64(fbb, 12UL)
ScalarStuff.addDefaultU64(fbb, 12UL)
ScalarStuff.addJustF32(fbb, 13.0f)
ScalarStuff.addMaybeF32(fbb, 13.0f)
ScalarStuff.addDefaultF32(fbb, 13.0f)
ScalarStuff.addJustF64(fbb, 14.0)
ScalarStuff.addMaybeF64(fbb, 14.0)
ScalarStuff.addDefaultF64(fbb, 14.0)
ScalarStuff.addJustBool(fbb, true)
ScalarStuff.addMaybeBool(fbb, true)
ScalarStuff.addDefaultBool(fbb, true)
ScalarStuff.addJustEnum(fbb, OptionalByte.Two)
ScalarStuff.addMaybeEnum(fbb, OptionalByte.Two)
ScalarStuff.addDefaultEnum(fbb, OptionalByte.Two)
pos = ScalarStuff.endScalarStuff(fbb)
fbb.finish(pos)
scalarStuff = ScalarStuff.asRoot(fbb.dataBuffer())
assertEquals(scalarStuff.justI8, 5.toByte())
assertEquals(scalarStuff.maybeI8, 5.toByte())
assertEquals(scalarStuff.defaultI8, 5.toByte())
assertEquals(scalarStuff.justU8, 6u)
assertEquals(scalarStuff.maybeU8, 6u)
assertEquals(scalarStuff.defaultU8, 6u)
assertEquals(scalarStuff.justI16, 7.toShort())
assertEquals(scalarStuff.maybeI16, 7.toShort())
assertEquals(scalarStuff.defaultI16, 7.toShort())
assertEquals(scalarStuff.justU16, 8u)
assertEquals(scalarStuff.maybeU16, 8u)
assertEquals(scalarStuff.defaultU16, 8u)
assertEquals(scalarStuff.justI32, 9)
assertEquals(scalarStuff.maybeI32, 9)
assertEquals(scalarStuff.defaultI32, 9)
assertEquals(scalarStuff.justU32, 10u)
assertEquals(scalarStuff.maybeU32, 10u)
assertEquals(scalarStuff.defaultU32, 10u)
assertEquals(scalarStuff.justI64, 11L)
assertEquals(scalarStuff.maybeI64, 11L)
assertEquals(scalarStuff.defaultI64, 11L)
assertEquals(scalarStuff.justU64, 12UL)
assertEquals(scalarStuff.maybeU64, 12UL)
assertEquals(scalarStuff.defaultU64, 12UL)
assertEquals(scalarStuff.justF32, 13.0f)
assertEquals(scalarStuff.maybeF32, 13.0f)
assertEquals(scalarStuff.defaultF32, 13.0f)
assertEquals(scalarStuff.justF64, 14.0)
assertEquals(scalarStuff.maybeF64, 14.0)
assertEquals(scalarStuff.defaultF64, 14.0)
assertEquals(scalarStuff.justBool, true)
assertEquals(scalarStuff.maybeBool, true)
assertEquals(scalarStuff.defaultBool, true)
assertEquals(scalarStuff.justEnum, OptionalByte.Two)
assertEquals(scalarStuff.maybeEnum, OptionalByte.Two)
assertEquals(scalarStuff.defaultEnum, OptionalByte.Two)
}
// @todo Seems like nesting code generation is broken for all generators.
// disabling test for now.
// @Test
// fun testNamespaceNesting() {
// // reference / manipulate these to verify compilation
// val fbb = FlatBufferBuilder(1)
// TableInNestedNS.startTableInNestedNS(fbb)
// TableInNestedNS.addFoo(fbb, 1234)
// val nestedTableOff = TableInNestedNS.endTableInNestedNs(fbb)
// TableInFirstNS.startTableInFirstNS(fbb)
// TableInFirstNS.addFooTable(fbb, nestedTableOff)
// TableInFirstNS.endTableInFirstNs(fbb)
// }
@Test
fun testNestedFlatBuffer() {
val nestedMonsterName = "NestedMonsterName"
val nestedMonsterHp: Short = 600
val nestedMonsterMana: Short = 1024
val fbb1 = FlatBufferBuilder(16)
val str1 = fbb1.createString(nestedMonsterName)
Monster.startMonster(fbb1)
Monster.addName(fbb1, str1)
Monster.addHp(fbb1, nestedMonsterHp)
Monster.addMana(fbb1, nestedMonsterMana)
val monster1 = Monster.endMonster(fbb1)
Monster.finishMonsterBuffer(fbb1, monster1)
val fbb1Bytes: ByteArray = fbb1.sizedByteArray()
val fbb2 = FlatBufferBuilder(16)
val str2 = fbb2.createString("My Monster")
val nestedBuffer = Monster.createTestnestedflatbufferVector(fbb2, fbb1Bytes.toUByteArray())
Monster.startMonster(fbb2)
Monster.addName(fbb2, str2)
Monster.addHp(fbb2, 50.toShort())
Monster.addMana(fbb2, 32.toShort())
Monster.addTestnestedflatbuffer(fbb2, nestedBuffer)
val monster = Monster.endMonster(fbb2)
Monster.finishMonsterBuffer(fbb2, monster)
// Now test the data extracted from the nested buffer
val mons = Monster.asRoot(fbb2.dataBuffer())
val nestedMonster = mons.testnestedflatbufferAsMonster
assertEquals(nestedMonsterMana, nestedMonster!!.mana)
assertEquals(nestedMonsterHp, nestedMonster.hp)
assertEquals(nestedMonsterName, nestedMonster.name)
}
@Test
fun testDictionaryLookup() {
val fbb = FlatBufferBuilder(16)
val lfIndex = LongFloatEntry.createLongFloatEntry(fbb, 0, 99.0f)
val vectorEntriesIdx = LongFloatMap.createEntriesVector(fbb, LongFloatEntryOffsetArray(1) { lfIndex })
val rootIdx = LongFloatMap.createLongFloatMap(fbb, vectorEntriesIdx)
LongFloatMap.finishLongFloatMapBuffer(fbb, rootIdx)
val map: LongFloatMap = LongFloatMap.asRoot(fbb.dataBuffer())
assertEquals(1, map.entriesLength)
val e: LongFloatEntry = map.entries(0)!!
assertEquals(0L, e.key)
assertEquals(99.0f, e.value)
val e2: LongFloatEntry = map.entriesByKey(0)!!
assertEquals(0L, e2.key)
assertEquals(99.0f, e2.value)
}
}
@@ -0,0 +1,302 @@
/*
* 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.
*/
package com.google.flatbuffers.kotlin
import com.google.flatbuffers.kotlin.FlexBuffersBuilder.Companion.SHARE_NONE
import kotlin.random.Random
import kotlin.test.Test
import kotlin.test.assertEquals
class FlexBuffersTest {
@Test
fun testWriteInt() {
val values = listOf(
Byte.MAX_VALUE.toLong() to 3,
Short.MAX_VALUE.toLong() to 4,
Int.MAX_VALUE.toLong() to 6,
Long.MAX_VALUE to 10
)
val builder = FlexBuffersBuilder()
values.forEach {
builder.clear()
builder.put(it.first)
val data = builder.finish()
val ref = getRoot(data)
// although we put a long, it is shrink to a byte
assertEquals(it.second, data.limit)
assertEquals(it.first, ref.toLong())
}
}
@Test
fun testWriteUInt() {
val values = listOf(
UByte.MAX_VALUE.toULong() to 3,
UShort.MAX_VALUE.toULong() to 4,
UInt.MAX_VALUE.toULong() to 6,
ULong.MAX_VALUE to 10
)
val builder = FlexBuffersBuilder()
values.forEach {
builder.clear()
builder.put(it.first)
val data = builder.finish()
val ref = getRoot(data)
// although we put a long, it is shrink to a byte
assertEquals(it.second, data.limit)
assertEquals(it.first, ref.toULong())
}
}
@Test
fun testWriteString() {
val text = "Ḧ̵̘́ȩ̵̐l̶̿͜l̶͚͝o̷̦̚ ̷̫̊w̴̤͊ö̸̞́r̴͎̾l̷͚̐d̶̰̍"
val builder = FlexBuffersBuilder()
builder.put(text)
val data = builder.finish()
val ref = getRoot(data)
assertEquals(text, ref.toString())
}
@Test
fun testInt8Array() {
val ary = intArrayOf(1, 2, 3, 4)
val builder = FlexBuffersBuilder()
builder.put(intArrayOf(1, 2, 3, 4))
val data = builder.finish()
val ref = getRoot(data)
// although we put a long, it is shrink to a byte
assertEquals(8, data.limit)
assertArrayEquals(ary, ref.toIntArray())
}
@Test
fun testShortArray() {
val builder = FlexBuffersBuilder(ArrayReadWriteBuffer(20))
val numbers = ShortArray(10) { it.toShort() }
builder.put(numbers)
val data = builder.finish()
val ref = getRoot(data)
assertArrayEquals(numbers, ref.toShortArray())
}
@Test
fun testHugeArray() {
val builder = FlexBuffersBuilder()
val numbers = IntArray(1024) { it }
builder.put(numbers)
val data = builder.finish()
val ref = getRoot(data)
assertArrayEquals(numbers, ref.toIntArray())
}
@Test
fun testFloatArray() {
val builder = FlexBuffersBuilder()
val numbers = FloatArray(1024) { it * 0.05f }
builder.put(numbers)
val data = builder.finish()
val ref = getRoot(data)
assertArrayEquals(numbers, ref.toFloatArray())
}
@Test
fun testDoubleArray() {
val builder = FlexBuffersBuilder()
val numbers = DoubleArray(1024) { it * 0.0005 }
builder.put(numbers)
val data = builder.finish()
val ref = getRoot(data)
assertArrayEquals(numbers, ref.toDoubleArray())
}
@Test
fun testLongArray() {
val ary: LongArray = longArrayOf(0, Short.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong(), Long.MAX_VALUE)
val builder = FlexBuffersBuilder()
builder.put(ary)
val data = builder.finish()
val ref = getRoot(data)
// although we put a long, it is shrink to a byte
assertArrayEquals(ary, ref.toLongArray())
}
@Test
fun testStringArray() {
val ary = Array(5) { "Hello world number: $it" }
val builder = FlexBuffersBuilder(ArrayReadWriteBuffer(20), SHARE_NONE)
builder.putVector {
ary.forEach { put(it) }
}
val data = builder.finish()
val vec = getRoot(data).toVector()
// although we put a long, it is shrink to a byte
assertEquals(5, vec.size)
val stringAry = vec.map { it.toString() }.toTypedArray()
// although we put a long, it is shrink to a byte
assertArrayEquals(ary, stringAry)
}
@Test
fun testBlobArray() {
val ary = ByteArray(1000) { Random.nextInt().toByte() }
val builder = FlexBuffersBuilder()
builder.put(ary)
val data = builder.finish()
val blob = getRoot(data).toBlob()
// although we put a long, it is shrink to a byte
assertArrayEquals(ary, blob.toByteArray())
for (i in 0 until blob.size) {
assertEquals(ary[i], blob[i])
}
}
@Test
fun testArrays() {
val builder = FlexBuffersBuilder()
val ary: Array<String> = Array(5) { "Hello world number: $it" }
val numbers = IntArray(10) { it }
val doubles = DoubleArray(10) { it * 0.35 }
// add 3 level array of arrays in the following way
// [ [ "..", ...] [ "..", ..., [ "..", ...] ] ]
val vec = builder.startVector()
// [0, 1, 2, 3 ,4 ,5 ,6 ,7 ,8, 9]
val vec1 = builder.startVector()
numbers.forEach { builder.put(it) }
builder.endTypedVector(vec1)
// [0, 2, 4, 6 , 8, 10, 12, 14, 16, 18]
builder.putTypedVector { doubles.forEach { put(it) } }
// nested array
// [ "He..", "He..", "He..", "He..", "He..", [ "He..", "He..", "He..", "He..", "He.." ] ]
val vec3 = builder.startVector()
ary.forEach { builder.put(it) }
builder.putVector { ary.forEach { put("inner: $it") } }
builder.endVector(vec3)
builder.endVector(vec)
val data = builder.finish()
val ref = getRoot(data)
val vecRef = getRoot(data).toVector()
// although we put a long, it is shrink to a byte
assertEquals(3, vecRef.size)
assertArrayEquals(numbers, vecRef[0].toVector().map { it.toInt() }.toIntArray())
assertArrayEquals(doubles, ref[1].toDoubleArray())
assertEquals("Hello world number: 4", vecRef[2][4].toString())
assertEquals("inner: Hello world number: 4", vecRef[2][5][4].toString())
assertEquals("inner: Hello world number: 4", ref[2][5][4].toString())
}
@Test
fun testMap() {
val builder = FlexBuffersBuilder(shareFlag = FlexBuffersBuilder.SHARE_KEYS_AND_STRINGS)
builder.putVector {
put(10)
putMap {
this["chello"] = "world"
this["aint"] = 10
this["bfloat"] = 12.3
}
put("aString")
}
val ref = getRoot(builder.finish())
val map = ref.toVector()
assertEquals(3, map.size)
assertEquals(10, map[0].toInt())
assertEquals("aString", map[2].toString())
assertEquals("world", map[1]["chello"].toString())
assertEquals(10, map[1]["aint"].toInt())
assertEquals(12.3, map[1]["bfloat"].toDouble())
}
@Test
fun testMultiMap() {
val builder = FlexBuffersBuilder(shareFlag = FlexBuffersBuilder.SHARE_KEYS_AND_STRINGS)
builder.putMap {
this["hello"] = "world"
this["int"] = 10
this["float"] = 12.3
this["intarray"] = intArrayOf(1, 2, 3, 4, 5)
this.putMap("myMap") {
this["cool"] = "beans"
}
}
val ref = getRoot(builder.finish())
val map = ref.toMap()
assertEquals(5, map.size)
assertEquals("world", map["hello"].toString())
assertEquals(10, map["int"].toInt())
assertEquals(12.3, map["float"].toDouble())
assertArrayEquals(intArrayOf(1, 2, 3, 4, 5), map["intarray"].toIntArray())
assertEquals("beans", ref["myMap"]["cool"].toString())
assertEquals(true, "myMap" in map)
assertEquals(true, "cool" in map["myMap"].toMap())
// testing null values
assertEquals(true, ref["invalid_key"].isNull)
val keys = map.keys.toTypedArray()
arrayOf("hello", "int", "float", "intarray", "myMap").sortedArray().forEachIndexed { i: Int, it: String ->
assertEquals(it, keys[i].toString())
}
}
@Test
fun testBigStringMap() {
val builder = FlexBuffersBuilder(shareFlag = FlexBuffersBuilder.SHARE_KEYS_AND_STRINGS)
val stringKey = Array(10000) { "Ḧ̵̘́ȩ̵̐myFairlyBigKey$it" }
val stringValue = Array(10000) { "Ḧ̵̘́ȩ̵̐myFairlyBigValue$it" }
val hashMap = mutableMapOf<String, String>()
val pos = builder.startMap()
for (i in stringKey.indices) {
builder[stringKey[i]] = stringValue[i]
hashMap[stringKey[i]] = stringValue[i]
}
builder.endMap(pos)
val ref = getRoot(builder.finish())
val map = ref.toMap()
val sortedKeys = stringKey.sortedArray()
val size = map.size
for (i in 0 until size) {
assertEquals(sortedKeys[i], map.keyAsString(i))
assertEquals(sortedKeys[i], map.keyAt(i).toString())
assertEquals(hashMap[sortedKeys[i]], map[map.keyAt(i)].toString())
}
}
@Test
fun testKeysAccess() {
for (i in 1 until 1000) {
val utf8String = "ሰማይ አይታረስ ንጉሥ አይከሰስ።$i"
val bytes = ByteArray(Utf8.encodedLength(utf8String))
val pos = Utf8.encodeUtf8Array(utf8String, bytes)
val key = Key(ArrayReadWriteBuffer(bytes), 0, pos)
assertEquals(utf8String.length, key.sizeInChars)
for (j in utf8String.indices) {
assertEquals(utf8String[j], key[j])
}
}
}
}
@@ -0,0 +1,427 @@
/*
* 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.
*/
package com.google.flatbuffers.kotlin
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class JSONTest {
@Test
fun parse2Test() {
val dataStr = """
{ "myKey" : [1, "yay"] }
""".trimIndent()
val data = dataStr.encodeToByteArray()
val buffer = ArrayReadWriteBuffer(data, writePosition = data.size)
val parser = JSONParser()
val root = parser.parse(buffer)
println(root.toJson())
}
@Test
fun parseSample() {
val dataStr = """
{
"ary" : [1, 2, 3],
"boolean_false": false,
"boolean_true": true, "double": 1.2E33,
"hello":"world"
,"interesting": "value",
"null_value": null,
"object" : {
"field1": "hello"
}
}
"""
val data = dataStr.encodeToByteArray()
val root = JSONParser().parse(ArrayReadWriteBuffer(data, writePosition = data.size))
println(root.toJson())
val map = root.toMap()
assertEquals(8, map.size)
assertEquals("world", map["hello"].toString())
assertEquals("value", map["interesting"].toString())
assertEquals(12e32, map["double"].toDouble())
assertArrayEquals(intArrayOf(1, 2, 3), map["ary"].toIntArray())
assertEquals(true, map["boolean_true"].toBoolean())
assertEquals(false, map["boolean_false"].toBoolean())
assertEquals(true, map["null_value"].isNull)
assertEquals("hello", map["object"]["field1"].toString())
val obj = map["object"]
assertEquals(true, obj.isMap)
assertEquals("{\"field1\":\"hello\"}", obj.toJson())
// TODO: Kotlin Double.toString() produce different strings dependending on the platform, so on JVM
// is 1.2E33, while on js is 1.2e+33. For now we are disabling this test.
//
// val minified = data.filterNot { it == ' '.toByte() || it == '\n'.toByte() }.toByteArray().decodeToString()
// assertEquals(minified, root.toJson())
}
@Test
fun testDoubles() {
val values = arrayOf(
"-0.0",
"1.0",
"1.7976931348613157",
"0.0",
"-0.5",
"3.141592653589793",
"2.718281828459045E-3",
"2.2250738585072014E-308",
"4.9E-15",
)
val parser = JSONParser()
assertEquals(-0.0, parser.parse(values[0]).toDouble())
assertEquals(1.0, parser.parse(values[1]).toDouble())
assertEquals(1.7976931348613157, parser.parse(values[2]).toDouble())
assertEquals(0.0, parser.parse(values[3]).toDouble())
assertEquals(-0.5, parser.parse(values[4]).toDouble())
assertEquals(3.141592653589793, parser.parse(values[5]).toDouble())
assertEquals(2.718281828459045e-3, parser.parse(values[6]).toDouble())
assertEquals(2.2250738585072014E-308, parser.parse(values[7]).toDouble())
assertEquals(4.9E-15, parser.parse(values[8]).toDouble())
}
@Test
fun testInts() {
val values = arrayOf(
"-0",
"0",
"-1",
"${Int.MAX_VALUE}",
"${Int.MIN_VALUE}",
"${Long.MAX_VALUE}",
"${Long.MIN_VALUE}",
)
val parser = JSONParser()
assertEquals(parser.parse(values[0]).toInt(), 0)
assertEquals(parser.parse(values[1]).toInt(), 0)
assertEquals(parser.parse(values[2]).toInt(), -1)
assertEquals(parser.parse(values[3]).toInt(), Int.MAX_VALUE)
assertEquals(parser.parse(values[4]).toInt(), Int.MIN_VALUE)
assertEquals(parser.parse(values[5]).toLong(), Long.MAX_VALUE)
assertEquals(parser.parse(values[6]).toLong(), Long.MIN_VALUE)
}
@Test
fun testBooleansAndNull() {
val values = arrayOf(
"true",
"false",
"null"
)
val parser = JSONParser()
assertEquals(true, parser.parse(values[0]).toBoolean())
assertEquals(false, parser.parse(values[1]).toBoolean())
assertEquals(true, parser.parse(values[2]).isNull)
}
@Test
fun testStrings() {
val values = arrayOf(
"\"\"",
"\"a\"",
"\"hello world\"",
"\"\\\"\\\\\\/\\b\\f\\n\\r\\t cool\"",
"\"\\u0000\"",
"\"\\u0021\"",
"\"hell\\u24AC\\n\\ro wor \\u0021 ld\"",
"\"\\/_\\\\_\\\"_\\uCAFE\\uBABE\\uAB98\\uFCDE\\ubcda\\uef4A\\b\\n\\r\\t`1~!@#\$%^&*()_+-=[]{}|;:',./<>?\"",
)
val parser = JSONParser()
// empty
var ref = parser.parse(values[0])
assertEquals(true, ref.isString)
assertEquals("", ref.toString())
// a
ref = parser.parse(values[1])
assertEquals(true, ref.isString)
assertEquals("a", ref.toString())
// hello world
ref = parser.parse(values[2])
assertEquals(true, ref.isString)
assertEquals("hello world", ref.toString())
// "\\\"\\\\\\/\\b\\f\\n\\r\\t\""
ref = parser.parse(values[3])
assertEquals(true, ref.isString)
assertEquals("\"\\/\b${12.toChar()}\n\r\t cool", ref.toString())
// 0
ref = parser.parse(values[4])
assertEquals(true, ref.isString)
assertEquals(0.toChar().toString(), ref.toString())
// u0021
ref = parser.parse(values[5])
assertEquals(true, ref.isString)
assertEquals(0x21.toChar().toString(), ref.toString())
// "\"hell\\u24AC\\n\\ro wor \\u0021 ld\"",
ref = parser.parse(values[6])
assertEquals(true, ref.isString)
assertEquals("hell${0x24AC.toChar()}\n\ro wor ${0x21.toChar()} ld", ref.toString())
ref = parser.parse(values[7])
println(ref.toJson())
assertEquals(true, ref.isString)
assertEquals("/_\\_\"_쫾몾ꮘﳞ볚\b\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?", ref.toString())
}
@Test
fun testUnicode() {
// took from test/unicode_test.json
val data = """
{
"name": "unicode_test",
"testarrayofstring": [
"Цлїςσδε",
"フムアムカモケモ",
"フムヤムカモケモ",
"㊀㊁㊂㊃㊄",
"☳☶☲",
"𡇙𝌆"
],
"testarrayoftables": [
{
"name": "Цлїςσδε"
},
{
"name": "☳☶☲"
},
{
"name": "フムヤムカモケモ"
},
{
"name": "㊀㊁㊂㊃㊄"
},
{
"name": "フムアムカモケモ"
},
{
"name": "𡇙𝌆"
}
]
}
""".trimIndent()
val parser = JSONParser()
val ref = parser.parse(data)
// name
assertEquals(3, ref.toMap().size)
assertEquals("unicode_test", ref["name"].toString())
// testarrayofstring
assertEquals(6, ref["testarrayofstring"].toVector().size)
assertEquals("Цлїςσδε", ref["testarrayofstring"][0].toString())
assertEquals("フムアムカモケモ", ref["testarrayofstring"][1].toString())
assertEquals("フムヤムカモケモ", ref["testarrayofstring"][2].toString())
assertEquals("㊀㊁㊂㊃㊄", ref["testarrayofstring"][3].toString())
assertEquals("☳☶☲", ref["testarrayofstring"][4].toString())
assertEquals("𡇙𝌆", ref["testarrayofstring"][5].toString())
// testarrayoftables
assertEquals(6, ref["testarrayoftables"].toVector().size)
assertEquals("Цлїςσδε", ref["testarrayoftables"][0]["name"].toString())
assertEquals("☳☶☲", ref["testarrayoftables"][1]["name"].toString())
assertEquals("フムヤムカモケモ", ref["testarrayoftables"][2]["name"].toString())
assertEquals("㊀㊁㊂㊃㊄", ref["testarrayoftables"][3]["name"].toString())
assertEquals("フムアムカモケモ", ref["testarrayoftables"][4]["name"].toString())
assertEquals("𡇙𝌆", ref["testarrayoftables"][5]["name"].toString())
}
@Test
fun testArrays() {
val values = arrayOf(
"[]",
"[1]",
"[0,1, 2,3 , 4 ]",
"[1.0, 2.2250738585072014E-308, 4.9E-320]",
"[1.0, 2, \"hello world\"] ",
"[ 1.1, 2, [ \"hello\" ] ]",
"[[[1]]]"
)
val parser = JSONParser()
// empty
var ref = parser.parse(values[0])
assertEquals(true, ref.isVector)
assertEquals(0, parser.parse(values[0]).toVector().size)
// single
ref = parser.parse(values[1])
assertEquals(true, ref.isTypedVector)
assertEquals(1, ref[0].toInt())
// ints
ref = parser.parse(values[2])
assertEquals(true, ref.isTypedVector)
assertEquals(T_VECTOR_INT, ref.type)
assertEquals(5, ref.toVector().size)
for (i in 0..4) {
assertEquals(i, ref[i].toInt())
}
// floats
ref = parser.parse(values[3])
assertEquals(true, ref.isTypedVector)
assertEquals(T_VECTOR_FLOAT, ref.type)
assertEquals(3, ref.toVector().size)
assertEquals(1.0, ref[0].toDouble())
assertEquals(2.2250738585072014E-308, ref[1].toDouble())
assertEquals(4.9E-320, ref[2].toDouble())
// mixed
ref = parser.parse(values[4])
assertEquals(false, ref.isTypedVector)
assertEquals(T_VECTOR, ref.type)
assertEquals(1.0, ref[0].toDouble())
assertEquals(2, ref[1].toInt())
assertEquals("hello world", ref[2].toString())
// nester array
ref = parser.parse(values[5])
assertEquals(false, ref.isTypedVector)
assertEquals(T_VECTOR, ref.type)
assertEquals(1.1, ref[0].toDouble())
assertEquals(2, ref[1].toInt())
assertEquals("hello", ref[2][0].toString())
}
/**
* Several test cases provided by json.org
* For more details, see: http://json.org/JSON_checker/, with only
* one exception. Single strings are considered accepted, whereas on
* the test suit is should fail.
*/
@Test
fun testParseMustFail() {
val failList = listOf(
"[\"Unclosed array\"",
"{unquoted_key: \"keys must be quoted\"}",
"[\"extra comma\",]",
"[\"double extra comma\",,]",
"[ , \"<-- missing value\"]",
"[\"Comma after the close\"],",
"[\"Extra close\"]]",
"{\"Extra comma\": true,}",
"{\"Extra value after close\": true} \"misplaced quoted value\"",
"{\"Illegal expression\": 1 + 2}",
"{\"Illegal invocation\": alert()}",
"{\"Numbers cannot have leading zeroes\": 013}",
"{\"Numbers cannot be hex\": 0x14}",
"[\"Illegal backslash escape: \\x15\"]",
"[\\naked]",
"[\"Illegal backslash escape: \\017\"]",
"[[[[[[[[[[[[[[[[[[[[[[[\"Too deep\"]]]]]]]]]]]]]]]]]]]]]]]",
"{\"Missing colon\" null}",
"{\"Double colon\":: null}",
"{\"Comma instead of colon\", null}",
"[\"Colon instead of comma\": false]",
"[\"Bad value\", truth]",
"['single quote']",
"[\"\ttab\tcharacter\tin\tstring\t\"]",
"[\"tab\\ character\\ in\\ string\\ \"]",
"[\"line\nbreak\"]",
"[\"line\\\nbreak\"]",
"[0e]",
"[0e+]",
"[0e+-1]",
"{\"Comma instead if closing brace\": true,",
"[\"mismatch\"}"
)
for (data in failList) {
try {
JSONParser().parse(ArrayReadBuffer(data.encodeToByteArray()))
assertTrue(false, "SHOULD NOT PASS: $data")
} catch (e: IllegalStateException) {
println("FAIL $e")
}
}
}
@Test
fun testParseMustPass() {
val passList = listOf(
"[\n" +
" \"JSON Test Pattern pass1\",\n" +
" {\"object with 1 member\":[\"array with 1 element\"]},\n" +
" {},\n" +
" [],\n" +
" -42,\n" +
" true,\n" +
" false,\n" +
" null,\n" +
" {\n" +
" \"integer\": 1234567890,\n" +
" \"real\": -9876.543210,\n" +
" \"e\": 0.123456789e-12,\n" +
" \"E\": 1.234567890E+34,\n" +
" \"\": 23456789012E66,\n" +
" \"zero\": 0,\n" +
" \"one\": 1,\n" +
" \"space\": \" \",\n" +
" \"quote\": \"\\\"\",\n" +
" \"backslash\": \"\\\\\",\n" +
" \"controls\": \"\\b\\f\\n\\r\\t\",\n" +
" \"slash\": \"/ & \\/\",\n" +
" \"alpha\": \"abcdefghijklmnopqrstuvwyz\",\n" +
" \"ALPHA\": \"ABCDEFGHIJKLMNOPQRSTUVWYZ\",\n" +
" \"digit\": \"0123456789\",\n" +
" \"0123456789\": \"digit\",\n" +
" \"special\": \"`1~!@#\$%^&*()_+-={':[,]}|;.</>?\",\n" +
" \"hex\": \"\\u0123\\u4567\\u89AB\\uCDEF\\uabcd\\uef4A\",\n" +
" \"true\": true,\n" +
" \"false\": false,\n" +
" \"null\": null,\n" +
" \"array\":[ ],\n" +
" \"object\":{ },\n" +
" \"address\": \"50 St. James Street\",\n" +
" \"url\": \"http://www.JSON.org/\",\n" +
" \"comment\": \"// /* <!-- --\",\n" +
" \"# -- --> */\": \" \",\n" +
" \" s p a c e d \" :[1,2 , 3\n" +
"\n" +
",\n" +
"\n" +
"4 , 5 , 6 ,7 ],\"compact\":[1,2,3,4,5,6,7],\n" +
" \"jsontext\": \"{\\\"object with 1 member\\\":[\\\"array with 1 element\\\"]}\",\n" +
" \"quotes\": \"&#34; \\u0022 %22 0x22 034 &#x22;\",\n" +
" \"\\/\\\\\\\"\\uCAFE\\uBABE\\uAB98\\uFCDE\\ubcda\\uef4A\\b\\f\\n\\r\\t`1~!@#\$%^&*()_+-=[]{}|;:',./<>?\"\n" +
": \"A key can be any string\"\n" +
" },\n" +
" 0.5 ,98.6\n" +
",\n" +
"99.44\n" +
",\n" +
"\n" +
"1066,\n" +
"1e1,\n" +
"0.1e1,\n" +
"1e-1,\n" +
"1e00,2e+00,2e-00\n" +
",\"rosebud\"]",
"{\n" +
" \"JSON Test Pattern pass3\": {\n" +
" \"The outermost value\": \"must be an object or array.\",\n" +
" \"In this test\": \"It is an object.\"\n" +
" }\n" +
"}",
"[[[[[[[[[[[[[[[[[[[\"Not too deep\"]]]]]]]]]]]]]]]]]]]",
)
for (data in passList) {
JSONParser().parse(ArrayReadBuffer(data.encodeToByteArray()))
}
}
}
@@ -0,0 +1,41 @@
/*
* 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
/**
* This implementation uses Little Endian order.
*/
public actual inline fun ByteArray.getUByte(index: Int): UByte = ByteArrayOps.getUByte(this, index)
public actual inline fun ByteArray.getShort(index: Int): Short = ByteArrayOps.getShort(this, index)
public actual inline fun ByteArray.getUShort(index: Int): UShort = ByteArrayOps.getUShort(this, index)
public actual inline fun ByteArray.getInt(index: Int): Int = ByteArrayOps.getInt(this, index)
public actual inline fun ByteArray.getUInt(index: Int): UInt = ByteArrayOps.getUInt(this, index)
public actual inline fun ByteArray.getLong(index: Int): Long = ByteArrayOps.getLong(this, index)
public actual inline fun ByteArray.getULong(index: Int): ULong = ByteArrayOps.getULong(this, index)
public actual inline fun ByteArray.getFloat(index: Int): Float = ByteArrayOps.getFloat(this, index)
public actual inline fun ByteArray.getDouble(index: Int): Double = ByteArrayOps.getDouble(this, index)
public actual inline fun ByteArray.setUByte(index: Int, value: UByte): Unit = ByteArrayOps.setUByte(this, index, value)
public actual inline fun ByteArray.setShort(index: Int, value: Short): Unit = ByteArrayOps.setShort(this, index, value)
public actual inline fun ByteArray.setUShort(index: Int, value: UShort): Unit = ByteArrayOps.setUShort(this, index, value)
public actual inline fun ByteArray.setInt(index: Int, value: Int): Unit = ByteArrayOps.setInt(this, index, value)
public actual inline fun ByteArray.setUInt(index: Int, value: UInt): Unit = ByteArrayOps.setUInt(this, index, value)
public actual inline fun ByteArray.setLong(index: Int, value: Long): Unit = ByteArrayOps.setLong(this, index, value)
public actual inline fun ByteArray.setULong(index: Int, value: ULong): Unit = ByteArrayOps.setULong(this, index, value)
public actual inline fun ByteArray.setFloat(index: Int, value: Float): Unit = ByteArrayOps.setFloat(this, index, value)
public actual inline fun ByteArray.setDouble(index: Int, value: Double): Unit = ByteArrayOps.setDouble(this, index, value)
@@ -0,0 +1,43 @@
/*
* 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:JvmName("JVMByteArray")
@file:Suppress("NOTHING_TO_INLINE")
package com.google.flatbuffers.kotlin
/**
* This implementation uses Little Endian order.
*/
public actual inline fun ByteArray.getUByte(index: Int): UByte = ByteArrayOps.getUByte(this, index)
public actual inline fun ByteArray.getShort(index: Int): Short = ByteArrayOps.getShort(this, index)
public actual inline fun ByteArray.getUShort(index: Int): UShort = ByteArrayOps.getUShort(this, index)
public actual inline fun ByteArray.getInt(index: Int): Int = ByteArrayOps.getInt(this, index)
public actual inline fun ByteArray.getUInt(index: Int): UInt = ByteArrayOps.getUInt(this, index)
public actual inline fun ByteArray.getLong(index: Int): Long = ByteArrayOps.getLong(this, index)
public actual inline fun ByteArray.getULong(index: Int): ULong = ByteArrayOps.getULong(this, index)
public actual inline fun ByteArray.getFloat(index: Int): Float = ByteArrayOps.getFloat(this, index)
public actual inline fun ByteArray.getDouble(index: Int): Double = ByteArrayOps.getDouble(this, index)
public actual inline fun ByteArray.setUByte(index: Int, value: UByte): Unit = ByteArrayOps.setUByte(this, index, value)
public actual inline fun ByteArray.setShort(index: Int, value: Short): Unit = ByteArrayOps.setShort(this, index, value)
public actual inline fun ByteArray.setUShort(index: Int, value: UShort): Unit = ByteArrayOps.setUShort(this, index, value)
public actual inline fun ByteArray.setInt(index: Int, value: Int): Unit = ByteArrayOps.setInt(this, index, value)
public actual inline fun ByteArray.setUInt(index: Int, value: UInt): Unit = ByteArrayOps.setUInt(this, index, value)
public actual inline fun ByteArray.setLong(index: Int, value: Long): Unit = ByteArrayOps.setLong(this, index, value)
public actual inline fun ByteArray.setULong(index: Int, value: ULong): Unit = ByteArrayOps.setULong(this, index, value)
public actual inline fun ByteArray.setFloat(index: Int, value: Float): Unit = ByteArrayOps.setFloat(this, index, value)
public actual inline fun ByteArray.setDouble(index: Int, value: Double): Unit = ByteArrayOps.setDouble(this, index, value)
@@ -0,0 +1,40 @@
/*
* 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.
*/
package com.google.flatbuffers.kotlin
import org.junit.Test
import kotlin.test.assertEquals
class Utf8Test {
@Test
fun testUtf8EncodingDecoding() {
val classLoader = this.javaClass.classLoader
val utf8Lines = String(classLoader.getResourceAsStream("utf8_sample.txt")!!.readBytes())
.split("\n")
.filter { it.trim().isNotEmpty() }
val utf8Bytes = utf8Lines.map {
s -> ByteArray(Utf8.encodedLength(s)).also {
Utf8.encodeUtf8Array(s, it)
}
}
utf8Bytes.indices.forEach {
assertArrayEquals(utf8Lines[it].encodeToByteArray(), utf8Bytes[it])
assertEquals(utf8Lines[it], Utf8.decodeUtf8Array(utf8Bytes[it]))
}
}
}
@@ -0,0 +1,201 @@
Markus Kuhn <http://www.cl.cam.ac.uk/~mgk25/> - 2015-08-28 - CC BY 4.0
UTF-8 encoded sample plain-text file
‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Markus Kuhn [ˈmaʳkʊs kuːn] <mkuhn@acm.org> — 1999-08-20
The ASCII compatible UTF-8 encoding of ISO 10646 and Unicode
plain-text files is defined in RFC 2279 and in ISO 10646-1 Annex R.
Using Unicode/UTF-8, you can write in emails and source code things such as
Mathematics and Sciences:
∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ∀x∈ℝ: ⌈x⌉ = −⌊−x⌋, α ∧ ¬β = ¬(¬α ∨ β),
ℕ ⊆ ℕ₀ ⊂ ℤ ⊂ ℚ ⊂ ℝ ⊂ ℂ, ⊥ < a ≠ b ≡ c ≤ d ≪ ⇒ (A ⇔ B),
2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm
Linguistics and dictionaries:
ði ıntəˈnæʃənəl fəˈnɛtık əsoʊsiˈeıʃn
Y [ˈʏpsilɔn], Yen [jɛn], Yoga [ˈjoːgɑ]
APL:
((VV)=⍳⍴V)/V←,V ⌷←⍳→⍴∆∇⊃‾⍎⍕⌈
Nicer typography in plain text files:
╔══════════════════════════════════════════╗
║ ║
║ • single and “double” quotes ║
║ ║
║ • Curly apostrophes: “Weve been here” ║
║ ║
║ • Latin-1 apostrophe and accents: '´` ║
║ ║
║ • deutsche „Anführungszeichen“ ║
║ ║
║ • †, ‡, ‰, •, 34, —, 5/+5, ™, … ║
║ ║
║ • ASCII safety test: 1lI|, 0OD, 8B ║
║ ╭─────────╮ ║
║ • the euro symbol: │ 14.95 € │ ║
║ ╰─────────╯ ║
╚══════════════════════════════════════════╝
Greek (in Polytonic):
The Greek anthem:
Σὲ γνωρίζω ἀπὸ τὴν κόψη
τοῦ σπαθιοῦ τὴν τρομερή,
σὲ γνωρίζω ἀπὸ τὴν ὄψη
ποὺ μὲ βία μετράει τὴ γῆ.
᾿Απ᾿ τὰ κόκκαλα βγαλμένη
τῶν ῾Ελλήνων τὰ ἱερά
καὶ σὰν πρῶτα ἀνδρειωμένη
χαῖρε, ὦ χαῖρε, ᾿Ελευθεριά!
From a speech of Demosthenes in the 4th century BC:
Οὐχὶ ταὐτὰ παρίσταταί μοι γιγνώσκειν, ὦ ἄνδρες ᾿Αθηναῖοι,
ὅταν τ᾿ εἰς τὰ πράγματα ἀποβλέψω καὶ ὅταν πρὸς τοὺς
λόγους οὓς ἀκούω· τοὺς μὲν γὰρ λόγους περὶ τοῦ
τιμωρήσασθαι Φίλιππον ὁρῶ γιγνομένους, τὰ δὲ πράγματ᾿
εἰς τοῦτο προήκοντα, ὥσθ᾿ ὅπως μὴ πεισόμεθ᾿ αὐτοὶ
πρότερον κακῶς σκέψασθαι δέον. οὐδέν οὖν ἄλλο μοι δοκοῦσιν
οἱ τὰ τοιαῦτα λέγοντες ἢ τὴν ὑπόθεσιν, περὶ ἧς βουλεύεσθαι,
οὐχὶ τὴν οὖσαν παριστάντες ὑμῖν ἁμαρτάνειν. ἐγὼ δέ, ὅτι μέν
ποτ᾿ ἐξῆν τῇ πόλει καὶ τὰ αὑτῆς ἔχειν ἀσφαλῶς καὶ Φίλιππον
τιμωρήσασθαι, καὶ μάλ᾿ ἀκριβῶς οἶδα· ἐπ᾿ ἐμοῦ γάρ, οὐ πάλαι
γέγονεν ταῦτ᾿ ἀμφότερα· νῦν μέντοι πέπεισμαι τοῦθ᾿ ἱκανὸν
προλαβεῖν ἡμῖν εἶναι τὴν πρώτην, ὅπως τοὺς συμμάχους
σώσομεν. ἐὰν γὰρ τοῦτο βεβαίως ὑπάρξῃ, τότε καὶ περὶ τοῦ
τίνα τιμωρήσεταί τις καὶ ὃν τρόπον ἐξέσται σκοπεῖν· πρὶν δὲ
τὴν ἀρχὴν ὀρθῶς ὑποθέσθαι, μάταιον ἡγοῦμαι περὶ τῆς
τελευτῆς ὁντινοῦν ποιεῖσθαι λόγον.
Δημοσθένους, Γ´ ᾿Ολυνθιακὸς
Georgian:
From a Unicode conference invitation:
გთხოვთ ახლავე გაიაროთ რეგისტრაცია Unicode-ის მეათე საერთაშორისო
კონფერენციაზე დასასწრებად, რომელიც გაიმართება 10-12 მარტს,
ქ. მაინცში, გერმანიაში. კონფერენცია შეჰკრებს ერთად მსოფლიოს
ექსპერტებს ისეთ დარგებში როგორიცაა ინტერნეტი და Unicode-ი,
ინტერნაციონალიზაცია და ლოკალიზაცია, Unicode-ის გამოყენება
ოპერაციულ სისტემებსა, და გამოყენებით პროგრამებში, შრიფტებში,
ტექსტების დამუშავებასა და მრავალენოვან კომპიუტერულ სისტემებში.
Russian:
From a Unicode conference invitation:
Зарегистрируйтесь сейчас на Десятую Международную Конференцию по
Unicode, которая состоится 10-12 марта 1997 года в Майнце в Германии.
Конференция соберет широкий круг экспертов по вопросам глобального
Интернета и Unicode, локализации и интернационализации, воплощению и
применению Unicode в различных операционных системах и программных
приложениях, шрифтах, верстке и многоязычных компьютерных системах.
Thai (UCS Level 2):
Excerpt from a poetry on The Romance of The Three Kingdoms (a Chinese
classic 'San Gua'):
[----------------------------|------------------------]
๏ แผ่นดินฮั่นเสื่อมโทรมแสนสังเวช พระปกเกศกองบู๊กู้ขึ้นใหม่
สิบสองกษัตริย์ก่อนหน้าแลถัดไป สององค์ไซร้โง่เขลาเบาปัญญา
ทรงนับถือขันทีเป็นที่พึ่ง บ้านเมืองจึงวิปริตเป็นนักหนา
โฮจิ๋นเรียกทัพทั่วหัวเมืองมา หมายจะฆ่ามดชั่วตัวสำคัญ
เหมือนขับไสไล่เสือจากเคหา รับหมาป่าเข้ามาเลยอาสัญ
ฝ่ายอ้องอุ้นยุแยกให้แตกกัน ใช้สาวนั้นเป็นชนวนชื่นชวนใจ
พลันลิฉุยกุยกีกลับก่อเหตุ ช่างอาเพศจริงหนาฟ้าร้องไห้
ต้องรบราฆ่าฟันจนบรรลัย ฤๅหาใครค้ำชูกู้บรรลังก์ ฯ
(The above is a two-column text. If combining characters are handled
correctly, the lines of the second column should be aligned with the
| character above.)
Ethiopian:
Proverbs in the Amharic language:
ሰማይ አይታረስ ንጉሥ አይከሰስ።
ብላ ካለኝ እንደአባቴ በቆመጠኝ።
ጌጥ ያለቤቱ ቁምጥና ነው።
ደሀ በሕልሙ ቅቤ ባይጠጣ ንጣት በገደለው።
የአፍ ወለምታ በቅቤ አይታሽም።
አይጥ በበላ ዳዋ ተመታ።
ሲተረጉሙ ይደረግሙ።
ቀስ በቀስ፥ ዕንቁላል በእግሩ ይሄዳል።
ድር ቢያብር አንበሳ ያስር።
ሰው እንደቤቱ እንጅ እንደ ጉረቤቱ አይተዳደርም።
እግዜር የከፈተውን ጉሮሮ ሳይዘጋው አይድርም።
የጎረቤት ሌባ፥ ቢያዩት ይስቅ ባያዩት ያጠልቅ።
ሥራ ከመፍታት ልጄን ላፋታት።
ዓባይ ማደሪያ የለው፥ ግንድ ይዞ ይዞራል።
የእስላም አገሩ መካ የአሞራ አገሩ ዋርካ።
ተንጋሎ ቢተፉ ተመልሶ ባፉ።
ወዳጅህ ማር ቢሆን ጨርስህ አትላሰው።
እግርህን በፍራሽህ ልክ ዘርጋ።
Runes:
ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ
(Old English, which transcribed into Latin reads 'He cwaeth that he
bude thaem lande northweardum with tha Westsae.' and means 'He said
that he lived in the northern land near the Western Sea.')
Braille:
⡌⠁⠧⠑ ⠼⠁⠒ ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌
⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠙⠑⠁⠙⠒ ⠞⠕ ⠃⠑⠛⠔ ⠺⠊⠹⠲ ⡹⠻⠑ ⠊⠎ ⠝⠕ ⠙⠳⠃⠞
⠱⠁⠞⠑⠧⠻ ⠁⠃⠳⠞ ⠹⠁⠞⠲ ⡹⠑ ⠗⠑⠛⠊⠌⠻ ⠕⠋ ⠙⠊⠎ ⠃⠥⠗⠊⠁⠇ ⠺⠁⠎
⠎⠊⠛⠝⠫ ⠃⠹ ⠹⠑ ⠊⠇⠻⠛⠹⠍⠁⠝⠂ ⠹⠑ ⠊⠇⠻⠅⠂ ⠹⠑ ⠥⠝⠙⠻⠞⠁⠅⠻⠂
⠁⠝⠙ ⠹⠑ ⠡⠊⠑⠋ ⠍⠳⠗⠝⠻⠲ ⡎⠊⠗⠕⠕⠛⠑ ⠎⠊⠛⠝⠫ ⠊⠞⠲ ⡁⠝⠙
⡎⠊⠗⠕⠕⠛⠑⠰⠎ ⠝⠁⠍⠑ ⠺⠁⠎ ⠛⠕⠕⠙ ⠥⠏⠕⠝ ⠰⡡⠁⠝⠛⠑⠂ ⠋⠕⠗ ⠁⠝⠹⠹⠔⠛ ⠙⠑
⠡⠕⠎⠑ ⠞⠕ ⠏⠥⠞ ⠙⠊⠎ ⠙⠁⠝⠙ ⠞⠕⠲
⡕⠇⠙ ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠁⠎ ⠙⠑⠁⠙ ⠁⠎ ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲
⡍⠔⠙⠖ ⡊ ⠙⠕⠝⠰⠞ ⠍⠑⠁⠝ ⠞⠕ ⠎⠁⠹ ⠹⠁⠞ ⡊ ⠅⠝⠪⠂ ⠕⠋ ⠍⠹
⠪⠝ ⠅⠝⠪⠇⠫⠛⠑⠂ ⠱⠁⠞ ⠹⠻⠑ ⠊⠎ ⠏⠜⠞⠊⠊⠥⠇⠜⠇⠹ ⠙⠑⠁⠙ ⠁⠃⠳⠞
⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲ ⡊ ⠍⠊⠣⠞ ⠙⠁⠧⠑ ⠃⠑⠲ ⠔⠊⠇⠔⠫⠂ ⠍⠹⠎⠑⠇⠋⠂ ⠞⠕
⠗⠑⠛⠜⠙ ⠁ ⠊⠕⠋⠋⠔⠤⠝⠁⠊⠇ ⠁⠎ ⠹⠑ ⠙⠑⠁⠙⠑⠌ ⠏⠊⠑⠊⠑ ⠕⠋ ⠊⠗⠕⠝⠍⠕⠝⠛⠻⠹
⠔ ⠹⠑ ⠞⠗⠁⠙⠑⠲ ⡃⠥⠞ ⠹⠑ ⠺⠊⠎⠙⠕⠍ ⠕⠋ ⠳⠗ ⠁⠝⠊⠑⠌⠕⠗⠎
⠊⠎ ⠔ ⠹⠑ ⠎⠊⠍⠊⠇⠑⠆ ⠁⠝⠙ ⠍⠹ ⠥⠝⠙⠁⠇⠇⠪⠫ ⠙⠁⠝⠙⠎
⠩⠁⠇⠇ ⠝⠕⠞ ⠙⠊⠌⠥⠗⠃ ⠊⠞⠂ ⠕⠗ ⠹⠑ ⡊⠳⠝⠞⠗⠹⠰⠎ ⠙⠕⠝⠑ ⠋⠕⠗⠲ ⡹⠳
⠺⠊⠇⠇ ⠹⠻⠑⠋⠕⠗⠑ ⠏⠻⠍⠊⠞ ⠍⠑ ⠞⠕ ⠗⠑⠏⠑⠁⠞⠂ ⠑⠍⠏⠙⠁⠞⠊⠊⠁⠇⠇⠹⠂ ⠹⠁⠞
⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠁⠎ ⠙⠑⠁⠙ ⠁⠎ ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲
(The first couple of paragraphs of "A Christmas Carol" by Dickens)
Compact font selection example text:
ABCDEFGHIJKLMNOPQRSTUVWXYZ /0123456789
abcdefghijklmnopqrstuvwxyz £©µÀÆÖÞßéöÿ
–—‘“”„†•…‰™œŠŸž€ ΑΒΓΔΩαβγδω АБВГДабвгд
∀∂∈ℝ∧∪≡∞ ↑↗↨↻⇣ ┐┼╔╘░►☺♀ fi⑀₂ἠḂӥẄɐː⍎אԱა
Greetings in various languages:
Hello world, Καλημέρα κόσμε, コンニチハ
Box drawing alignment tests: █
╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳
║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳
║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳
╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳
║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎
║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏
╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█
@@ -0,0 +1,42 @@
/*
* 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
/**
* This implementation assumes that of native macOSX64 the byte order of the implementation is Little Endian.
*/
public actual inline fun ByteArray.getUByte(index: Int): UByte = getUByteAt(index)
public actual inline fun ByteArray.getShort(index: Int): Short = getShortAt(index)
public actual inline fun ByteArray.getUShort(index: Int): UShort = getUShortAt(index)
public actual inline fun ByteArray.getInt(index: Int): Int = getIntAt(index)
public actual inline fun ByteArray.getUInt(index: Int): UInt = getUIntAt(index)
public actual inline fun ByteArray.getLong(index: Int): Long = getLongAt(index)
public actual inline fun ByteArray.getULong(index: Int): ULong = getULongAt(index)
public actual inline fun ByteArray.setUByte(index: Int, value: UByte): Unit = setUByteAt(index, value)
public actual inline fun ByteArray.setShort(index: Int, value: Short): Unit = setShortAt(index, value)
public actual inline fun ByteArray.setUShort(index: Int, value: UShort): Unit = setUShortAt(index, value)
public actual inline fun ByteArray.setInt(index: Int, value: Int): Unit = setIntAt(index, value)
public actual inline fun ByteArray.setUInt(index: Int, value: UInt): Unit = setUIntAt(index, value)
public actual inline fun ByteArray.setLong(index: Int, value: Long): Unit = setLongAt(index, value)
public actual inline fun ByteArray.setULong(index: Int, value: ULong): Unit = setULongAt(index, value)
public actual inline fun ByteArray.setFloat(index: Int, value: Float): Unit = setFloatAt(index, value)
public actual inline fun ByteArray.setDouble(index: Int, value: Double): Unit = setDoubleAt(index, value)
public actual inline fun ByteArray.getFloat(index: Int): Float = Float.fromBits(getIntAt(index))
public actual inline fun ByteArray.getDouble(index: Int): Double = Double.fromBits(getLongAt(index))
+20
View File
@@ -0,0 +1,20 @@
#Gradle
group = "com.google.flatbuffers"
version = "2.0.0-SNAPSHOT"
org.gradle.parallel=true
org.gradle.caching=true
#Kotlin
kotlin.code.style=official
#MPP
kotlin.js.compiler=ir
kotlin.native.ignoreDisabledTargets=true
kotlin.mpp.stability.nowarn=true
kotlin.incremental.multiplatform=true
kotlin.native.binary.memoryModel=experimental
kotlin.native.distribution.type=prebuilt
org.gradle.jvmargs="-XX:+HeapDumpOnOutOfMemoryError"
@@ -0,0 +1,25 @@
[versions]
kotlin = "1.8.21"
plugin-kotlin = "1.6.10"
plugin-gver = "0.42.0"
kotlinx-benchmark = "0.4.8"
junit = "4.12"
gson = "2.8.5"
moshi-kotlin = "1.11.0"
[libraries]
kotlin-compiler = { module = "org.jetbrains.kotlin:kotlin-compiler", version.ref = "kotlin" }
moshi-kotlin = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi-kotlin" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
kotlinx-benchmark-runtime = { module = "org.jetbrains.kotlinx:kotlinx-benchmark-runtime", version.ref = "kotlinx-benchmark" }
junit = { module="junit:junit", version.ref="junit"}
kotlin-allopen = { module = "org.jetbrains.kotlin:kotlin-allopen", version.ref = "kotlin"}
plugin-kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
plugin-kotlinx-benchmark = { module="org.jetbrains.kotlinx:kotlinx-benchmark-plugin", version.ref="kotlinx-benchmark"}
plugin-jmhreport = { module = "gradle.plugin.io.morethan.jmhreport:gradle-jmh-report", version="0.9.0" }
plugin-download = { module = "de.undercouch:gradle-download-task", version = "5.3.0"}
Binary file not shown.
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+234
View File
@@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# 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
#
# https://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.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
+89
View File
@@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+3
View File
@@ -0,0 +1,3 @@
rootProject.name = "flatbuffers-kotlin"
include("flatbuffers-kotlin")
include("benchmark")
+15
View File
@@ -0,0 +1,15 @@
/*
* Copyright $YEAR 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.
*/