import 'dart:collection'; import 'dart:convert'; import 'dart:typed_data'; import 'types.dart'; /// Main class to read a value out of a FlexBuffer. /// /// This class let you access values stored in the buffer in a lazy fashion. class Reference { final ByteData _buffer; final int _offset; final BitWidth _parentWidth; final String _path; final int _byteWidth; final ValueType _valueType; int? _length; Reference._( this._buffer, this._offset, this._parentWidth, int packedType, this._path, [int? byteWidth, ValueType? valueType]) : _byteWidth = byteWidth ?? 1 << (packedType & 3), _valueType = valueType ?? ValueTypeUtils.fromInt(packedType >> 2); /// Use this method to access the root value of a FlexBuffer. static Reference fromBuffer(ByteBuffer buffer) { final len = buffer.lengthInBytes; if (len < 3) { throw UnsupportedError('Buffer needs to be bigger than 3'); } final byteData = ByteData.view(buffer); final byteWidth = byteData.getUint8(len - 1); final packedType = byteData.getUint8(len - 2); final offset = len - byteWidth - 2; return Reference._(ByteData.view(buffer), offset, BitWidthUtil.fromByteWidth(byteWidth), packedType, "/"); } /// Returns true if the underlying value is null. bool get isNull => _valueType == ValueType.Null; /// Returns true if the underlying value can be represented as [num]. bool get isNum => ValueTypeUtils.isNumber(_valueType) || ValueTypeUtils.isIndirectNumber(_valueType); /// Returns true if the underlying value was encoded as a float (direct or indirect). bool get isDouble => _valueType == ValueType.Float || _valueType == ValueType.IndirectFloat; /// Returns true if the underlying value was encoded as an int or uint (direct or indirect). bool get isInt => isNum && !isDouble; /// Returns true if the underlying value was encoded as a string or a key. bool get isString => _valueType == ValueType.String || _valueType == ValueType.Key; /// Returns true if the underlying value was encoded as a bool. bool get isBool => _valueType == ValueType.Bool; /// Returns true if the underlying value was encoded as a blob. bool get isBlob => _valueType == ValueType.Blob; /// Returns true if the underlying value points to a vector. bool get isVector => ValueTypeUtils.isAVector(_valueType); /// Returns true if the underlying value points to a map. bool get isMap => _valueType == ValueType.Map; /// If this [isBool], returns the bool value. Otherwise, returns null. bool? get boolValue { if (_valueType == ValueType.Bool) { return _readInt(_offset, _parentWidth) != 0; } return null; } /// Returns an [int], if the underlying value can be represented as an int. /// /// Otherwise returns [null]. int? get intValue { if (_valueType == ValueType.Int) { return _readInt(_offset, _parentWidth); } if (_valueType == ValueType.UInt) { return _readUInt(_offset, _parentWidth); } if (_valueType == ValueType.IndirectInt) { return _readInt(_indirect, BitWidthUtil.fromByteWidth(_byteWidth)); } if (_valueType == ValueType.IndirectUInt) { return _readUInt(_indirect, BitWidthUtil.fromByteWidth(_byteWidth)); } return null; } /// Returns [double], if the underlying value [isDouble]. /// /// Otherwise returns [null]. double? get doubleValue { if (_valueType == ValueType.Float) { return _readFloat(_offset, _parentWidth); } if (_valueType == ValueType.IndirectFloat) { return _readFloat(_indirect, BitWidthUtil.fromByteWidth(_byteWidth)); } return null; } /// Returns [num], if the underlying value is numeric, be it int uint, or float (direct or indirect). /// /// Otherwise returns [null]. num? get numValue => doubleValue ?? intValue; /// Returns [String] value or null otherwise. /// /// This method performers a utf8 decoding, as FlexBuffers format stores strings in utf8 encoding. String? get stringValue { if (_valueType == ValueType.String || _valueType == ValueType.Key) { return utf8.decode(_buffer.buffer.asUint8List(_indirect, length)); } return null; } /// Returns [Uint8List] value or null otherwise. Uint8List? get blobValue { if (_valueType == ValueType.Blob) { return _buffer.buffer.asUint8List(_indirect, length); } return null; } /// Can be used with an [int] or a [String] value for key. /// If the underlying value in FlexBuffer is a vector, then use [int] for access. /// If the underlying value in FlexBuffer is a map, then use [String] for access. /// Returns [Reference] value. Throws an exception when [key] is not applicable. Reference operator [](Object key) { if (key is int && ValueTypeUtils.isAVector(_valueType)) { final index = key; if (index >= length || index < 0) { throw ArgumentError( 'Key: [$key] is not applicable on: $_path of: $_valueType length: $length'); } final elementOffset = _indirect + index * _byteWidth; int packedType = 0; int? byteWidth; ValueType? valueType; if (ValueTypeUtils.isTypedVector(_valueType)) { byteWidth = 1; valueType = ValueTypeUtils.typedVectorElementType(_valueType); } else if (ValueTypeUtils.isFixedTypedVector(_valueType)) { byteWidth = 1; valueType = ValueTypeUtils.fixedTypedVectorElementType(_valueType); } else { packedType = _buffer.getUint8(_indirect + length * _byteWidth + index); } return Reference._( _buffer, elementOffset, BitWidthUtil.fromByteWidth(_byteWidth), packedType, "$_path[$index]", byteWidth, valueType); } if (key is String && _valueType == ValueType.Map) { final index = _keyIndex(key); if (index != null) { return _valueForIndexWithKey(index, key); } } throw ArgumentError( 'Key: [$key] is not applicable on: $_path of: $_valueType'); } /// Get an iterable if the underlying flexBuffer value is a vector. /// Otherwise throws an exception. Iterable get vectorIterable { if (isVector == false) { throw UnsupportedError('Value is not a vector. It is: $_valueType'); } return _VectorIterator(this); } /// Get an iterable for keys if the underlying flexBuffer value is a map. /// Otherwise throws an exception. Iterable get mapKeyIterable { if (isMap == false) { throw UnsupportedError('Value is not a map. It is: $_valueType'); } return _MapKeyIterator(this); } /// Get an iterable for values if the underlying flexBuffer value is a map. /// Otherwise throws an exception. Iterable get mapValueIterable { if (isMap == false) { throw UnsupportedError('Value is not a map. It is: $_valueType'); } return _MapValueIterator(this); } /// Returns the length of the the underlying FlexBuffer value. /// If the underlying value is [null] the length is 0. /// If the underlying value is a number, or a bool, the length is 1. /// If the underlying value is a vector, or map, the length reflects number of elements / element pairs. /// 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). int get length { if (_length == null) { // needs to be checked before more generic isAVector if (ValueTypeUtils.isFixedTypedVector(_valueType)) { _length = ValueTypeUtils.fixedTypedVectorElementSize(_valueType); } else if (_valueType == ValueType.Blob || ValueTypeUtils.isAVector(_valueType) || _valueType == ValueType.Map) { _length = _readUInt( _indirect - _byteWidth, BitWidthUtil.fromByteWidth(_byteWidth)); } else if (_valueType == ValueType.Null) { _length = 0; } else if (_valueType == ValueType.String) { final indirect = _indirect; var sizeByteWidth = _byteWidth; var size = _readUInt(indirect - sizeByteWidth, BitWidthUtil.fromByteWidth(sizeByteWidth)); while (_buffer.getInt8(indirect + size) != 0) { sizeByteWidth <<= 1; size = _readUInt(indirect - sizeByteWidth, BitWidthUtil.fromByteWidth(sizeByteWidth)); } _length = size; } else if (_valueType == ValueType.Key) { final indirect = _indirect; var size = 1; while (_buffer.getInt8(indirect + size) != 0) { size += 1; } _length = size; } else { _length = 1; } } return _length!; } /// Returns a minified JSON representation of the underlying FlexBuffer value. /// /// This method involves materializing the entire object tree, which may be /// expensive. It is more efficient to work with [Reference] and access only the needed data. /// Blob values are represented as base64 encoded string. String get json { if (_valueType == ValueType.Bool) { return boolValue! ? 'true' : 'false'; } if (_valueType == ValueType.Null) { return 'null'; } if (ValueTypeUtils.isNumber(_valueType)) { return jsonEncode(numValue); } if (_valueType == ValueType.String) { return jsonEncode(stringValue); } if (_valueType == ValueType.Blob) { return jsonEncode(base64Encode(blobValue!)); } if (ValueTypeUtils.isAVector(_valueType)) { final result = StringBuffer(); result.write('['); for (var i = 0; i < length; i++) { result.write(this[i].json); if (i < length - 1) { result.write(','); } } result.write(']'); return result.toString(); } if (_valueType == ValueType.Map) { final result = StringBuffer(); result.write('{'); for (var i = 0; i < length; i++) { result.write(jsonEncode(_keyForIndex(i))); result.write(':'); result.write(_valueForIndex(i).json); if (i < length - 1) { result.write(','); } } result.write('}'); return result.toString(); } throw UnsupportedError( 'Type: $_valueType is not supported for JSON conversion'); } /// Computes the indirect offset of the value. /// /// To optimize for the more common case of being called only once, this /// value is not cached. Callers that need to use it more than once should /// cache the return value in a local variable. int get _indirect { final step = _readUInt(_offset, _parentWidth); return _offset - step; } int _readInt(int offset, BitWidth width) { _validateOffset(offset, width); if (width == BitWidth.width8) { return _buffer.getInt8(offset); } if (width == BitWidth.width16) { return _buffer.getInt16(offset, Endian.little); } if (width == BitWidth.width32) { return _buffer.getInt32(offset, Endian.little); } return _buffer.getInt64(offset, Endian.little); } int _readUInt(int offset, BitWidth width) { _validateOffset(offset, width); if (width == BitWidth.width8) { return _buffer.getUint8(offset); } if (width == BitWidth.width16) { return _buffer.getUint16(offset, Endian.little); } if (width == BitWidth.width32) { return _buffer.getUint32(offset, Endian.little); } return _buffer.getUint64(offset, Endian.little); } double _readFloat(int offset, BitWidth width) { _validateOffset(offset, width); if (width.index < BitWidth.width32.index) { throw StateError('Bad width: $width'); } if (width == BitWidth.width32) { return _buffer.getFloat32(offset, Endian.little); } return _buffer.getFloat64(offset, Endian.little); } void _validateOffset(int offset, BitWidth width) { if (_offset < 0 || _buffer.lengthInBytes <= offset + width.index || offset & (BitWidthUtil.toByteWidth(width) - 1) != 0) { throw StateError('Bad offset: $offset, width: $width'); } } int? _keyIndex(String key) { final input = utf8.encode(key); final keysVectorOffset = _indirect - _byteWidth * 3; final indirectOffset = keysVectorOffset - _readUInt(keysVectorOffset, BitWidthUtil.fromByteWidth(_byteWidth)); final byteWidth = _readUInt( keysVectorOffset + _byteWidth, BitWidthUtil.fromByteWidth(_byteWidth)); var low = 0; var high = length - 1; while (low <= high) { final mid = (high + low) >> 1; final dif = _diffKeys(input, mid, indirectOffset, byteWidth); if (dif == 0) return mid; if (dif < 0) { high = mid - 1; } else { low = mid + 1; } } return null; } int _diffKeys(List input, int index, int indirectOffset, int byteWidth) { final keyOffset = indirectOffset + index * byteWidth; final keyIndirectOffset = keyOffset - _readUInt(keyOffset, BitWidthUtil.fromByteWidth(byteWidth)); for (var i = 0; i < input.length; i++) { final dif = input[i] - _buffer.getUint8(keyIndirectOffset + i); if (dif != 0) { return dif; } } return (_buffer.getUint8(keyIndirectOffset + input.length) == 0) ? 0 : -1; } Reference _valueForIndexWithKey(int index, String key) { final indirect = _indirect; final elementOffset = indirect + index * _byteWidth; final packedType = _buffer.getUint8(indirect + length * _byteWidth + index); return Reference._(_buffer, elementOffset, BitWidthUtil.fromByteWidth(_byteWidth), packedType, "$_path/$key"); } Reference _valueForIndex(int index) { final indirect = _indirect; final elementOffset = indirect + index * _byteWidth; final packedType = _buffer.getUint8(indirect + length * _byteWidth + index); return Reference._(_buffer, elementOffset, BitWidthUtil.fromByteWidth(_byteWidth), packedType, "$_path/[$index]"); } String _keyForIndex(int index) { final keysVectorOffset = _indirect - _byteWidth * 3; final indirectOffset = keysVectorOffset - _readUInt(keysVectorOffset, BitWidthUtil.fromByteWidth(_byteWidth)); final byteWidth = _readUInt( keysVectorOffset + _byteWidth, BitWidthUtil.fromByteWidth(_byteWidth)); final keyOffset = indirectOffset + index * byteWidth; final keyIndirectOffset = keyOffset - _readUInt(keyOffset, BitWidthUtil.fromByteWidth(byteWidth)); var length = 0; while (_buffer.getUint8(keyIndirectOffset + length) != 0) { length += 1; } return utf8.decode(_buffer.buffer.asUint8List(keyIndirectOffset, length)); } } class _VectorIterator with IterableMixin implements Iterator { final Reference _vector; int index = -1; _VectorIterator(this._vector); @override Reference get current => _vector[index]; @override bool moveNext() { index++; return index < _vector.length; } @override Iterator get iterator => this; } class _MapKeyIterator with IterableMixin implements Iterator { final Reference _map; int index = -1; _MapKeyIterator(this._map); @override String get current => _map._keyForIndex(index); @override bool moveNext() { index++; return index < _map.length; } @override Iterator get iterator => this; } class _MapValueIterator with IterableMixin implements Iterator { final Reference _map; int index = -1; _MapValueIterator(this._map); @override Reference get current => _map._valueForIndex(index); @override bool moveNext() { index++; return index < _map.length; } @override Iterator get iterator => this; }