• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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