1import 'dart:convert'; 2import 'dart:typed_data'; 3 4import 'types.dart'; 5 6/// The main builder class for creation of a FlexBuffer. 7class Builder { 8 ByteData _buffer; 9 List<_StackValue> _stack; 10 List<_StackPointer> _stackPointers; 11 int _offset; 12 bool _finished; 13 Map<String, _StackValue> _stringCache; 14 Map<String, _StackValue> _keyCache; 15 Map<_KeysHash, _StackValue> _keyVectorCache; 16 Map<int, _StackValue> _indirectIntCache; 17 Map<double, _StackValue> _indirectDoubleCache; 18 19 /// Instantiate the builder if you intent to gradually build up the buffer by calling 20 /// add... methods and calling [finish] to receive the the resulting byte array. 21 /// 22 /// The default size of internal buffer is set to 2048. Provide a different value in order to avoid buffer copies. 23 Builder({int size = 2048}) { 24 _buffer = ByteData(size); 25 _stack = []; 26 _stackPointers = []; 27 _offset = 0; 28 _finished = false; 29 _stringCache = {}; 30 _keyCache = {}; 31 _keyVectorCache = {}; 32 _indirectIntCache = {}; 33 _indirectDoubleCache = {}; 34 } 35 36 /// Use this method in order to turn an object into a FlexBuffer directly. 37 /// 38 /// Use the manual instantiation of the [Builder] and gradual addition of values, if performance is more important than convenience. 39 static ByteBuffer buildFromObject(Object value) { 40 final builder = Builder(); 41 builder._add(value); 42 final buffer = builder.finish(); 43 final byteData = ByteData(buffer.lengthInBytes); 44 byteData.buffer.asUint8List().setAll(0, buffer); 45 return byteData.buffer; 46 } 47 48 void _add(Object value) { 49 if (value == null) { 50 addNull(); 51 } else if (value is bool) { 52 addBool(value); 53 } else if (value is int) { 54 addInt(value); 55 } else if (value is double) { 56 addDouble(value); 57 } else if (value is ByteBuffer) { 58 addBlob(value); 59 } else if (value is String) { 60 addString(value); 61 } else if (value is List<dynamic>) { 62 startVector(); 63 for (var i = 0; i < value.length; i++) { 64 _add(value[i]); 65 } 66 end(); 67 } else if (value is Map<String, dynamic>) { 68 startMap(); 69 value.forEach((key, value) { 70 addKey(key); 71 _add(value); 72 }); 73 end(); 74 } else { 75 throw UnsupportedError('Value of unexpected type: $value'); 76 } 77 } 78 79 /// Use this method if you want to store a null value. 80 /// 81 /// Specifically useful when building up a vector where values can be null. 82 void addNull() { 83 _integrityCheckOnValueAddition(); 84 _stack.add(_StackValue.WithNull()); 85 } 86 87 /// Adds a string value. 88 void addInt(int value) { 89 _integrityCheckOnValueAddition(); 90 _stack.add(_StackValue.WithInt(value)); 91 } 92 93 /// Adds a bool value. 94 void addBool(bool value) { 95 _integrityCheckOnValueAddition(); 96 _stack.add(_StackValue.WithBool(value)); 97 } 98 99 /// Adds a double value. 100 void addDouble(double value) { 101 _integrityCheckOnValueAddition(); 102 _stack.add(_StackValue.WithDouble(value)); 103 } 104 105 /// Adds a string value. 106 void addString(String value) { 107 _integrityCheckOnValueAddition(); 108 if (_stringCache.containsKey(value)) { 109 _stack.add(_stringCache[value]); 110 return; 111 } 112 final utf8String = utf8.encode(value); 113 final length = utf8String.length; 114 final bitWidth = BitWidthUtil.uwidth(length); 115 final byteWidth = _align(bitWidth); 116 _writeUInt(length, byteWidth); 117 final stringOffset = _offset; 118 final newOffset = _newOffset(length + 1); 119 _pushBuffer(utf8String); 120 _offset = newOffset; 121 final stackValue = _StackValue.WithOffset(stringOffset, ValueType.String, bitWidth); 122 _stack.add(stackValue); 123 _stringCache[value] = stackValue; 124 } 125 126 /// This methods adds a key to a map and should be followed by an add... value call. 127 /// 128 /// It also implies that you call this method only after you called [startMap]. 129 void addKey(String value) { 130 _integrityCheckOnKeyAddition(); 131 if (_keyCache.containsKey(value)) { 132 _stack.add(_keyCache[value]); 133 return; 134 } 135 final utf8String = utf8.encode(value); 136 final length = utf8String.length; 137 final keyOffset = _offset; 138 final newOffset = _newOffset(length + 1); 139 _pushBuffer(utf8String); 140 _offset = newOffset; 141 final stackValue = _StackValue.WithOffset(keyOffset, ValueType.Key, BitWidth.width8); 142 _stack.add(stackValue); 143 _keyCache[value] = stackValue; 144 } 145 146 /// Adds a byte array. 147 /// 148 /// This method can be used to store any generic BLOB. 149 void addBlob(ByteBuffer value) { 150 _integrityCheckOnValueAddition(); 151 final length = value.lengthInBytes; 152 final bitWidth = BitWidthUtil.uwidth(length); 153 final byteWidth = _align(bitWidth); 154 _writeUInt(length, byteWidth); 155 final blobOffset = _offset; 156 final newOffset = _newOffset(length); 157 _pushBuffer(value.asUint8List()); 158 _offset = newOffset; 159 final stackValue = _StackValue.WithOffset(blobOffset, ValueType.Blob, bitWidth); 160 _stack.add(stackValue); 161 } 162 163 /// Stores int value indirectly in the buffer. 164 /// 165 /// Adding large integer values indirectly might be beneficial if those values suppose to be store in a vector together with small integer values. 166 /// This is due to the fact that FlexBuffers will add padding to small integer values, if they are stored together with large integer values. 167 /// When we add integer indirectly the vector of ints will contain not the value itself, but only the relative offset to the value. 168 /// By setting the [cache] parameter to true, you make sure that the builder tracks added int value and performs deduplication. 169 void addIntIndirectly(int value, {bool cache = false}) { 170 _integrityCheckOnValueAddition(); 171 if (_indirectIntCache.containsKey(value)) { 172 _stack.add(_indirectIntCache[value]); 173 return; 174 } 175 final stackValue = _StackValue.WithInt(value); 176 final byteWidth = _align(stackValue.width); 177 final newOffset = _newOffset(byteWidth); 178 final valueOffset = _offset; 179 _pushBuffer(stackValue.asU8List(stackValue.width)); 180 final stackOffset = _StackValue.WithOffset(valueOffset, ValueType.IndirectInt, stackValue.width); 181 _stack.add(stackOffset); 182 _offset = newOffset; 183 if (cache) { 184 _indirectIntCache[value] = stackOffset; 185 } 186 } 187 188 /// Stores double value indirectly in the buffer. 189 /// 190 /// Double are stored as 8 or 4 byte values in FlexBuffers. If they are stored in a mixed vector, values which are smaller than 4 / 8 bytes will be padded. 191 /// When we add double indirectly, the vector will contain not the value itself, but only the relative offset to the value. Which could occupy only 1 or 2 bytes, reducing the odds for unnecessary padding. 192 /// By setting the [cache] parameter to true, you make sure that the builder tracks already added double value and performs deduplication. 193 void addDoubleIndirectly(double value, {bool cache = false}) { 194 _integrityCheckOnValueAddition(); 195 if (cache && _indirectDoubleCache.containsKey(value)) { 196 _stack.add(_indirectDoubleCache[value]); 197 return; 198 } 199 final stackValue = _StackValue.WithDouble(value); 200 final byteWidth = _align(stackValue.width); 201 final newOffset = _newOffset(byteWidth); 202 final valueOffset = _offset; 203 _pushBuffer(stackValue.asU8List(stackValue.width)); 204 final stackOffset = _StackValue.WithOffset(valueOffset, ValueType.IndirectFloat, stackValue.width); 205 _stack.add(stackOffset); 206 _offset = newOffset; 207 if (cache) { 208 _indirectDoubleCache[value] = stackOffset; 209 } 210 } 211 212 /// This method starts a vector definition and needs to be followed by 0 to n add... value calls. 213 /// 214 /// The vector definition needs to be finished with an [end] call. 215 /// It is also possible to add nested vector or map by calling [startVector] / [startMap]. 216 void startVector() { 217 _integrityCheckOnValueAddition(); 218 _stackPointers.add(_StackPointer(_stack.length, true)); 219 } 220 221 /// This method starts a map definition. 222 /// 223 /// This method call needs to be followed by 0 to n [addKey] + add... value calls. 224 /// The map definition needs to be finished with an [end] call. 225 /// It is also possible to add nested vector or map by calling [startVector] / [startMap] after calling [addKey]. 226 void startMap() { 227 _integrityCheckOnValueAddition(); 228 _stackPointers.add(_StackPointer(_stack.length, false)); 229 } 230 231 /// Marks that the addition of values to the last vector, or map have ended. 232 void end() { 233 final pointer = _stackPointers.removeLast(); 234 if (pointer.isVector) { 235 _endVector(pointer); 236 } else { 237 _sortKeysAndEndMap(pointer); 238 } 239 } 240 241 /// Finish building the FlatBuffer and return array of bytes. 242 /// 243 /// Can be called multiple times, to get the array of bytes. 244 /// After the first call, adding values, or starting vectors / maps will result in an exception. 245 Uint8List finish() { 246 if (_finished == false) { 247 _finish(); 248 } 249 return _buffer.buffer.asUint8List(0, _offset); 250 } 251 252 /// Builds a FlatBuffer with current state without finishing the builder. 253 /// 254 /// Creates an internal temporary copy of current builder and finishes the copy. 255 /// Use this method, when the state of a long lasting builder need to be persisted periodically. 256 ByteBuffer snapshot() { 257 final tmp = Builder(size: _offset + 200); 258 tmp._offset = _offset; 259 tmp._stack = List.from(_stack); 260 tmp._stackPointers = List.from(_stackPointers); 261 tmp._buffer.buffer.asUint8List().setAll(0, _buffer.buffer.asUint8List(0, _offset)); 262 for (var i = 0; i < tmp._stackPointers.length; i++){ 263 tmp.end(); 264 } 265 final buffer = tmp.finish(); 266 final bd = ByteData(buffer.lengthInBytes); 267 bd.buffer.asUint8List().setAll(0, buffer); 268 return bd.buffer; 269 } 270 271 void _integrityCheckOnValueAddition() { 272 if (_finished) { 273 throw StateError('Adding values after finish is prohibited'); 274 } 275 if (_stackPointers.isNotEmpty && _stackPointers.last.isVector == false) { 276 if (_stack.last.type != ValueType.Key) { 277 throw StateError('Adding value to a map before adding a key is prohibited'); 278 } 279 } 280 } 281 282 void _integrityCheckOnKeyAddition() { 283 if (_finished) { 284 throw StateError('Adding values after finish is prohibited'); 285 } 286 if (_stackPointers.isEmpty || _stackPointers.last.isVector) { 287 throw StateError('Adding key before staring a map is prohibited'); 288 } 289 } 290 291 void _finish() { 292 if (_stack.length != 1) { 293 throw StateError('Stack has to be exactly 1, but is ${_stack.length}. You have to end all started vectors and maps, before calling [finish]'); 294 } 295 final value = _stack[0]; 296 final byteWidth = _align(value.elementWidth(_offset, 0)); 297 _writeStackValue(value, byteWidth); 298 _writeUInt(value.storedPackedType(), 1); 299 _writeUInt(byteWidth, 1); 300 _finished = true; 301 } 302 303 _StackValue _createVector(int start, int vecLength, int step, [_StackValue keys]) { 304 var bitWidth = BitWidthUtil.uwidth(vecLength); 305 var prefixElements = 1; 306 if (keys != null) { 307 var elemWidth = keys.elementWidth(_offset, 0); 308 if (elemWidth.index > bitWidth.index) { 309 bitWidth = elemWidth; 310 } 311 prefixElements += 2; 312 } 313 var vectorType = ValueType.Key; 314 var typed = keys == null; 315 for (var i = start; i < _stack.length; i += step) { 316 final elemWidth = _stack[i].elementWidth(_offset, i + prefixElements); 317 if (elemWidth.index > bitWidth.index) { 318 bitWidth = elemWidth; 319 } 320 if (i == start) { 321 vectorType = _stack[i].type; 322 typed &= ValueTypeUtils.isTypedVectorElement(vectorType); 323 } else { 324 if (vectorType != _stack[i].type) { 325 typed = false; 326 } 327 } 328 } 329 final byteWidth = _align(bitWidth); 330 final fix = typed & ValueTypeUtils.isNumber(vectorType) && vecLength >= 2 && vecLength <= 4; 331 if (keys != null) { 332 _writeStackValue(keys, byteWidth); 333 _writeUInt(1 << keys.width.index, byteWidth); 334 } 335 if (fix == false) { 336 _writeUInt(vecLength, byteWidth); 337 } 338 final vecOffset = _offset; 339 for (var i = start; i < _stack.length; i += step) { 340 _writeStackValue(_stack[i], byteWidth); 341 } 342 if (typed == false) { 343 for (var i = start; i < _stack.length; i += step) { 344 _writeUInt(_stack[i].storedPackedType(), 1); 345 } 346 } 347 if (keys != null) { 348 return _StackValue.WithOffset(vecOffset, ValueType.Map, bitWidth); 349 } 350 if (typed) { 351 final vType = ValueTypeUtils.toTypedVector(vectorType, fix ? vecLength : 0); 352 return _StackValue.WithOffset(vecOffset, vType, bitWidth); 353 } 354 return _StackValue.WithOffset(vecOffset, ValueType.Vector, bitWidth); 355 } 356 357 void _endVector(_StackPointer pointer) { 358 final vecLength = _stack.length - pointer.stackPosition; 359 final vec = _createVector(pointer.stackPosition, vecLength, 1); 360 _stack.removeRange(pointer.stackPosition, _stack.length); 361 _stack.add(vec); 362 } 363 364 void _sortKeysAndEndMap(_StackPointer pointer) { 365 if (((_stack.length - pointer.stackPosition) & 1) == 1) { 366 throw StateError('The stack needs to hold key value pairs (even number of elements). Check if you combined [addKey] with add... method calls properly.'); 367 } 368 369 var sorted = true; 370 for (var i = pointer.stackPosition; i < _stack.length - 2; i += 2) { 371 if (_shouldFlip(_stack[i], _stack[i+2])) { 372 sorted = false; 373 break; 374 } 375 } 376 377 if (sorted == false) { 378 for (var i = pointer.stackPosition; i < _stack.length; i += 2) { 379 var flipIndex = i; 380 for (var j = i + 2; j < _stack.length; j += 2) { 381 if (_shouldFlip(_stack[flipIndex], _stack[j])) { 382 flipIndex = j; 383 } 384 } 385 if (flipIndex != i) { 386 var k = _stack[flipIndex]; 387 var v = _stack[flipIndex + 1]; 388 _stack[flipIndex] = _stack[i]; 389 _stack[flipIndex + 1] = _stack[i + 1]; 390 _stack[i] = k; 391 _stack[i + 1] = v; 392 } 393 } 394 } 395 _endMap(pointer); 396 } 397 398 void _endMap(_StackPointer pointer) { 399 final vecLength = (_stack.length - pointer.stackPosition) >> 1; 400 final offsets = <int>[]; 401 for (var i = pointer.stackPosition; i < _stack.length; i += 2) { 402 offsets.add(_stack[i].offset); 403 } 404 final keysHash = _KeysHash(offsets); 405 var keysStackValue; 406 if (_keyVectorCache.containsKey(keysHash)) { 407 keysStackValue = _keyVectorCache[keysHash]; 408 } else { 409 keysStackValue = _createVector(pointer.stackPosition, vecLength, 2); 410 _keyVectorCache[keysHash] = keysStackValue; 411 } 412 final vec = _createVector(pointer.stackPosition + 1, vecLength, 2, keysStackValue); 413 _stack.removeRange(pointer.stackPosition, _stack.length); 414 _stack.add(vec); 415 } 416 417 bool _shouldFlip(_StackValue v1, _StackValue v2) { 418 if (v1.type != ValueType.Key || v2.type != ValueType.Key) { 419 throw StateError('Stack values are not keys $v1 | $v2. Check if you combined [addKey] with add... method calls properly.'); 420 } 421 422 var c1, c2; 423 var index = 0; 424 do { 425 c1 = _buffer.getUint8(v1.offset + index); 426 c2 = _buffer.getUint8(v2.offset + index); 427 if (c2 < c1) return true; 428 if (c1 < c2) return false; 429 index += 1; 430 } while (c1 != 0 && c2 != 0); 431 return false; 432 } 433 434 int _align(BitWidth width) { 435 final byteWidth = BitWidthUtil.toByteWidth(width); 436 _offset += BitWidthUtil.paddingSize(_offset, byteWidth); 437 return byteWidth; 438 } 439 440 void _writeStackValue(_StackValue value, int byteWidth) { 441 final newOffset = _newOffset(byteWidth); 442 if (value.isOffset) { 443 final relativeOffset = _offset - value.offset; 444 if (byteWidth == 8 || relativeOffset < (1 << (byteWidth * 8))) { 445 _writeUInt(relativeOffset, byteWidth); 446 } else { 447 throw StateError('Unexpected size $byteWidth. This might be a bug. Please create an issue https://github.com/google/flatbuffers/issues/new'); 448 } 449 } else { 450 _pushBuffer(value.asU8List(BitWidthUtil.fromByteWidth(byteWidth))); 451 } 452 _offset = newOffset; 453 } 454 455 void _writeUInt(int value, int byteWidth) { 456 final newOffset = _newOffset(byteWidth); 457 _pushUInt(value, BitWidthUtil.fromByteWidth(byteWidth)); 458 _offset = newOffset; 459 } 460 461 int _newOffset(int newValueSize) { 462 final newOffset = _offset + newValueSize; 463 var size = _buffer.lengthInBytes; 464 final prevSize = size; 465 while (size < newOffset) { 466 size <<= 1; 467 } 468 if (prevSize < size) { 469 final newBuf = ByteData(size); 470 newBuf.buffer 471 .asUint8List() 472 .setAll(0, _buffer.buffer.asUint8List()); 473 } 474 return newOffset; 475 } 476 477 void _pushInt(int value, BitWidth width) { 478 switch (width) { 479 480 case BitWidth.width8: 481 _buffer.setInt8(_offset, value); 482 break; 483 case BitWidth.width16: 484 _buffer.setInt16(_offset, value, Endian.little); 485 break; 486 case BitWidth.width32: 487 _buffer.setInt32(_offset, value, Endian.little); 488 break; 489 case BitWidth.width64: 490 _buffer.setInt64(_offset, value, Endian.little); 491 break; 492 } 493 } 494 495 void _pushUInt(int value, BitWidth width) { 496 switch (width) { 497 498 case BitWidth.width8: 499 _buffer.setUint8(_offset, value); 500 break; 501 case BitWidth.width16: 502 _buffer.setUint16(_offset, value, Endian.little); 503 break; 504 case BitWidth.width32: 505 _buffer.setUint32(_offset, value, Endian.little); 506 break; 507 case BitWidth.width64: 508 _buffer.setUint64(_offset, value, Endian.little); 509 break; 510 } 511 } 512 513 void _pushBuffer(List<int> value) { 514 _buffer.buffer.asUint8List().setAll(_offset, value); 515 } 516} 517 518class _StackValue { 519 Object _value; 520 int _offset; 521 ValueType _type; 522 BitWidth _width; 523 _StackValue.WithNull() { 524 _type = ValueType.Null; 525 _width = BitWidth.width8; 526 } 527 _StackValue.WithInt(int value) { 528 _type = value != null ? ValueType.Int : ValueType.Null; 529 _width = BitWidthUtil.width(value); 530 _value = value; 531 } 532 _StackValue.WithBool(bool value) { 533 _type = value != null ? ValueType.Bool : ValueType.Null; 534 _width = BitWidth.width8; 535 _value = value; 536 } 537 _StackValue.WithDouble(double value) { 538 _type = value != null ? ValueType.Float : ValueType.Null; 539 _width = BitWidthUtil.width(value); 540 _value = value; 541 } 542 _StackValue.WithOffset(int value, ValueType type, BitWidth width) { 543 _offset = value; 544 _type = type; 545 _width = width; 546 } 547 548 BitWidth storedWidth({BitWidth width = BitWidth.width8}) { 549 return ValueTypeUtils.isInline(_type) ? BitWidthUtil.max(_width, width) : _width; 550 } 551 552 int storedPackedType({BitWidth width = BitWidth.width8}) { 553 return ValueTypeUtils.packedType(_type, storedWidth(width: width)); 554 } 555 556 BitWidth elementWidth(int size, int index) { 557 if (ValueTypeUtils.isInline(_type)) return _width; 558 for(var i = 0; i < 4; i++) { 559 final width = 1 << i; 560 final offsetLoc = size + BitWidthUtil.paddingSize(size, width) + index * width; 561 final offset = offsetLoc - _offset; 562 final bitWidth = BitWidthUtil.uwidth(offset); 563 if (1 << bitWidth.index == width) { 564 return bitWidth; 565 } 566 } 567 throw StateError('Element is of unknown. Size: $size at index: $index. This might be a bug. Please create an issue https://github.com/google/flatbuffers/issues/new'); 568 } 569 570 List<int> asU8List(BitWidth width) { 571 if (ValueTypeUtils.isNumber(_type)) { 572 if (_type == ValueType.Float) { 573 if (width == BitWidth.width32) { 574 final result = ByteData(4); 575 result.setFloat32(0, _value, Endian.little); 576 return result.buffer.asUint8List(); 577 } else { 578 final result = ByteData(8); 579 result.setFloat64(0, _value, Endian.little); 580 return result.buffer.asUint8List(); 581 } 582 } else { 583 switch(width) { 584 case BitWidth.width8: 585 final result = ByteData(1); 586 result.setInt8(0, _value); 587 return result.buffer.asUint8List(); 588 case BitWidth.width16: 589 final result = ByteData(2); 590 result.setInt16(0, _value, Endian.little); 591 return result.buffer.asUint8List(); 592 case BitWidth.width32: 593 final result = ByteData(4); 594 result.setInt32(0, _value, Endian.little); 595 return result.buffer.asUint8List(); 596 case BitWidth.width64: 597 final result = ByteData(8); 598 result.setInt64(0, _value, Endian.little); 599 return result.buffer.asUint8List(); 600 } 601 } 602 } 603 if (_type == ValueType.Null) { 604 final result = ByteData(1); 605 result.setInt8(0, 0); 606 return result.buffer.asUint8List(); 607 } 608 if (_type == ValueType.Bool) { 609 final result = ByteData(1); 610 result.setInt8(0, _value ? 1 : 0); 611 return result.buffer.asUint8List(); 612 } 613 614 throw StateError('Unexpected type: $_type. This might be a bug. Please create an issue https://github.com/google/flatbuffers/issues/new'); 615 } 616 617 ValueType get type { 618 return _type; 619 } 620 621 BitWidth get width { 622 return _width; 623 } 624 625 bool get isOffset { 626 return !ValueTypeUtils.isInline(_type); 627 } 628 int get offset => _offset; 629 630 bool get isFloat32 { 631 return _type == ValueType.Float && _width == BitWidth.width32; 632 } 633} 634 635class _StackPointer { 636 int stackPosition; 637 bool isVector; 638 _StackPointer(this.stackPosition, this.isVector); 639} 640 641class _KeysHash { 642 final List<int> keys; 643 644 const _KeysHash(this.keys); 645 646 @override 647 bool operator ==(Object other) { 648 if (other is _KeysHash) { 649 if (keys.length != other.keys.length) return false; 650 for (var i = 0; i < keys.length; i++) { 651 if (keys[i] != other.keys[i]) return false; 652 } 653 return true; 654 } 655 return false; 656 } 657 658 @override 659 int get hashCode { 660 var result = 17; 661 for (var i = 0; i < keys.length; i++) { 662 result = result * 23 + keys[i]; 663 } 664 return result; 665 } 666} 667