xref: /aosp_15_r20/external/flatbuffers/dart/lib/src/reference.dart (revision 890232f25432b36107d06881e0a25aaa6b473652)
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  final int _byteWidth;
15  final ValueType _valueType;
16  int? _length;
17
18  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);
23
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;
34    return Reference._(ByteData.view(buffer), offset,
35        BitWidthUtil.fromByteWidth(byteWidth), packedType, "/");
36  }
37
38  /// Returns true if the underlying value is null.
39  bool get isNull => _valueType == ValueType.Null;
40
41  /// Returns true if the underlying value can be represented as [num].
42  bool get isNum =>
43      ValueTypeUtils.isNumber(_valueType) ||
44      ValueTypeUtils.isIndirectNumber(_valueType);
45
46  /// Returns true if the underlying value was encoded as a float (direct or indirect).
47  bool get isDouble =>
48      _valueType == ValueType.Float || _valueType == ValueType.IndirectFloat;
49
50  /// Returns true if the underlying value was encoded as an int or uint (direct or indirect).
51  bool get isInt => isNum && !isDouble;
52
53  /// Returns true if the underlying value was encoded as a string or a key.
54  bool get isString =>
55      _valueType == ValueType.String || _valueType == ValueType.Key;
56
57  /// Returns true if the underlying value was encoded as a bool.
58  bool get isBool => _valueType == ValueType.Bool;
59
60  /// Returns true if the underlying value was encoded as a blob.
61  bool get isBlob => _valueType == ValueType.Blob;
62
63  /// Returns true if the underlying value points to a vector.
64  bool get isVector => ValueTypeUtils.isAVector(_valueType);
65
66  /// 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.
70  bool? get boolValue {
71    if (_valueType == ValueType.Bool) {
72      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].
80  int? get intValue {
81    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].
99  double? get doubleValue {
100    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].
112  num? get numValue => doubleValue ?? intValue;
113
114  /// Returns [String] value or null otherwise.
115  ///
116  /// This method performers a utf8 decoding, as FlexBuffers format stores strings in utf8 encoding.
117  String? get stringValue {
118    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.
125  Uint8List? get blobValue {
126    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;
139      if (index >= length || index < 0) {
140        throw ArgumentError(
141            'Key: [$key] is not applicable on: $_path of: $_valueType length: $length');
142      }
143      final elementOffset = _indirect + index * _byteWidth;
144      int packedType = 0;
145      int? byteWidth;
146      ValueType? valueType;
147      if (ValueTypeUtils.isTypedVector(_valueType)) {
148        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);
155      }
156      return Reference._(
157          _buffer,
158          elementOffset,
159          BitWidthUtil.fromByteWidth(_byteWidth),
160          packedType,
161          "$_path[$index]",
162          byteWidth,
163          valueType);
164    }
165    if (key is String && _valueType == ValueType.Map) {
166      final index = _keyIndex(key);
167      if (index != null) {
168        return _valueForIndexWithKey(index, key);
169      }
170    }
171    throw ArgumentError(
172        'Key: [$key] is not applicable on: $_path of: $_valueType');
173  }
174
175  /// Get an iterable if the underlying flexBuffer value is a vector.
176  /// Otherwise throws an exception.
177  Iterable<Reference> get vectorIterable {
178    if (isVector == false) {
179      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 {
187    if (isMap == false) {
188      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 {
196    if (isMap == false) {
197      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 {
208    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;
239      }
240    }
241    return _length!;
242  }
243
244  /// 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 {
250    if (_valueType == ValueType.Bool) {
251      return boolValue! ? 'true' : 'false';
252    }
253    if (_valueType == ValueType.Null) {
254      return 'null';
255    }
256    if (ValueTypeUtils.isNumber(_valueType)) {
257      return jsonEncode(numValue);
258    }
259    if (_valueType == ValueType.String) {
260      return jsonEncode(stringValue);
261    }
262    if (_valueType == ValueType.Blob) {
263      return jsonEncode(base64Encode(blobValue!));
264    }
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    }
291    throw UnsupportedError(
292        'Type: $_valueType is not supported for JSON conversion');
293  }
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) {
347    if (_offset < 0 ||
348        _buffer.lengthInBytes <= offset + width.index ||
349        offset & (BitWidthUtil.toByteWidth(width) - 1) != 0) {
350      throw StateError('Bad offset: $offset, width: $width');
351    }
352  }
353
354  int? _keyIndex(String key) {
355    final input = utf8.encode(key);
356    final keysVectorOffset = _indirect - _byteWidth * 3;
357    final indirectOffset = keysVectorOffset -
358        _readUInt(keysVectorOffset, BitWidthUtil.fromByteWidth(_byteWidth));
359    final byteWidth = _readUInt(
360        keysVectorOffset + _byteWidth, BitWidthUtil.fromByteWidth(_byteWidth));
361    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
376  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));
380    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);
393    return Reference._(_buffer, elementOffset,
394        BitWidthUtil.fromByteWidth(_byteWidth), packedType, "$_path/$key");
395  }
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);
401    return Reference._(_buffer, elementOffset,
402        BitWidthUtil.fromByteWidth(_byteWidth), packedType, "$_path/[$index]");
403  }
404
405  String _keyForIndex(int index) {
406    final keysVectorOffset = _indirect - _byteWidth * 3;
407    final indirectOffset = keysVectorOffset -
408        _readUInt(keysVectorOffset, BitWidthUtil.fromByteWidth(_byteWidth));
409    final byteWidth = _readUInt(
410        keysVectorOffset + _byteWidth, BitWidthUtil.fromByteWidth(_byteWidth));
411    final keyOffset = indirectOffset + index * byteWidth;
412    final keyIndirectOffset =
413        keyOffset - _readUInt(keyOffset, BitWidthUtil.fromByteWidth(byteWidth));
414    var length = 0;
415    while (_buffer.getUint8(keyIndirectOffset + length) != 0) {
416      length += 1;
417    }
418    return utf8.decode(_buffer.buffer.asUint8List(keyIndirectOffset, length));
419  }
420}
421
422class _VectorIterator
423    with IterableMixin<Reference>
424    implements Iterator<Reference> {
425  final Reference _vector;
426  int index = -1;
427
428  _VectorIterator(this._vector);
429
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;
445  int index = -1;
446
447  _MapKeyIterator(this._map);
448
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
462class _MapValueIterator
463    with IterableMixin<Reference>
464    implements Iterator<Reference> {
465  final Reference _map;
466  int index = -1;
467
468  _MapValueIterator(this._map);
469
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}
482