blob: 3954f0687072dcb00e82d6eee18a0378f7ae1ace [file] [log] [blame]
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;
int _byteWidth;
ValueType _valueType;
int _length;
Reference._(this._buffer, this._offset, this._parentWidth, int packedType, this._path) {
_byteWidth = 1 << (packedType & 3);
_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;
final reference = Reference._(_buffer, elementOffset, BitWidthUtil.fromByteWidth(_byteWidth), 0, "$_path[$index]");
reference._byteWidth = 1;
if (ValueTypeUtils.isTypedVector(_valueType)) {
reference._valueType = ValueTypeUtils.typedVectorElementType(_valueType);
return reference;
}
if(ValueTypeUtils.isFixedTypedVector(_valueType)) {
reference._valueType = ValueTypeUtils.fixedTypedVectorElementType(_valueType);
return reference;
}
final packedType = _buffer.getUint8(_indirect + length * _byteWidth + index);
return Reference._(_buffer, elementOffset, BitWidthUtil.fromByteWidth(_byteWidth), packedType, "$_path[$index]");
}
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<Reference> 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<String> 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<Reference> 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) {
return _length;
}
// 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 size_byte_width = _byteWidth;
var size = _readUInt(indirect - size_byte_width, BitWidthUtil.fromByteWidth(size_byte_width));
while (_buffer.getInt8(indirect + size) != 0) {
size_byte_width <<= 1;
size = _readUInt(indirect - size_byte_width, BitWidthUtil.fromByteWidth(size_byte_width));
}
_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<int> input, int index, int indirect_offset, int byteWidth) {
final keyOffset = indirect_offset + 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<Reference> implements Iterator<Reference> {
final Reference _vector;
int index;
_VectorIterator(this._vector) {
index = -1;
}
@override
Reference get current => _vector[index];
@override
bool moveNext() {
index++;
return index < _vector.length;
}
@override
Iterator<Reference> get iterator => this;
}
class _MapKeyIterator with IterableMixin<String> implements Iterator<String> {
final Reference _map;
int index;
_MapKeyIterator(this._map) {
index = -1;
}
@override
String get current => _map._keyForIndex(index);
@override
bool moveNext() {
index++;
return index < _map.length;
}
@override
Iterator<String> get iterator => this;
}
class _MapValueIterator with IterableMixin<Reference> implements Iterator<Reference> {
final Reference _map;
int index;
_MapValueIterator(this._map) {
index = -1;
}
@override
Reference get current => _map._valueForIndex(index);
@override
bool moveNext() {
index++;
return index < _map.length;
}
@override
Iterator<Reference> get iterator => this;
}