blob: 5ce46dcb2985049023e571402c139fb098c1953a [file] [log] [blame]
Austin Schuh272c6132020-11-14 16:37:52 -08001import '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}