• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import '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;
14  int _byteWidth;
15  ValueType _valueType;
16  int _length;
17
18  Reference._(this._buffer, this._offset, this._parentWidth, int packedType, this._path) {
19    _byteWidth = 1 << (packedType & 3);
20    _valueType = ValueTypeUtils.fromInt(packedType >> 2);
21  }
22
23  /// Use this method to access the root value of a FlexBuffer.
24  static Reference fromBuffer(ByteBuffer buffer) {
25    final len = buffer.lengthInBytes;
26    if (len < 3) {
27      throw UnsupportedError('Buffer needs to be bigger than 3');
28    }
29    final byteData = ByteData.view(buffer);
30    final byteWidth = byteData.getUint8(len - 1);
31    final packedType = byteData.getUint8(len - 2);
32    final offset = len - byteWidth - 2;
33    return Reference._(ByteData.view(buffer), offset, BitWidthUtil.fromByteWidth(byteWidth), packedType, "/");
34  }
35
36  /// Returns true if the underlying value is null.
37  bool get isNull => _valueType == ValueType.Null;
38  /// Returns true if the underlying value can be represented as [num].
39  bool get isNum => ValueTypeUtils.isNumber(_valueType) || ValueTypeUtils.isIndirectNumber(_valueType);
40  /// Returns true if the underlying value was encoded as a float (direct or indirect).
41  bool get isDouble => _valueType == ValueType.Float || _valueType == ValueType.IndirectFloat;
42  /// Returns true if the underlying value was encoded as an int or uint (direct or indirect).
43  bool get isInt => isNum && !isDouble;
44  /// Returns true if the underlying value was encoded as a string or a key.
45  bool get isString => _valueType == ValueType.String || _valueType == ValueType.Key;
46  /// Returns true if the underlying value was encoded as a bool.
47  bool get isBool => _valueType == ValueType.Bool;
48  /// Returns true if the underlying value was encoded as a blob.
49  bool get isBlob => _valueType == ValueType.Blob;
50  /// Returns true if the underlying value points to a vector.
51  bool get isVector => ValueTypeUtils.isAVector(_valueType);
52  /// Returns true if the underlying value points to a map.
53  bool get isMap => _valueType == ValueType.Map;
54
55  /// If this [isBool], returns the bool value. Otherwise, returns null.
56  bool get boolValue {
57    if(_valueType == ValueType.Bool) {
58      return _readInt(_offset, _parentWidth) != 0;
59    }
60    return null;
61  }
62
63  /// Returns an [int], if the underlying value can be represented as an int.
64  ///
65  /// Otherwise returns [null].
66  int get intValue {
67    if (_valueType == ValueType.Int) {
68      return _readInt(_offset, _parentWidth);
69    }
70    if (_valueType == ValueType.UInt) {
71      return _readUInt(_offset, _parentWidth);
72    }
73    if (_valueType == ValueType.IndirectInt) {
74      return _readInt(_indirect, BitWidthUtil.fromByteWidth(_byteWidth));
75    }
76    if (_valueType == ValueType.IndirectUInt) {
77      return _readUInt(_indirect, BitWidthUtil.fromByteWidth(_byteWidth));
78    }
79    return null;
80  }
81
82  /// Returns [double], if the underlying value [isDouble].
83  ///
84  /// Otherwise returns [null].
85  double get doubleValue {
86    if (_valueType == ValueType.Float) {
87      return _readFloat(_offset, _parentWidth);
88    }
89    if (_valueType == ValueType.IndirectFloat) {
90      return _readFloat(_indirect, BitWidthUtil.fromByteWidth(_byteWidth));
91    }
92    return null;
93  }
94
95  /// Returns [num], if the underlying value is numeric, be it int uint, or float (direct or indirect).
96  ///
97  /// Otherwise returns [null].
98  num get numValue => doubleValue ?? intValue;
99
100  /// Returns [String] value or null otherwise.
101  ///
102  /// This method performers a utf8 decoding, as FlexBuffers format stores strings in utf8 encoding.
103  String get stringValue {
104    if (_valueType == ValueType.String || _valueType == ValueType.Key) {
105      return utf8.decode(_buffer.buffer.asUint8List(_indirect, length));
106    }
107    return null;
108  }
109
110  /// Returns [Uint8List] value or null otherwise.
111  Uint8List get blobValue {
112    if (_valueType == ValueType.Blob) {
113      return _buffer.buffer.asUint8List(_indirect, length);
114    }
115    return null;
116  }
117
118  /// Can be used with an [int] or a [String] value for key.
119  /// If the underlying value in FlexBuffer is a vector, then use [int] for access.
120  /// If the underlying value in FlexBuffer is a map, then use [String] for access.
121  /// Returns [Reference] value. Throws an exception when [key] is not applicable.
122  Reference operator [](Object key) {
123    if (key is int && ValueTypeUtils.isAVector(_valueType)) {
124      final index = key;
125      if(index >= length || index < 0) {
126        throw ArgumentError('Key: [$key] is not applicable on: $_path of: $_valueType length: $length');
127      }
128      final elementOffset = _indirect + index * _byteWidth;
129      final reference = Reference._(_buffer, elementOffset, BitWidthUtil.fromByteWidth(_byteWidth), 0, "$_path[$index]");
130      reference._byteWidth = 1;
131      if (ValueTypeUtils.isTypedVector(_valueType)) {
132        reference._valueType = ValueTypeUtils.typedVectorElementType(_valueType);
133        return reference;
134      }
135      if(ValueTypeUtils.isFixedTypedVector(_valueType)) {
136        reference._valueType = ValueTypeUtils.fixedTypedVectorElementType(_valueType);
137        return reference;
138      }
139      final packedType = _buffer.getUint8(_indirect + length * _byteWidth + index);
140      return Reference._(_buffer, elementOffset, BitWidthUtil.fromByteWidth(_byteWidth), packedType, "$_path[$index]");
141    }
142    if (key is String && _valueType == ValueType.Map) {
143      final index = _keyIndex(key);
144      if (index != null) {
145        return _valueForIndexWithKey(index, key);
146      }
147    }
148    throw ArgumentError('Key: [$key] is not applicable on: $_path of: $_valueType');
149  }
150
151  /// Get an iterable if the underlying flexBuffer value is a vector.
152  /// Otherwise throws an exception.
153  Iterable<Reference> get vectorIterable {
154    if(isVector == false) {
155      throw UnsupportedError('Value is not a vector. It is: $_valueType');
156    }
157    return _VectorIterator(this);
158  }
159
160  /// Get an iterable for keys if the underlying flexBuffer value is a map.
161  /// Otherwise throws an exception.
162  Iterable<String> get mapKeyIterable {
163    if(isMap == false) {
164      throw UnsupportedError('Value is not a map. It is: $_valueType');
165    }
166    return _MapKeyIterator(this);
167  }
168
169  /// Get an iterable for values if the underlying flexBuffer value is a map.
170  /// Otherwise throws an exception.
171  Iterable<Reference> get mapValueIterable {
172    if(isMap == false) {
173      throw UnsupportedError('Value is not a map. It is: $_valueType');
174    }
175    return _MapValueIterator(this);
176  }
177
178  /// Returns the length of the the underlying FlexBuffer value.
179  /// If the underlying value is [null] the length is 0.
180  /// If the underlying value is a number, or a bool, the length is 1.
181  /// If the underlying value is a vector, or map, the length reflects number of elements / element pairs.
182  /// 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).
183  int get length {
184    if (_length != null) {
185      return _length;
186    }
187    // needs to be checked before more generic isAVector
188    if(ValueTypeUtils.isFixedTypedVector(_valueType)) {
189      _length = ValueTypeUtils.fixedTypedVectorElementSize(_valueType);
190    } else if(_valueType == ValueType.Blob || ValueTypeUtils.isAVector(_valueType) || _valueType == ValueType.Map){
191      _length = _readUInt(_indirect - _byteWidth, BitWidthUtil.fromByteWidth(_byteWidth));
192    } else if (_valueType == ValueType.Null) {
193      _length = 0;
194    } else if (_valueType == ValueType.String) {
195      final indirect = _indirect;
196      var size_byte_width = _byteWidth;
197      var size = _readUInt(indirect - size_byte_width, BitWidthUtil.fromByteWidth(size_byte_width));
198      while (_buffer.getInt8(indirect + size) != 0) {
199        size_byte_width <<= 1;
200        size = _readUInt(indirect - size_byte_width, BitWidthUtil.fromByteWidth(size_byte_width));
201      }
202      _length = size;
203    } else if (_valueType == ValueType.Key) {
204      final indirect = _indirect;
205      var size = 1;
206      while (_buffer.getInt8(indirect + size) != 0) {
207        size += 1;
208      }
209      _length = size;
210    } else {
211      _length = 1;
212    }
213    return _length;
214  }
215
216
217  /// Returns a minified JSON representation of the underlying FlexBuffer value.
218  ///
219  /// This method involves materializing the entire object tree, which may be
220  /// expensive. It is more efficient to work with [Reference] and access only the needed data.
221  /// Blob values are represented as base64 encoded string.
222  String get json {
223    if(_valueType == ValueType.Bool) {
224      return boolValue ? 'true' : 'false';
225    }
226    if (_valueType == ValueType.Null) {
227      return 'null';
228    }
229    if(ValueTypeUtils.isNumber(_valueType)) {
230      return jsonEncode(numValue);
231    }
232    if (_valueType == ValueType.String) {
233      return jsonEncode(stringValue);
234    }
235    if (_valueType == ValueType.Blob) {
236      return jsonEncode(base64Encode(blobValue));
237    }
238    if (ValueTypeUtils.isAVector(_valueType)) {
239      final result = StringBuffer();
240      result.write('[');
241      for (var i = 0; i < length; i++) {
242        result.write(this[i].json);
243        if (i < length - 1) {
244          result.write(',');
245        }
246      }
247      result.write(']');
248      return result.toString();
249    }
250    if (_valueType == ValueType.Map) {
251      final result = StringBuffer();
252      result.write('{');
253      for (var i = 0; i < length; i++) {
254        result.write(jsonEncode(_keyForIndex(i)));
255        result.write(':');
256        result.write(_valueForIndex(i).json);
257        if (i < length - 1) {
258          result.write(',');
259        }
260      }
261      result.write('}');
262      return result.toString();
263    }
264    throw UnsupportedError('Type: $_valueType is not supported for JSON conversion');
265  }
266
267  /// Computes the indirect offset of the value.
268  ///
269  /// To optimize for the more common case of being called only once, this
270  /// value is not cached. Callers that need to use it more than once should
271  /// cache the return value in a local variable.
272  int get _indirect {
273    final step = _readUInt(_offset, _parentWidth);
274    return _offset - step;
275  }
276
277  int _readInt(int offset, BitWidth width) {
278    _validateOffset(offset, width);
279    if (width == BitWidth.width8) {
280      return _buffer.getInt8(offset);
281    }
282    if (width == BitWidth.width16) {
283      return _buffer.getInt16(offset, Endian.little);
284    }
285    if (width == BitWidth.width32) {
286      return _buffer.getInt32(offset, Endian.little);
287    }
288    return _buffer.getInt64(offset, Endian.little);
289  }
290
291  int _readUInt(int offset, BitWidth width) {
292    _validateOffset(offset, width);
293    if (width == BitWidth.width8) {
294      return _buffer.getUint8(offset);
295    }
296    if (width == BitWidth.width16) {
297      return _buffer.getUint16(offset, Endian.little);
298    }
299    if (width == BitWidth.width32) {
300      return _buffer.getUint32(offset, Endian.little);
301    }
302    return _buffer.getUint64(offset, Endian.little);
303  }
304
305  double _readFloat(int offset, BitWidth width) {
306    _validateOffset(offset, width);
307    if (width.index < BitWidth.width32.index) {
308      throw StateError('Bad width: $width');
309    }
310
311    if (width == BitWidth.width32) {
312      return _buffer.getFloat32(offset, Endian.little);
313    }
314
315    return _buffer.getFloat64(offset, Endian.little);
316  }
317
318  void _validateOffset(int offset, BitWidth width) {
319    if (_offset < 0 || _buffer.lengthInBytes <= offset + width.index || offset & (BitWidthUtil.toByteWidth(width) - 1) != 0) {
320      throw StateError('Bad offset: $offset, width: $width');
321    }
322  }
323
324  int _keyIndex(String key) {
325    final input = utf8.encode(key);
326    final keysVectorOffset = _indirect - _byteWidth * 3;
327    final indirectOffset = keysVectorOffset - _readUInt(keysVectorOffset, BitWidthUtil.fromByteWidth(_byteWidth));
328    final byteWidth = _readUInt(keysVectorOffset + _byteWidth, BitWidthUtil.fromByteWidth(_byteWidth));
329    var low = 0;
330    var high = length - 1;
331    while (low <= high) {
332      final mid = (high + low) >> 1;
333      final dif = _diffKeys(input, mid, indirectOffset, byteWidth);
334      if (dif == 0) return mid;
335      if (dif < 0) {
336        high = mid - 1;
337      } else {
338        low = mid + 1;
339      }
340    }
341    return null;
342  }
343
344  int _diffKeys(List<int> input, int index, int indirect_offset, int byteWidth) {
345    final keyOffset = indirect_offset + index * byteWidth;
346    final keyIndirectOffset = keyOffset - _readUInt(keyOffset, BitWidthUtil.fromByteWidth(byteWidth));
347    for (var i = 0; i < input.length; i++) {
348      final dif = input[i] - _buffer.getUint8(keyIndirectOffset + i);
349      if (dif != 0) {
350        return dif;
351      }
352    }
353    return (_buffer.getUint8(keyIndirectOffset + input.length) == 0) ? 0 : -1;
354  }
355
356  Reference _valueForIndexWithKey(int index, String key) {
357    final indirect = _indirect;
358    final elementOffset = indirect + index * _byteWidth;
359    final packedType = _buffer.getUint8(indirect + length * _byteWidth + index);
360    return Reference._(_buffer, elementOffset, BitWidthUtil.fromByteWidth(_byteWidth), packedType, "$_path/$key");
361  }
362
363  Reference _valueForIndex(int index) {
364    final indirect = _indirect;
365    final elementOffset = indirect + index * _byteWidth;
366    final packedType = _buffer.getUint8(indirect + length * _byteWidth + index);
367    return Reference._(_buffer, elementOffset, BitWidthUtil.fromByteWidth(_byteWidth), packedType, "$_path/[$index]");
368  }
369
370  String _keyForIndex(int index) {
371    final keysVectorOffset = _indirect - _byteWidth * 3;
372    final indirectOffset = keysVectorOffset - _readUInt(keysVectorOffset, BitWidthUtil.fromByteWidth(_byteWidth));
373    final byteWidth = _readUInt(keysVectorOffset + _byteWidth, BitWidthUtil.fromByteWidth(_byteWidth));
374    final keyOffset = indirectOffset + index * byteWidth;
375    final keyIndirectOffset = keyOffset - _readUInt(keyOffset, BitWidthUtil.fromByteWidth(byteWidth));
376    var length = 0;
377    while (_buffer.getUint8(keyIndirectOffset + length) != 0) {
378      length += 1;
379    }
380    return utf8.decode(_buffer.buffer.asUint8List(keyIndirectOffset, length));
381  }
382
383}
384
385class _VectorIterator with IterableMixin<Reference> implements Iterator<Reference> {
386  final Reference _vector;
387  int index;
388
389  _VectorIterator(this._vector) {
390    index = -1;
391  }
392
393  @override
394  Reference get current => _vector[index];
395
396  @override
397  bool moveNext() {
398    index++;
399    return index < _vector.length;
400  }
401
402  @override
403  Iterator<Reference> get iterator => this;
404}
405
406class _MapKeyIterator with IterableMixin<String> implements Iterator<String> {
407  final Reference _map;
408  int index;
409
410  _MapKeyIterator(this._map) {
411    index = -1;
412  }
413
414  @override
415  String get current => _map._keyForIndex(index);
416
417  @override
418  bool moveNext() {
419    index++;
420    return index < _map.length;
421  }
422
423  @override
424  Iterator<String> get iterator => this;
425}
426
427class _MapValueIterator with IterableMixin<Reference> implements Iterator<Reference> {
428  final Reference _map;
429  int index;
430
431  _MapValueIterator(this._map) {
432    index = -1;
433  }
434
435  @override
436  Reference get current => _map._valueForIndex(index);
437
438  @override
439  bool moveNext() {
440    index++;
441    return index < _map.length;
442  }
443
444  @override
445  Iterator<Reference> get iterator => this;
446}
447