Austin Schuh | e89fa2d | 2019-08-14 20:24:23 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2014 Google Inc. All rights reserved. |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | import MyGame.Example.* |
| 18 | import com.google.flatbuffers.ByteBufferUtil |
| 19 | import com.google.flatbuffers.FlatBufferBuilder |
| 20 | import NamespaceA.* |
| 21 | import NamespaceA.NamespaceB.* |
| 22 | import NamespaceA.NamespaceB.TableInNestedNS |
| 23 | import java.io.File |
| 24 | import java.io.FileOutputStream |
| 25 | import java.io.InputStream |
| 26 | import java.io.RandomAccessFile |
| 27 | import java.nio.ByteBuffer |
| 28 | import java.nio.ByteOrder |
| 29 | import java.nio.channels.FileChannel |
| 30 | |
| 31 | import com.google.flatbuffers.Constants.SIZE_PREFIX_LENGTH |
| 32 | |
| 33 | @kotlin.ExperimentalUnsignedTypes |
| 34 | class KotlinTest { |
| 35 | |
| 36 | companion object { |
| 37 | @JvmStatic |
| 38 | fun main(args: Array<String>) { |
| 39 | |
| 40 | // First, let's test reading a FlatBuffer generated by C++ code: |
| 41 | // This file was generated from monsterdata_test.json |
| 42 | |
| 43 | val data = RandomAccessFile(File("monsterdata_test.mon"), "r").use { |
| 44 | val temp = ByteArray(it.length().toInt()) |
| 45 | it.readFully(temp) |
| 46 | temp |
| 47 | } |
| 48 | |
| 49 | // Now test it: |
| 50 | |
| 51 | val bb = ByteBuffer.wrap(data) |
| 52 | TestBuffer(bb) |
| 53 | |
| 54 | // Second, let's create a FlatBuffer from scratch in Java, and test it also. |
| 55 | // We use an initial size of 1 to exercise the reallocation algorithm, |
| 56 | // normally a size larger than the typical FlatBuffer you generate would be |
| 57 | // better for performance. |
| 58 | val fbb = FlatBufferBuilder(1) |
| 59 | |
| 60 | TestBuilderBasics(fbb, true) |
| 61 | TestBuilderBasics(fbb, false) |
| 62 | |
| 63 | TestExtendedBuffer(fbb.dataBuffer().asReadOnlyBuffer()) |
| 64 | |
| 65 | TestNamespaceNesting() |
| 66 | |
| 67 | TestNestedFlatBuffer() |
| 68 | |
| 69 | TestCreateByteVector() |
| 70 | |
| 71 | TestCreateUninitializedVector() |
| 72 | |
| 73 | TestByteBufferFactory() |
| 74 | |
| 75 | TestSizedInputStream() |
| 76 | |
| 77 | TestVectorOfUnions() |
| 78 | |
| 79 | println("FlatBuffers test: completed successfully") |
| 80 | } |
| 81 | |
| 82 | fun TestEnums() { |
| 83 | assert(Color.name(Color.Red.toInt()) == "Red") |
| 84 | assert(Color.name(Color.Blue.toInt()) == "Blue") |
| 85 | assert(Any_.name(Any_.NONE.toInt()) == "NONE") |
| 86 | assert(Any_.name(Any_.Monster.toInt()) == "Monster") |
| 87 | } |
| 88 | |
| 89 | fun TestBuffer(bb: ByteBuffer) { |
| 90 | assert(Monster.MonsterBufferHasIdentifier(bb) == true) |
| 91 | |
| 92 | val monster = Monster.getRootAsMonster(bb) |
| 93 | |
| 94 | assert(monster.hp == 80.toShort()) |
| 95 | assert(monster.mana == 150.toShort()) // default |
| 96 | |
| 97 | assert(monster.name == "MyMonster") |
| 98 | // monster.friendly() // can't access, deprecated |
| 99 | |
| 100 | val pos = monster.pos!! |
| 101 | assert(pos.x == 1.0f) |
| 102 | assert(pos.y == 2.0f) |
| 103 | assert(pos.z == 3.0f) |
| 104 | assert(pos.test1 == 3.0) |
| 105 | // issue: int != byte |
| 106 | assert(pos.test2 == Color.Green) |
| 107 | val t = pos.test3!! |
| 108 | assert(t.a == 5.toShort()) |
| 109 | assert(t.b == 6.toByte()) |
| 110 | |
| 111 | assert(monster.testType == Any_.Monster) |
| 112 | val monster2 = Monster() |
| 113 | assert(monster.test(monster2) != null == true) |
| 114 | assert(monster2.name == "Fred") |
| 115 | |
| 116 | assert(monster.inventoryLength == 5) |
| 117 | var invsum = 0u |
| 118 | for (i in 0 until monster.inventoryLength) |
| 119 | invsum += monster.inventory(i) |
| 120 | assert(invsum == 10u) |
| 121 | |
| 122 | // Alternative way of accessing a vector: |
| 123 | val ibb = monster.inventoryAsByteBuffer |
| 124 | invsum = 0u |
| 125 | while (ibb.position() < ibb.limit()) |
| 126 | invsum += ibb.get().toUInt() |
| 127 | assert(invsum == 10u) |
| 128 | |
| 129 | |
| 130 | val test_0 = monster.test4(0)!! |
| 131 | val test_1 = monster.test4(1)!! |
| 132 | assert(monster.test4Length == 2) |
| 133 | assert(test_0.a + test_0.b + test_1.a + test_1.b == 100) |
| 134 | |
| 135 | assert(monster.testarrayofstringLength == 2) |
| 136 | assert(monster.testarrayofstring(0) == "test1") |
| 137 | assert(monster.testarrayofstring(1) == "test2") |
| 138 | |
| 139 | assert(monster.testbool == true) |
| 140 | } |
| 141 | |
| 142 | // this method checks additional fields not present in the binary buffer read from file |
| 143 | // these new tests are performed on top of the regular tests |
| 144 | fun TestExtendedBuffer(bb: ByteBuffer) { |
| 145 | TestBuffer(bb) |
| 146 | |
| 147 | val monster = Monster.getRootAsMonster(bb) |
| 148 | |
| 149 | assert(monster.testhashu32Fnv1 == (1u + Integer.MAX_VALUE.toUInt())) |
| 150 | } |
| 151 | |
| 152 | fun TestNamespaceNesting() { |
| 153 | // reference / manipulate these to verify compilation |
| 154 | val fbb = FlatBufferBuilder(1) |
| 155 | |
| 156 | TableInNestedNS.startTableInNestedNS(fbb) |
| 157 | TableInNestedNS.addFoo(fbb, 1234) |
| 158 | val nestedTableOff = TableInNestedNS.endTableInNestedNS(fbb) |
| 159 | |
| 160 | TableInFirstNS.startTableInFirstNS(fbb) |
| 161 | TableInFirstNS.addFooTable(fbb, nestedTableOff) |
| 162 | } |
| 163 | |
| 164 | fun TestNestedFlatBuffer() { |
| 165 | val nestedMonsterName = "NestedMonsterName" |
| 166 | val nestedMonsterHp: Short = 600 |
| 167 | val nestedMonsterMana: Short = 1024 |
| 168 | |
| 169 | var fbb1: FlatBufferBuilder? = FlatBufferBuilder(16) |
| 170 | val str1 = fbb1!!.createString(nestedMonsterName) |
| 171 | Monster.startMonster(fbb1) |
| 172 | Monster.addName(fbb1, str1) |
| 173 | Monster.addHp(fbb1, nestedMonsterHp) |
| 174 | Monster.addMana(fbb1, nestedMonsterMana) |
| 175 | val monster1 = Monster.endMonster(fbb1) |
| 176 | Monster.finishMonsterBuffer(fbb1, monster1) |
| 177 | val fbb1Bytes = fbb1.sizedByteArray() |
| 178 | |
| 179 | val fbb2 = FlatBufferBuilder(16) |
| 180 | val str2 = fbb2.createString("My Monster") |
| 181 | val nestedBuffer = Monster.createTestnestedflatbufferVector(fbb2, fbb1Bytes.asUByteArray()) |
| 182 | Monster.startMonster(fbb2) |
| 183 | Monster.addName(fbb2, str2) |
| 184 | Monster.addHp(fbb2, 50.toShort()) |
| 185 | Monster.addMana(fbb2, 32.toShort()) |
| 186 | Monster.addTestnestedflatbuffer(fbb2, nestedBuffer) |
| 187 | val monster = Monster.endMonster(fbb2) |
| 188 | Monster.finishMonsterBuffer(fbb2, monster) |
| 189 | |
| 190 | // Now test the data extracted from the nested buffer |
| 191 | val mons = Monster.getRootAsMonster(fbb2.dataBuffer()) |
| 192 | val nestedMonster = mons.testnestedflatbufferAsMonster!! |
| 193 | |
| 194 | assert(nestedMonsterMana == nestedMonster.mana) |
| 195 | assert(nestedMonsterHp == nestedMonster.hp) |
| 196 | assert(nestedMonsterName == nestedMonster.name) |
| 197 | } |
| 198 | |
| 199 | fun TestCreateByteVector() { |
| 200 | val fbb = FlatBufferBuilder(16) |
| 201 | val str = fbb.createString("MyMonster") |
| 202 | val inventory = byteArrayOf(0, 1, 2, 3, 4) |
| 203 | val vec = fbb.createByteVector(inventory) |
| 204 | Monster.startMonster(fbb) |
| 205 | Monster.addInventory(fbb, vec) |
| 206 | Monster.addName(fbb, str) |
| 207 | val monster1 = Monster.endMonster(fbb) |
| 208 | Monster.finishMonsterBuffer(fbb, monster1) |
| 209 | val monsterObject = Monster.getRootAsMonster(fbb.dataBuffer()) |
| 210 | |
| 211 | assert(monsterObject.inventory(1) == inventory[1].toUByte()) |
| 212 | assert(monsterObject.inventoryLength == inventory.size) |
| 213 | assert(ByteBuffer.wrap(inventory) == monsterObject.inventoryAsByteBuffer) |
| 214 | } |
| 215 | |
| 216 | fun TestCreateUninitializedVector() { |
| 217 | val fbb = FlatBufferBuilder(16) |
| 218 | val str = fbb.createString("MyMonster") |
| 219 | val inventory = byteArrayOf(0, 1, 2, 3, 4) |
| 220 | val bb = fbb.createUnintializedVector(1, inventory.size, 1) |
| 221 | for (i in inventory) { |
| 222 | bb.put(i) |
| 223 | } |
| 224 | val vec = fbb.endVector() |
| 225 | Monster.startMonster(fbb) |
| 226 | Monster.addInventory(fbb, vec) |
| 227 | Monster.addName(fbb, str) |
| 228 | val monster1 = Monster.endMonster(fbb) |
| 229 | Monster.finishMonsterBuffer(fbb, monster1) |
| 230 | val monsterObject = Monster.getRootAsMonster(fbb.dataBuffer()) |
| 231 | |
| 232 | assert(monsterObject.inventory(1) == inventory[1].toUByte()) |
| 233 | assert(monsterObject.inventoryLength == inventory.size) |
| 234 | assert(ByteBuffer.wrap(inventory) == monsterObject.inventoryAsByteBuffer) |
| 235 | } |
| 236 | |
| 237 | fun TestByteBufferFactory() { |
| 238 | class MappedByteBufferFactory : FlatBufferBuilder.ByteBufferFactory() { |
| 239 | override fun newByteBuffer(capacity: Int): ByteBuffer? { |
| 240 | var bb: ByteBuffer? |
| 241 | try { |
| 242 | bb = RandomAccessFile("javatest.bin", "rw").channel.map( |
| 243 | FileChannel.MapMode.READ_WRITE, |
| 244 | 0, |
| 245 | capacity.toLong() |
| 246 | ).order(ByteOrder.LITTLE_ENDIAN) |
| 247 | } catch (e: Throwable) { |
| 248 | println("FlatBuffers test: couldn't map ByteBuffer to a file") |
| 249 | bb = null |
| 250 | } |
| 251 | |
| 252 | return bb |
| 253 | } |
| 254 | } |
| 255 | |
| 256 | val fbb = FlatBufferBuilder(1, MappedByteBufferFactory()) |
| 257 | |
| 258 | TestBuilderBasics(fbb, false) |
| 259 | } |
| 260 | |
| 261 | fun TestSizedInputStream() { |
| 262 | // Test on default FlatBufferBuilder that uses HeapByteBuffer |
| 263 | val fbb = FlatBufferBuilder(1) |
| 264 | |
| 265 | TestBuilderBasics(fbb, false) |
| 266 | |
| 267 | val `in` = fbb.sizedInputStream() |
| 268 | val array = fbb.sizedByteArray() |
| 269 | var count = 0 |
| 270 | var currentVal = 0 |
| 271 | |
| 272 | while (currentVal != -1 && count < array.size) { |
| 273 | try { |
| 274 | currentVal = `in`.read() |
| 275 | } catch (e: java.io.IOException) { |
| 276 | println("FlatBuffers test: couldn't read from InputStream") |
| 277 | return |
| 278 | } |
| 279 | |
| 280 | assert(currentVal.toByte() == array[count]) |
| 281 | count++ |
| 282 | } |
| 283 | assert(count == array.size) |
| 284 | } |
| 285 | |
| 286 | fun TestBuilderBasics(fbb: FlatBufferBuilder, sizePrefix: Boolean) { |
| 287 | val names = intArrayOf(fbb.createString("Frodo"), fbb.createString("Barney"), fbb.createString("Wilma")) |
| 288 | val off = IntArray(3) |
| 289 | Monster.startMonster(fbb) |
| 290 | Monster.addName(fbb, names[0]) |
| 291 | off[0] = Monster.endMonster(fbb) |
| 292 | Monster.startMonster(fbb) |
| 293 | Monster.addName(fbb, names[1]) |
| 294 | off[1] = Monster.endMonster(fbb) |
| 295 | Monster.startMonster(fbb) |
| 296 | Monster.addName(fbb, names[2]) |
| 297 | off[2] = Monster.endMonster(fbb) |
| 298 | val sortMons = fbb.createSortedVectorOfTables(Monster(), off) |
| 299 | |
| 300 | // We set up the same values as monsterdata.json: |
| 301 | |
| 302 | val str = fbb.createString("MyMonster") |
| 303 | |
| 304 | val inv = Monster.createInventoryVector(fbb, byteArrayOf(0, 1, 2, 3, 4).asUByteArray()) |
| 305 | |
| 306 | val fred = fbb.createString("Fred") |
| 307 | Monster.startMonster(fbb) |
| 308 | Monster.addName(fbb, fred) |
| 309 | val mon2 = Monster.endMonster(fbb) |
| 310 | |
| 311 | Monster.startTest4Vector(fbb, 2) |
| 312 | Test.createTest(fbb, 10.toShort(), 20.toByte()) |
| 313 | Test.createTest(fbb, 30.toShort(), 40.toByte()) |
| 314 | val test4 = fbb.endVector() |
| 315 | |
| 316 | val testArrayOfString = |
| 317 | Monster.createTestarrayofstringVector(fbb, intArrayOf(fbb.createString("test1"), fbb.createString("test2"))) |
| 318 | |
| 319 | Monster.startMonster(fbb) |
| 320 | Monster.addPos( |
| 321 | fbb, Vec3.createVec3( |
| 322 | fbb, 1.0f, 2.0f, 3.0f, 3.0, |
| 323 | Color.Green, 5.toShort(), 6.toByte() |
| 324 | ) |
| 325 | ) |
| 326 | Monster.addHp(fbb, 80.toShort()) |
| 327 | Monster.addName(fbb, str) |
| 328 | Monster.addInventory(fbb, inv) |
| 329 | Monster.addTestType(fbb, Any_.Monster) |
| 330 | Monster.addTest(fbb, mon2) |
| 331 | Monster.addTest4(fbb, test4) |
| 332 | Monster.addTestarrayofstring(fbb, testArrayOfString) |
| 333 | Monster.addTestbool(fbb, true) |
| 334 | Monster.addTesthashu32Fnv1(fbb, UInt.MAX_VALUE + 1u) |
| 335 | Monster.addTestarrayoftables(fbb, sortMons) |
| 336 | val mon = Monster.endMonster(fbb) |
| 337 | |
| 338 | if (sizePrefix) { |
| 339 | Monster.finishSizePrefixedMonsterBuffer(fbb, mon) |
| 340 | } else { |
| 341 | Monster.finishMonsterBuffer(fbb, mon) |
| 342 | } |
| 343 | |
| 344 | // Write the result to a file for debugging purposes: |
| 345 | // Note that the binaries are not necessarily identical, since the JSON |
| 346 | // parser may serialize in a slightly different order than the above |
| 347 | // Java code. They are functionally equivalent though. |
| 348 | |
| 349 | try { |
| 350 | val filename = "monsterdata_java_wire" + (if (sizePrefix) "_sp" else "") + ".mon" |
| 351 | val fc = FileOutputStream(filename).channel |
| 352 | fc.write(fbb.dataBuffer().duplicate()) |
| 353 | fc.close() |
| 354 | } catch (e: java.io.IOException) { |
| 355 | println("FlatBuffers test: couldn't write file") |
| 356 | return |
| 357 | } |
| 358 | |
| 359 | // Test it: |
| 360 | var dataBuffer = fbb.dataBuffer() |
| 361 | if (sizePrefix) { |
| 362 | assert( |
| 363 | ByteBufferUtil.getSizePrefix(dataBuffer) + SIZE_PREFIX_LENGTH == |
| 364 | dataBuffer.remaining() |
| 365 | ) |
| 366 | dataBuffer = ByteBufferUtil.removeSizePrefix(dataBuffer) |
| 367 | } |
| 368 | TestExtendedBuffer(dataBuffer) |
| 369 | |
| 370 | // Make sure it also works with read only ByteBuffers. This is slower, |
| 371 | // since creating strings incurs an additional copy |
| 372 | // (see Table.__string). |
| 373 | TestExtendedBuffer(dataBuffer.asReadOnlyBuffer()) |
| 374 | |
| 375 | TestEnums() |
| 376 | |
| 377 | //Attempt to mutate Monster fields and check whether the buffer has been mutated properly |
| 378 | // revert to original values after testing |
| 379 | val monster = Monster.getRootAsMonster(dataBuffer) |
| 380 | |
| 381 | // mana is optional and does not exist in the buffer so the mutation should fail |
| 382 | // the mana field should retain its default value |
| 383 | assert(monster.mutateMana(10.toShort()) == false) |
| 384 | assert(monster.mana == 150.toShort()) |
| 385 | |
| 386 | // Accessing a vector of sorted by the key tables |
| 387 | assert(monster.testarrayoftables(0)!!.name == "Barney") |
| 388 | assert(monster.testarrayoftables(1)!!.name == "Frodo") |
| 389 | assert(monster.testarrayoftables(2)!!.name == "Wilma") |
| 390 | |
| 391 | // Example of searching for a table by the key |
| 392 | assert(monster.testarrayoftablesByKey("Frodo")!!.name == "Frodo") |
| 393 | assert(monster.testarrayoftablesByKey("Barney")!!.name == "Barney") |
| 394 | assert(monster.testarrayoftablesByKey("Wilma")!!.name == "Wilma") |
| 395 | |
| 396 | // testType is an existing field and mutating it should succeed |
| 397 | assert(monster.testType == Any_.Monster) |
| 398 | assert(monster.mutateTestType(Any_.NONE) == true) |
| 399 | assert(monster.testType == Any_.NONE) |
| 400 | assert(monster.mutateTestType(Any_.Monster) == true) |
| 401 | assert(monster.testType == Any_.Monster) |
| 402 | |
| 403 | //mutate the inventory vector |
| 404 | assert(monster.mutateInventory(0, 1u) == true) |
| 405 | assert(monster.mutateInventory(1, 2u) == true) |
| 406 | assert(monster.mutateInventory(2, 3u) == true) |
| 407 | assert(monster.mutateInventory(3, 4u) == true) |
| 408 | assert(monster.mutateInventory(4, 5u) == true) |
| 409 | |
| 410 | for (i in 0 until monster.inventoryLength) { |
| 411 | assert(monster.inventory(i) == (i.toUByte() + 1u).toUByte()) |
| 412 | } |
| 413 | |
| 414 | //reverse mutation |
| 415 | assert(monster.mutateInventory(0, 0u) == true) |
| 416 | assert(monster.mutateInventory(1, 1u) == true) |
| 417 | assert(monster.mutateInventory(2, 2u) == true) |
| 418 | assert(monster.mutateInventory(3, 3u) == true) |
| 419 | assert(monster.mutateInventory(4, 4u) == true) |
| 420 | |
| 421 | // get a struct field and edit one of its fields |
| 422 | val pos = monster.pos!! |
| 423 | assert(pos.x == 1.0f) |
| 424 | pos.mutateX(55.0f) |
| 425 | assert(pos.x == 55.0f) |
| 426 | pos.mutateX(1.0f) |
| 427 | assert(pos.x == 1.0f) |
| 428 | } |
| 429 | |
| 430 | fun TestVectorOfUnions() { |
| 431 | val fbb = FlatBufferBuilder() |
| 432 | |
| 433 | val swordAttackDamage = 1 |
| 434 | |
| 435 | val characterVector = intArrayOf(Attacker.createAttacker(fbb, swordAttackDamage)) |
| 436 | |
| 437 | val characterTypeVector = ubyteArrayOf(Character_.MuLan) |
| 438 | |
| 439 | Movie.finishMovieBuffer( |
| 440 | fbb, |
| 441 | Movie.createMovie( |
| 442 | fbb, |
| 443 | 0u, |
| 444 | 0, |
| 445 | Movie.createCharactersTypeVector(fbb, characterTypeVector), |
| 446 | Movie.createCharactersVector(fbb, characterVector) |
| 447 | ) |
| 448 | ) |
| 449 | |
| 450 | val movie = Movie.getRootAsMovie(fbb.dataBuffer()) |
| 451 | |
| 452 | assert(movie.charactersTypeLength == characterTypeVector.size) |
| 453 | assert(movie.charactersLength == characterVector.size) |
| 454 | |
| 455 | assert(movie.charactersType(0) == characterTypeVector[0]) |
| 456 | |
| 457 | assert((movie.characters(Attacker(), 0) as Attacker).swordAttackDamage == swordAttackDamage) |
| 458 | } |
| 459 | } |
| 460 | } |