blob: e52d0b7092dc0cf7588a52e4085916145242a286 [file] [log] [blame]
Austin Schuh272c6132020-11-14 16:37:52 -08001import 'dart:collection';
2import 'dart:convert';
3import 'dart:typed_data';
4import 'types.dart';
5
6/// Main class to read a value out of a FlexBuffer.
7///
8/// This class let you access values stored in the buffer in a lazy fashion.
9class Reference {
10 final ByteData _buffer;
11 final int _offset;
12 final BitWidth _parentWidth;
13 final String _path;
James Kuszmaul8e62b022022-03-22 09:33:25 -070014 final int _byteWidth;
15 final ValueType _valueType;
16 int? _length;
Austin Schuh272c6132020-11-14 16:37:52 -080017
James Kuszmaul8e62b022022-03-22 09:33:25 -070018 Reference._(
19 this._buffer, this._offset, this._parentWidth, int packedType, this._path,
20 [int? byteWidth, ValueType? valueType])
21 : _byteWidth = byteWidth ?? 1 << (packedType & 3),
22 _valueType = valueType ?? ValueTypeUtils.fromInt(packedType >> 2);
Austin Schuh272c6132020-11-14 16:37:52 -080023
24 /// Use this method to access the root value of a FlexBuffer.
25 static Reference fromBuffer(ByteBuffer buffer) {
26 final len = buffer.lengthInBytes;
27 if (len < 3) {
28 throw UnsupportedError('Buffer needs to be bigger than 3');
29 }
30 final byteData = ByteData.view(buffer);
31 final byteWidth = byteData.getUint8(len - 1);
32 final packedType = byteData.getUint8(len - 2);
33 final offset = len - byteWidth - 2;
James Kuszmaul8e62b022022-03-22 09:33:25 -070034 return Reference._(ByteData.view(buffer), offset,
35 BitWidthUtil.fromByteWidth(byteWidth), packedType, "/");
Austin Schuh272c6132020-11-14 16:37:52 -080036 }
37
38 /// Returns true if the underlying value is null.
39 bool get isNull => _valueType == ValueType.Null;
James Kuszmaul8e62b022022-03-22 09:33:25 -070040
Austin Schuh272c6132020-11-14 16:37:52 -080041 /// Returns true if the underlying value can be represented as [num].
James Kuszmaul8e62b022022-03-22 09:33:25 -070042 bool get isNum =>
43 ValueTypeUtils.isNumber(_valueType) ||
44 ValueTypeUtils.isIndirectNumber(_valueType);
45
Austin Schuh272c6132020-11-14 16:37:52 -080046 /// Returns true if the underlying value was encoded as a float (direct or indirect).
James Kuszmaul8e62b022022-03-22 09:33:25 -070047 bool get isDouble =>
48 _valueType == ValueType.Float || _valueType == ValueType.IndirectFloat;
49
Austin Schuh272c6132020-11-14 16:37:52 -080050 /// Returns true if the underlying value was encoded as an int or uint (direct or indirect).
51 bool get isInt => isNum && !isDouble;
James Kuszmaul8e62b022022-03-22 09:33:25 -070052
Austin Schuh272c6132020-11-14 16:37:52 -080053 /// Returns true if the underlying value was encoded as a string or a key.
James Kuszmaul8e62b022022-03-22 09:33:25 -070054 bool get isString =>
55 _valueType == ValueType.String || _valueType == ValueType.Key;
56
Austin Schuh272c6132020-11-14 16:37:52 -080057 /// Returns true if the underlying value was encoded as a bool.
58 bool get isBool => _valueType == ValueType.Bool;
James Kuszmaul8e62b022022-03-22 09:33:25 -070059
Austin Schuh272c6132020-11-14 16:37:52 -080060 /// Returns true if the underlying value was encoded as a blob.
61 bool get isBlob => _valueType == ValueType.Blob;
James Kuszmaul8e62b022022-03-22 09:33:25 -070062
Austin Schuh272c6132020-11-14 16:37:52 -080063 /// Returns true if the underlying value points to a vector.
64 bool get isVector => ValueTypeUtils.isAVector(_valueType);
James Kuszmaul8e62b022022-03-22 09:33:25 -070065
Austin Schuh272c6132020-11-14 16:37:52 -080066 /// Returns true if the underlying value points to a map.
67 bool get isMap => _valueType == ValueType.Map;
68
69 /// If this [isBool], returns the bool value. Otherwise, returns null.
James Kuszmaul8e62b022022-03-22 09:33:25 -070070 bool? get boolValue {
71 if (_valueType == ValueType.Bool) {
Austin Schuh272c6132020-11-14 16:37:52 -080072 return _readInt(_offset, _parentWidth) != 0;
73 }
74 return null;
75 }
76
77 /// Returns an [int], if the underlying value can be represented as an int.
78 ///
79 /// Otherwise returns [null].
James Kuszmaul8e62b022022-03-22 09:33:25 -070080 int? get intValue {
Austin Schuh272c6132020-11-14 16:37:52 -080081 if (_valueType == ValueType.Int) {
82 return _readInt(_offset, _parentWidth);
83 }
84 if (_valueType == ValueType.UInt) {
85 return _readUInt(_offset, _parentWidth);
86 }
87 if (_valueType == ValueType.IndirectInt) {
88 return _readInt(_indirect, BitWidthUtil.fromByteWidth(_byteWidth));
89 }
90 if (_valueType == ValueType.IndirectUInt) {
91 return _readUInt(_indirect, BitWidthUtil.fromByteWidth(_byteWidth));
92 }
93 return null;
94 }
95
96 /// Returns [double], if the underlying value [isDouble].
97 ///
98 /// Otherwise returns [null].
James Kuszmaul8e62b022022-03-22 09:33:25 -070099 double? get doubleValue {
Austin Schuh272c6132020-11-14 16:37:52 -0800100 if (_valueType == ValueType.Float) {
101 return _readFloat(_offset, _parentWidth);
102 }
103 if (_valueType == ValueType.IndirectFloat) {
104 return _readFloat(_indirect, BitWidthUtil.fromByteWidth(_byteWidth));
105 }
106 return null;
107 }
108
109 /// Returns [num], if the underlying value is numeric, be it int uint, or float (direct or indirect).
110 ///
111 /// Otherwise returns [null].
James Kuszmaul8e62b022022-03-22 09:33:25 -0700112 num? get numValue => doubleValue ?? intValue;
Austin Schuh272c6132020-11-14 16:37:52 -0800113
114 /// Returns [String] value or null otherwise.
James Kuszmaul8e62b022022-03-22 09:33:25 -0700115 ///
116 /// This method performers a utf8 decoding, as FlexBuffers format stores strings in utf8 encoding.
117 String? get stringValue {
Austin Schuh272c6132020-11-14 16:37:52 -0800118 if (_valueType == ValueType.String || _valueType == ValueType.Key) {
119 return utf8.decode(_buffer.buffer.asUint8List(_indirect, length));
120 }
121 return null;
122 }
123
124 /// Returns [Uint8List] value or null otherwise.
James Kuszmaul8e62b022022-03-22 09:33:25 -0700125 Uint8List? get blobValue {
Austin Schuh272c6132020-11-14 16:37:52 -0800126 if (_valueType == ValueType.Blob) {
127 return _buffer.buffer.asUint8List(_indirect, length);
128 }
129 return null;
130 }
131
132 /// Can be used with an [int] or a [String] value for key.
133 /// If the underlying value in FlexBuffer is a vector, then use [int] for access.
134 /// If the underlying value in FlexBuffer is a map, then use [String] for access.
135 /// Returns [Reference] value. Throws an exception when [key] is not applicable.
136 Reference operator [](Object key) {
137 if (key is int && ValueTypeUtils.isAVector(_valueType)) {
138 final index = key;
James Kuszmaul8e62b022022-03-22 09:33:25 -0700139 if (index >= length || index < 0) {
140 throw ArgumentError(
141 'Key: [$key] is not applicable on: $_path of: $_valueType length: $length');
Austin Schuh272c6132020-11-14 16:37:52 -0800142 }
143 final elementOffset = _indirect + index * _byteWidth;
James Kuszmaul8e62b022022-03-22 09:33:25 -0700144 int packedType = 0;
145 int? byteWidth;
146 ValueType? valueType;
Austin Schuh272c6132020-11-14 16:37:52 -0800147 if (ValueTypeUtils.isTypedVector(_valueType)) {
James Kuszmaul8e62b022022-03-22 09:33:25 -0700148 byteWidth = 1;
149 valueType = ValueTypeUtils.typedVectorElementType(_valueType);
150 } else if (ValueTypeUtils.isFixedTypedVector(_valueType)) {
151 byteWidth = 1;
152 valueType = ValueTypeUtils.fixedTypedVectorElementType(_valueType);
153 } else {
154 packedType = _buffer.getUint8(_indirect + length * _byteWidth + index);
Austin Schuh272c6132020-11-14 16:37:52 -0800155 }
James Kuszmaul8e62b022022-03-22 09:33:25 -0700156 return Reference._(
157 _buffer,
158 elementOffset,
159 BitWidthUtil.fromByteWidth(_byteWidth),
160 packedType,
161 "$_path[$index]",
162 byteWidth,
163 valueType);
Austin Schuh272c6132020-11-14 16:37:52 -0800164 }
165 if (key is String && _valueType == ValueType.Map) {
166 final index = _keyIndex(key);
167 if (index != null) {
168 return _valueForIndexWithKey(index, key);
169 }
170 }
James Kuszmaul8e62b022022-03-22 09:33:25 -0700171 throw ArgumentError(
172 'Key: [$key] is not applicable on: $_path of: $_valueType');
Austin Schuh272c6132020-11-14 16:37:52 -0800173 }
174
175 /// Get an iterable if the underlying flexBuffer value is a vector.
176 /// Otherwise throws an exception.
177 Iterable<Reference> get vectorIterable {
James Kuszmaul8e62b022022-03-22 09:33:25 -0700178 if (isVector == false) {
Austin Schuh272c6132020-11-14 16:37:52 -0800179 throw UnsupportedError('Value is not a vector. It is: $_valueType');
180 }
181 return _VectorIterator(this);
182 }
183
184 /// Get an iterable for keys if the underlying flexBuffer value is a map.
185 /// Otherwise throws an exception.
186 Iterable<String> get mapKeyIterable {
James Kuszmaul8e62b022022-03-22 09:33:25 -0700187 if (isMap == false) {
Austin Schuh272c6132020-11-14 16:37:52 -0800188 throw UnsupportedError('Value is not a map. It is: $_valueType');
189 }
190 return _MapKeyIterator(this);
191 }
192
193 /// Get an iterable for values if the underlying flexBuffer value is a map.
194 /// Otherwise throws an exception.
195 Iterable<Reference> get mapValueIterable {
James Kuszmaul8e62b022022-03-22 09:33:25 -0700196 if (isMap == false) {
Austin Schuh272c6132020-11-14 16:37:52 -0800197 throw UnsupportedError('Value is not a map. It is: $_valueType');
198 }
199 return _MapValueIterator(this);
200 }
201
202 /// Returns the length of the the underlying FlexBuffer value.
203 /// If the underlying value is [null] the length is 0.
204 /// If the underlying value is a number, or a bool, the length is 1.
205 /// If the underlying value is a vector, or map, the length reflects number of elements / element pairs.
206 /// If the values is a string or a blob, the length reflects a number of bytes the value occupies (strings are encoded in utf8 format).
207 int get length {
James Kuszmaul8e62b022022-03-22 09:33:25 -0700208 if (_length == null) {
209 // needs to be checked before more generic isAVector
210 if (ValueTypeUtils.isFixedTypedVector(_valueType)) {
211 _length = ValueTypeUtils.fixedTypedVectorElementSize(_valueType);
212 } else if (_valueType == ValueType.Blob ||
213 ValueTypeUtils.isAVector(_valueType) ||
214 _valueType == ValueType.Map) {
215 _length = _readUInt(
216 _indirect - _byteWidth, BitWidthUtil.fromByteWidth(_byteWidth));
217 } else if (_valueType == ValueType.Null) {
218 _length = 0;
219 } else if (_valueType == ValueType.String) {
220 final indirect = _indirect;
221 var sizeByteWidth = _byteWidth;
222 var size = _readUInt(indirect - sizeByteWidth,
223 BitWidthUtil.fromByteWidth(sizeByteWidth));
224 while (_buffer.getInt8(indirect + size) != 0) {
225 sizeByteWidth <<= 1;
226 size = _readUInt(indirect - sizeByteWidth,
227 BitWidthUtil.fromByteWidth(sizeByteWidth));
228 }
229 _length = size;
230 } else if (_valueType == ValueType.Key) {
231 final indirect = _indirect;
232 var size = 1;
233 while (_buffer.getInt8(indirect + size) != 0) {
234 size += 1;
235 }
236 _length = size;
237 } else {
238 _length = 1;
Austin Schuh272c6132020-11-14 16:37:52 -0800239 }
Austin Schuh272c6132020-11-14 16:37:52 -0800240 }
James Kuszmaul8e62b022022-03-22 09:33:25 -0700241 return _length!;
Austin Schuh272c6132020-11-14 16:37:52 -0800242 }
243
Austin Schuh272c6132020-11-14 16:37:52 -0800244 /// Returns a minified JSON representation of the underlying FlexBuffer value.
245 ///
246 /// This method involves materializing the entire object tree, which may be
247 /// expensive. It is more efficient to work with [Reference] and access only the needed data.
248 /// Blob values are represented as base64 encoded string.
249 String get json {
James Kuszmaul8e62b022022-03-22 09:33:25 -0700250 if (_valueType == ValueType.Bool) {
251 return boolValue! ? 'true' : 'false';
Austin Schuh272c6132020-11-14 16:37:52 -0800252 }
253 if (_valueType == ValueType.Null) {
254 return 'null';
255 }
James Kuszmaul8e62b022022-03-22 09:33:25 -0700256 if (ValueTypeUtils.isNumber(_valueType)) {
Austin Schuh272c6132020-11-14 16:37:52 -0800257 return jsonEncode(numValue);
258 }
259 if (_valueType == ValueType.String) {
260 return jsonEncode(stringValue);
261 }
262 if (_valueType == ValueType.Blob) {
James Kuszmaul8e62b022022-03-22 09:33:25 -0700263 return jsonEncode(base64Encode(blobValue!));
Austin Schuh272c6132020-11-14 16:37:52 -0800264 }
265 if (ValueTypeUtils.isAVector(_valueType)) {
266 final result = StringBuffer();
267 result.write('[');
268 for (var i = 0; i < length; i++) {
269 result.write(this[i].json);
270 if (i < length - 1) {
271 result.write(',');
272 }
273 }
274 result.write(']');
275 return result.toString();
276 }
277 if (_valueType == ValueType.Map) {
278 final result = StringBuffer();
279 result.write('{');
280 for (var i = 0; i < length; i++) {
281 result.write(jsonEncode(_keyForIndex(i)));
282 result.write(':');
283 result.write(_valueForIndex(i).json);
284 if (i < length - 1) {
285 result.write(',');
286 }
287 }
288 result.write('}');
289 return result.toString();
290 }
James Kuszmaul8e62b022022-03-22 09:33:25 -0700291 throw UnsupportedError(
292 'Type: $_valueType is not supported for JSON conversion');
Austin Schuh272c6132020-11-14 16:37:52 -0800293 }
294
295 /// Computes the indirect offset of the value.
296 ///
297 /// To optimize for the more common case of being called only once, this
298 /// value is not cached. Callers that need to use it more than once should
299 /// cache the return value in a local variable.
300 int get _indirect {
301 final step = _readUInt(_offset, _parentWidth);
302 return _offset - step;
303 }
304
305 int _readInt(int offset, BitWidth width) {
306 _validateOffset(offset, width);
307 if (width == BitWidth.width8) {
308 return _buffer.getInt8(offset);
309 }
310 if (width == BitWidth.width16) {
311 return _buffer.getInt16(offset, Endian.little);
312 }
313 if (width == BitWidth.width32) {
314 return _buffer.getInt32(offset, Endian.little);
315 }
316 return _buffer.getInt64(offset, Endian.little);
317 }
318
319 int _readUInt(int offset, BitWidth width) {
320 _validateOffset(offset, width);
321 if (width == BitWidth.width8) {
322 return _buffer.getUint8(offset);
323 }
324 if (width == BitWidth.width16) {
325 return _buffer.getUint16(offset, Endian.little);
326 }
327 if (width == BitWidth.width32) {
328 return _buffer.getUint32(offset, Endian.little);
329 }
330 return _buffer.getUint64(offset, Endian.little);
331 }
332
333 double _readFloat(int offset, BitWidth width) {
334 _validateOffset(offset, width);
335 if (width.index < BitWidth.width32.index) {
336 throw StateError('Bad width: $width');
337 }
338
339 if (width == BitWidth.width32) {
340 return _buffer.getFloat32(offset, Endian.little);
341 }
342
343 return _buffer.getFloat64(offset, Endian.little);
344 }
345
346 void _validateOffset(int offset, BitWidth width) {
James Kuszmaul8e62b022022-03-22 09:33:25 -0700347 if (_offset < 0 ||
348 _buffer.lengthInBytes <= offset + width.index ||
349 offset & (BitWidthUtil.toByteWidth(width) - 1) != 0) {
Austin Schuh272c6132020-11-14 16:37:52 -0800350 throw StateError('Bad offset: $offset, width: $width');
351 }
352 }
353
James Kuszmaul8e62b022022-03-22 09:33:25 -0700354 int? _keyIndex(String key) {
Austin Schuh272c6132020-11-14 16:37:52 -0800355 final input = utf8.encode(key);
356 final keysVectorOffset = _indirect - _byteWidth * 3;
James Kuszmaul8e62b022022-03-22 09:33:25 -0700357 final indirectOffset = keysVectorOffset -
358 _readUInt(keysVectorOffset, BitWidthUtil.fromByteWidth(_byteWidth));
359 final byteWidth = _readUInt(
360 keysVectorOffset + _byteWidth, BitWidthUtil.fromByteWidth(_byteWidth));
Austin Schuh272c6132020-11-14 16:37:52 -0800361 var low = 0;
362 var high = length - 1;
363 while (low <= high) {
364 final mid = (high + low) >> 1;
365 final dif = _diffKeys(input, mid, indirectOffset, byteWidth);
366 if (dif == 0) return mid;
367 if (dif < 0) {
368 high = mid - 1;
369 } else {
370 low = mid + 1;
371 }
372 }
373 return null;
374 }
375
James Kuszmaul8e62b022022-03-22 09:33:25 -0700376 int _diffKeys(List<int> input, int index, int indirectOffset, int byteWidth) {
377 final keyOffset = indirectOffset + index * byteWidth;
378 final keyIndirectOffset =
379 keyOffset - _readUInt(keyOffset, BitWidthUtil.fromByteWidth(byteWidth));
Austin Schuh272c6132020-11-14 16:37:52 -0800380 for (var i = 0; i < input.length; i++) {
381 final dif = input[i] - _buffer.getUint8(keyIndirectOffset + i);
382 if (dif != 0) {
383 return dif;
384 }
385 }
386 return (_buffer.getUint8(keyIndirectOffset + input.length) == 0) ? 0 : -1;
387 }
388
389 Reference _valueForIndexWithKey(int index, String key) {
390 final indirect = _indirect;
391 final elementOffset = indirect + index * _byteWidth;
392 final packedType = _buffer.getUint8(indirect + length * _byteWidth + index);
James Kuszmaul8e62b022022-03-22 09:33:25 -0700393 return Reference._(_buffer, elementOffset,
394 BitWidthUtil.fromByteWidth(_byteWidth), packedType, "$_path/$key");
Austin Schuh272c6132020-11-14 16:37:52 -0800395 }
396
397 Reference _valueForIndex(int index) {
398 final indirect = _indirect;
399 final elementOffset = indirect + index * _byteWidth;
400 final packedType = _buffer.getUint8(indirect + length * _byteWidth + index);
James Kuszmaul8e62b022022-03-22 09:33:25 -0700401 return Reference._(_buffer, elementOffset,
402 BitWidthUtil.fromByteWidth(_byteWidth), packedType, "$_path/[$index]");
Austin Schuh272c6132020-11-14 16:37:52 -0800403 }
404
405 String _keyForIndex(int index) {
406 final keysVectorOffset = _indirect - _byteWidth * 3;
James Kuszmaul8e62b022022-03-22 09:33:25 -0700407 final indirectOffset = keysVectorOffset -
408 _readUInt(keysVectorOffset, BitWidthUtil.fromByteWidth(_byteWidth));
409 final byteWidth = _readUInt(
410 keysVectorOffset + _byteWidth, BitWidthUtil.fromByteWidth(_byteWidth));
Austin Schuh272c6132020-11-14 16:37:52 -0800411 final keyOffset = indirectOffset + index * byteWidth;
James Kuszmaul8e62b022022-03-22 09:33:25 -0700412 final keyIndirectOffset =
413 keyOffset - _readUInt(keyOffset, BitWidthUtil.fromByteWidth(byteWidth));
Austin Schuh272c6132020-11-14 16:37:52 -0800414 var length = 0;
415 while (_buffer.getUint8(keyIndirectOffset + length) != 0) {
416 length += 1;
417 }
418 return utf8.decode(_buffer.buffer.asUint8List(keyIndirectOffset, length));
419 }
Austin Schuh272c6132020-11-14 16:37:52 -0800420}
421
James Kuszmaul8e62b022022-03-22 09:33:25 -0700422class _VectorIterator
423 with IterableMixin<Reference>
424 implements Iterator<Reference> {
Austin Schuh272c6132020-11-14 16:37:52 -0800425 final Reference _vector;
James Kuszmaul8e62b022022-03-22 09:33:25 -0700426 int index = -1;
Austin Schuh272c6132020-11-14 16:37:52 -0800427
James Kuszmaul8e62b022022-03-22 09:33:25 -0700428 _VectorIterator(this._vector);
Austin Schuh272c6132020-11-14 16:37:52 -0800429
430 @override
431 Reference get current => _vector[index];
432
433 @override
434 bool moveNext() {
435 index++;
436 return index < _vector.length;
437 }
438
439 @override
440 Iterator<Reference> get iterator => this;
441}
442
443class _MapKeyIterator with IterableMixin<String> implements Iterator<String> {
444 final Reference _map;
James Kuszmaul8e62b022022-03-22 09:33:25 -0700445 int index = -1;
Austin Schuh272c6132020-11-14 16:37:52 -0800446
James Kuszmaul8e62b022022-03-22 09:33:25 -0700447 _MapKeyIterator(this._map);
Austin Schuh272c6132020-11-14 16:37:52 -0800448
449 @override
450 String get current => _map._keyForIndex(index);
451
452 @override
453 bool moveNext() {
454 index++;
455 return index < _map.length;
456 }
457
458 @override
459 Iterator<String> get iterator => this;
460}
461
James Kuszmaul8e62b022022-03-22 09:33:25 -0700462class _MapValueIterator
463 with IterableMixin<Reference>
464 implements Iterator<Reference> {
Austin Schuh272c6132020-11-14 16:37:52 -0800465 final Reference _map;
James Kuszmaul8e62b022022-03-22 09:33:25 -0700466 int index = -1;
Austin Schuh272c6132020-11-14 16:37:52 -0800467
James Kuszmaul8e62b022022-03-22 09:33:25 -0700468 _MapValueIterator(this._map);
Austin Schuh272c6132020-11-14 16:37:52 -0800469
470 @override
471 Reference get current => _map._valueForIndex(index);
472
473 @override
474 bool moveNext() {
475 index++;
476 return index < _map.length;
477 }
478
479 @override
480 Iterator<Reference> get iterator => this;
481}