mirror of
https://github.com/google/flatbuffers.git
synced 2026-06-02 20:15:34 +00:00
482 lines
16 KiB
Dart
482 lines
16 KiB
Dart
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<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 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<int> 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<Reference>
|
|
implements Iterator<Reference> {
|
|
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<Reference> get iterator => this;
|
|
}
|
|
|
|
class _MapKeyIterator with IterableMixin<String> implements Iterator<String> {
|
|
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<String> get iterator => this;
|
|
}
|
|
|
|
class _MapValueIterator
|
|
with IterableMixin<Reference>
|
|
implements Iterator<Reference> {
|
|
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<Reference> get iterator => this;
|
|
}
|