Merge commit '0f6aab9da6fe982218a01f4a5b896e65fcced437' as 'third_party/flatbuffers'

This commit is contained in:
Siarhei Fedartsou
2024-06-22 13:33:34 +02:00
1814 changed files with 326902 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
export 'src/builder.dart';
export 'src/reference.dart';
+676
View File
@@ -0,0 +1,676 @@
import 'dart:convert';
import 'dart:typed_data';
import 'types.dart';
/// The main builder class for creation of a FlexBuffer.
class Builder {
final ByteData _buffer;
List<_StackValue> _stack = [];
List<_StackPointer> _stackPointers = [];
int _offset = 0;
bool _finished = false;
final Map<String, _StackValue> _stringCache = {};
final Map<String, _StackValue> _keyCache = {};
final Map<_KeysHash, _StackValue> _keyVectorCache = {};
final Map<int, _StackValue> _indirectIntCache = {};
final Map<double, _StackValue> _indirectDoubleCache = {};
/// Instantiate the builder if you intent to gradually build up the buffer by calling
/// add... methods and calling [finish] to receive the resulting byte array.
///
/// The default size of internal buffer is set to 2048. Provide a different value in order to avoid buffer copies.
Builder({int size = 2048}) : _buffer = ByteData(size);
/// Use this method in order to turn an object into a FlexBuffer directly.
///
/// Use the manual instantiation of the [Builder] and gradual addition of values, if performance is more important than convenience.
static ByteBuffer buildFromObject(Object? value) {
final builder = Builder();
builder._add(value);
final buffer = builder.finish();
final byteData = ByteData(buffer.lengthInBytes);
byteData.buffer.asUint8List().setAll(0, buffer);
return byteData.buffer;
}
void _add(Object? value) {
if (value == null) {
addNull();
} else if (value is bool) {
addBool(value);
} else if (value is int) {
addInt(value);
} else if (value is double) {
addDouble(value);
} else if (value is ByteBuffer) {
addBlob(value);
} else if (value is String) {
addString(value);
} else if (value is List<dynamic>) {
startVector();
for (var i = 0; i < value.length; i++) {
_add(value[i]);
}
end();
} else if (value is Map<String, dynamic>) {
startMap();
value.forEach((key, value) {
addKey(key);
_add(value);
});
end();
} else {
throw UnsupportedError('Value of unexpected type: $value');
}
}
/// Use this method if you want to store a null value.
///
/// Specifically useful when building up a vector where values can be null.
void addNull() {
_integrityCheckOnValueAddition();
_stack.add(_StackValue.withNull());
}
/// Adds a string value.
void addInt(int value) {
_integrityCheckOnValueAddition();
_stack.add(_StackValue.withInt(value));
}
/// Adds a bool value.
void addBool(bool value) {
_integrityCheckOnValueAddition();
_stack.add(_StackValue.withBool(value));
}
/// Adds a double value.
void addDouble(double value) {
_integrityCheckOnValueAddition();
_stack.add(_StackValue.withDouble(value));
}
/// Adds a string value.
void addString(String value) {
_integrityCheckOnValueAddition();
if (_stringCache.containsKey(value)) {
_stack.add(_stringCache[value]!);
return;
}
final utf8String = utf8.encode(value);
final length = utf8String.length;
final bitWidth = BitWidthUtil.uwidth(length);
final byteWidth = _align(bitWidth);
_writeUInt(length, byteWidth);
final stringOffset = _offset;
final newOffset = _newOffset(length + 1);
_pushBuffer(utf8String);
_offset = newOffset;
final stackValue =
_StackValue.withOffset(stringOffset, ValueType.String, bitWidth);
_stack.add(stackValue);
_stringCache[value] = stackValue;
}
/// This methods adds a key to a map and should be followed by an add... value call.
///
/// It also implies that you call this method only after you called [startMap].
void addKey(String value) {
_integrityCheckOnKeyAddition();
if (_keyCache.containsKey(value)) {
_stack.add(_keyCache[value]!);
return;
}
final utf8String = utf8.encode(value);
final length = utf8String.length;
final keyOffset = _offset;
final newOffset = _newOffset(length + 1);
_pushBuffer(utf8String);
_offset = newOffset;
final stackValue =
_StackValue.withOffset(keyOffset, ValueType.Key, BitWidth.width8);
_stack.add(stackValue);
_keyCache[value] = stackValue;
}
/// Adds a byte array.
///
/// This method can be used to store any generic BLOB.
void addBlob(ByteBuffer value) {
_integrityCheckOnValueAddition();
final length = value.lengthInBytes;
final bitWidth = BitWidthUtil.uwidth(length);
final byteWidth = _align(bitWidth);
_writeUInt(length, byteWidth);
final blobOffset = _offset;
final newOffset = _newOffset(length);
_pushBuffer(value.asUint8List());
_offset = newOffset;
final stackValue =
_StackValue.withOffset(blobOffset, ValueType.Blob, bitWidth);
_stack.add(stackValue);
}
/// Stores int value indirectly in the buffer.
///
/// Adding large integer values indirectly might be beneficial if those values suppose to be store in a vector together with small integer values.
/// This is due to the fact that FlexBuffers will add padding to small integer values, if they are stored together with large integer values.
/// When we add integer indirectly the vector of ints will contain not the value itself, but only the relative offset to the value.
/// By setting the [cache] parameter to true, you make sure that the builder tracks added int value and performs deduplication.
void addIntIndirectly(int value, {bool cache = false}) {
_integrityCheckOnValueAddition();
if (_indirectIntCache.containsKey(value)) {
_stack.add(_indirectIntCache[value]!);
return;
}
final stackValue = _StackValue.withInt(value);
final byteWidth = _align(stackValue.width);
final newOffset = _newOffset(byteWidth);
final valueOffset = _offset;
_pushBuffer(stackValue.asU8List(stackValue.width));
final stackOffset = _StackValue.withOffset(
valueOffset, ValueType.IndirectInt, stackValue.width);
_stack.add(stackOffset);
_offset = newOffset;
if (cache) {
_indirectIntCache[value] = stackOffset;
}
}
/// Stores double value indirectly in the buffer.
///
/// Double are stored as 8 or 4 byte values in FlexBuffers. If they are stored in a mixed vector, values which are smaller than 4 / 8 bytes will be padded.
/// When we add double indirectly, the vector will contain not the value itself, but only the relative offset to the value. Which could occupy only 1 or 2 bytes, reducing the odds for unnecessary padding.
/// By setting the [cache] parameter to true, you make sure that the builder tracks already added double value and performs deduplication.
void addDoubleIndirectly(double value, {bool cache = false}) {
_integrityCheckOnValueAddition();
if (cache && _indirectDoubleCache.containsKey(value)) {
_stack.add(_indirectDoubleCache[value]!);
return;
}
final stackValue = _StackValue.withDouble(value);
final byteWidth = _align(stackValue.width);
final newOffset = _newOffset(byteWidth);
final valueOffset = _offset;
_pushBuffer(stackValue.asU8List(stackValue.width));
final stackOffset = _StackValue.withOffset(
valueOffset, ValueType.IndirectFloat, stackValue.width);
_stack.add(stackOffset);
_offset = newOffset;
if (cache) {
_indirectDoubleCache[value] = stackOffset;
}
}
/// This method starts a vector definition and needs to be followed by 0 to n add... value calls.
///
/// The vector definition needs to be finished with an [end] call.
/// It is also possible to add nested vector or map by calling [startVector] / [startMap].
void startVector() {
_integrityCheckOnValueAddition();
_stackPointers.add(_StackPointer(_stack.length, true));
}
/// This method starts a map definition.
///
/// This method call needs to be followed by 0 to n [addKey] + add... value calls.
/// The map definition needs to be finished with an [end] call.
/// It is also possible to add nested vector or map by calling [startVector] / [startMap] after calling [addKey].
void startMap() {
_integrityCheckOnValueAddition();
_stackPointers.add(_StackPointer(_stack.length, false));
}
/// Marks that the addition of values to the last vector, or map have ended.
void end() {
final pointer = _stackPointers.removeLast();
if (pointer.isVector) {
_endVector(pointer);
} else {
_sortKeysAndEndMap(pointer);
}
}
/// Finish building the FlatBuffer and return array of bytes.
///
/// Can be called multiple times, to get the array of bytes.
/// After the first call, adding values, or starting vectors / maps will result in an exception.
Uint8List finish() {
if (_finished == false) {
_finish();
}
return _buffer.buffer.asUint8List(0, _offset);
}
/// Builds a FlatBuffer with current state without finishing the builder.
///
/// Creates an internal temporary copy of current builder and finishes the copy.
/// Use this method, when the state of a long lasting builder need to be persisted periodically.
ByteBuffer snapshot() {
final tmp = Builder(size: _offset + 200);
tmp._offset = _offset;
tmp._stack = List.from(_stack);
tmp._stackPointers = List.from(_stackPointers);
tmp._buffer.buffer
.asUint8List()
.setAll(0, _buffer.buffer.asUint8List(0, _offset));
for (var i = 0; i < tmp._stackPointers.length; i++) {
tmp.end();
}
final buffer = tmp.finish();
final bd = ByteData(buffer.lengthInBytes);
bd.buffer.asUint8List().setAll(0, buffer);
return bd.buffer;
}
void _integrityCheckOnValueAddition() {
if (_finished) {
throw StateError('Adding values after finish is prohibited');
}
if (_stackPointers.isNotEmpty && _stackPointers.last.isVector == false) {
if (_stack.last.type != ValueType.Key) {
throw StateError(
'Adding value to a map before adding a key is prohibited');
}
}
}
void _integrityCheckOnKeyAddition() {
if (_finished) {
throw StateError('Adding values after finish is prohibited');
}
if (_stackPointers.isEmpty || _stackPointers.last.isVector) {
throw StateError('Adding key before staring a map is prohibited');
}
}
void _finish() {
if (_stack.length != 1) {
throw StateError(
'Stack has to be exactly 1, but is ${_stack.length}. You have to end all started vectors and maps, before calling [finish]');
}
final value = _stack[0];
final byteWidth = _align(value.elementWidth(_offset, 0));
_writeStackValue(value, byteWidth);
_writeUInt(value.storedPackedType(), 1);
_writeUInt(byteWidth, 1);
_finished = true;
}
_StackValue _createVector(int start, int vecLength, int step,
[_StackValue? keys]) {
var bitWidth = BitWidthUtil.uwidth(vecLength);
var prefixElements = 1;
if (keys != null) {
var elemWidth = keys.elementWidth(_offset, 0);
if (elemWidth.index > bitWidth.index) {
bitWidth = elemWidth;
}
prefixElements += 2;
}
var vectorType = ValueType.Key;
var typed = keys == null;
for (var i = start; i < _stack.length; i += step) {
final elemWidth = _stack[i].elementWidth(_offset, i + prefixElements);
if (elemWidth.index > bitWidth.index) {
bitWidth = elemWidth;
}
if (i == start) {
vectorType = _stack[i].type;
typed &= ValueTypeUtils.isTypedVectorElement(vectorType);
} else {
if (vectorType != _stack[i].type) {
typed = false;
}
}
}
final byteWidth = _align(bitWidth);
final fix = typed & ValueTypeUtils.isNumber(vectorType) &&
vecLength >= 2 &&
vecLength <= 4;
if (keys != null) {
_writeStackValue(keys, byteWidth);
_writeUInt(1 << keys.width.index, byteWidth);
}
if (fix == false) {
_writeUInt(vecLength, byteWidth);
}
final vecOffset = _offset;
for (var i = start; i < _stack.length; i += step) {
_writeStackValue(_stack[i], byteWidth);
}
if (typed == false) {
for (var i = start; i < _stack.length; i += step) {
_writeUInt(_stack[i].storedPackedType(), 1);
}
}
if (keys != null) {
return _StackValue.withOffset(vecOffset, ValueType.Map, bitWidth);
}
if (typed) {
final vType =
ValueTypeUtils.toTypedVector(vectorType, fix ? vecLength : 0);
return _StackValue.withOffset(vecOffset, vType, bitWidth);
}
return _StackValue.withOffset(vecOffset, ValueType.Vector, bitWidth);
}
void _endVector(_StackPointer pointer) {
final vecLength = _stack.length - pointer.stackPosition;
final vec = _createVector(pointer.stackPosition, vecLength, 1);
_stack.removeRange(pointer.stackPosition, _stack.length);
_stack.add(vec);
}
void _sortKeysAndEndMap(_StackPointer pointer) {
if (((_stack.length - pointer.stackPosition) & 1) == 1) {
throw StateError(
'The stack needs to hold key value pairs (even number of elements). Check if you combined [addKey] with add... method calls properly.');
}
var sorted = true;
for (var i = pointer.stackPosition; i < _stack.length - 2; i += 2) {
if (_shouldFlip(_stack[i], _stack[i + 2])) {
sorted = false;
break;
}
}
if (sorted == false) {
for (var i = pointer.stackPosition; i < _stack.length; i += 2) {
var flipIndex = i;
for (var j = i + 2; j < _stack.length; j += 2) {
if (_shouldFlip(_stack[flipIndex], _stack[j])) {
flipIndex = j;
}
}
if (flipIndex != i) {
var k = _stack[flipIndex];
var v = _stack[flipIndex + 1];
_stack[flipIndex] = _stack[i];
_stack[flipIndex + 1] = _stack[i + 1];
_stack[i] = k;
_stack[i + 1] = v;
}
}
}
_endMap(pointer);
}
void _endMap(_StackPointer pointer) {
final vecLength = (_stack.length - pointer.stackPosition) >> 1;
final offsets = <int>[];
for (var i = pointer.stackPosition; i < _stack.length; i += 2) {
offsets.add(_stack[i].offset!);
}
final keysHash = _KeysHash(offsets);
_StackValue? keysStackValue;
if (_keyVectorCache.containsKey(keysHash)) {
keysStackValue = _keyVectorCache[keysHash];
} else {
keysStackValue = _createVector(pointer.stackPosition, vecLength, 2);
_keyVectorCache[keysHash] = keysStackValue;
}
final vec =
_createVector(pointer.stackPosition + 1, vecLength, 2, keysStackValue);
_stack.removeRange(pointer.stackPosition, _stack.length);
_stack.add(vec);
}
bool _shouldFlip(_StackValue v1, _StackValue v2) {
if (v1.type != ValueType.Key || v2.type != ValueType.Key) {
throw StateError(
'Stack values are not keys $v1 | $v2. Check if you combined [addKey] with add... method calls properly.');
}
late int c1, c2;
var index = 0;
do {
c1 = _buffer.getUint8(v1.offset! + index);
c2 = _buffer.getUint8(v2.offset! + index);
if (c2 < c1) return true;
if (c1 < c2) return false;
index += 1;
} while (c1 != 0 && c2 != 0);
return false;
}
int _align(BitWidth width) {
final byteWidth = BitWidthUtil.toByteWidth(width);
_offset += BitWidthUtil.paddingSize(_offset, byteWidth);
return byteWidth;
}
void _writeStackValue(_StackValue value, int byteWidth) {
final newOffset = _newOffset(byteWidth);
if (value.isOffset) {
final relativeOffset = _offset - value.offset!;
if (byteWidth == 8 || relativeOffset < (1 << (byteWidth * 8))) {
_writeUInt(relativeOffset, byteWidth);
} else {
throw StateError(
'Unexpected size $byteWidth. This might be a bug. Please create an issue https://github.com/google/flatbuffers/issues/new');
}
} else {
_pushBuffer(value.asU8List(BitWidthUtil.fromByteWidth(byteWidth)));
}
_offset = newOffset;
}
void _writeUInt(int value, int byteWidth) {
final newOffset = _newOffset(byteWidth);
_pushUInt(value, BitWidthUtil.fromByteWidth(byteWidth));
_offset = newOffset;
}
int _newOffset(int newValueSize) {
final newOffset = _offset + newValueSize;
var size = _buffer.lengthInBytes;
final prevSize = size;
while (size < newOffset) {
size <<= 1;
}
if (prevSize < size) {
final newBuf = ByteData(size);
newBuf.buffer.asUint8List().setAll(0, _buffer.buffer.asUint8List());
}
return newOffset;
}
void _pushInt(int value, BitWidth width) {
switch (width) {
case BitWidth.width8:
_buffer.setInt8(_offset, value);
break;
case BitWidth.width16:
_buffer.setInt16(_offset, value, Endian.little);
break;
case BitWidth.width32:
_buffer.setInt32(_offset, value, Endian.little);
break;
case BitWidth.width64:
_buffer.setInt64(_offset, value, Endian.little);
break;
}
}
void _pushUInt(int value, BitWidth width) {
switch (width) {
case BitWidth.width8:
_buffer.setUint8(_offset, value);
break;
case BitWidth.width16:
_buffer.setUint16(_offset, value, Endian.little);
break;
case BitWidth.width32:
_buffer.setUint32(_offset, value, Endian.little);
break;
case BitWidth.width64:
_buffer.setUint64(_offset, value, Endian.little);
break;
}
}
void _pushBuffer(List<int> value) {
_buffer.buffer.asUint8List().setAll(_offset, value);
}
}
class _StackValue {
late Object _value;
int? _offset;
final ValueType _type;
final BitWidth _width;
_StackValue.withNull()
: _type = ValueType.Null,
_width = BitWidth.width8;
_StackValue.withInt(int value)
: _type = ValueType.Int,
_width = BitWidthUtil.width(value),
_value = value;
_StackValue.withBool(bool value)
: _type = ValueType.Bool,
_width = BitWidth.width8,
_value = value;
_StackValue.withDouble(double value)
: _type = ValueType.Float,
_width = BitWidthUtil.width(value),
_value = value;
_StackValue.withOffset(int value, ValueType type, BitWidth width)
: _offset = value,
_type = type,
_width = width;
BitWidth storedWidth({BitWidth width = BitWidth.width8}) {
return ValueTypeUtils.isInline(_type)
? BitWidthUtil.max(_width, width)
: _width;
}
int storedPackedType({BitWidth width = BitWidth.width8}) {
return ValueTypeUtils.packedType(_type, storedWidth(width: width));
}
BitWidth elementWidth(int size, int index) {
if (ValueTypeUtils.isInline(_type)) return _width;
final offset = _offset!;
for (var i = 0; i < 4; i++) {
final width = 1 << i;
final bitWidth = BitWidthUtil.uwidth(size +
BitWidthUtil.paddingSize(size, width) +
index * width -
offset);
if (1 << bitWidth.index == width) {
return bitWidth;
}
}
throw StateError(
'Element is of unknown. Size: $size at index: $index. This might be a bug. Please create an issue https://github.com/google/flatbuffers/issues/new');
}
List<int> asU8List(BitWidth width) {
if (ValueTypeUtils.isNumber(_type)) {
if (_type == ValueType.Float) {
if (width == BitWidth.width32) {
final result = ByteData(4);
result.setFloat32(0, _value as double, Endian.little);
return result.buffer.asUint8List();
} else {
final result = ByteData(8);
result.setFloat64(0, _value as double, Endian.little);
return result.buffer.asUint8List();
}
} else {
switch (width) {
case BitWidth.width8:
final result = ByteData(1);
result.setInt8(0, _value as int);
return result.buffer.asUint8List();
case BitWidth.width16:
final result = ByteData(2);
result.setInt16(0, _value as int, Endian.little);
return result.buffer.asUint8List();
case BitWidth.width32:
final result = ByteData(4);
result.setInt32(0, _value as int, Endian.little);
return result.buffer.asUint8List();
case BitWidth.width64:
final result = ByteData(8);
result.setInt64(0, _value as int, Endian.little);
return result.buffer.asUint8List();
}
}
}
if (_type == ValueType.Null) {
final result = ByteData(1);
result.setInt8(0, 0);
return result.buffer.asUint8List();
}
if (_type == ValueType.Bool) {
final result = ByteData(1);
result.setInt8(0, _value as bool ? 1 : 0);
return result.buffer.asUint8List();
}
throw StateError(
'Unexpected type: $_type. This might be a bug. Please create an issue https://github.com/google/flatbuffers/issues/new');
}
ValueType get type {
return _type;
}
BitWidth get width {
return _width;
}
bool get isOffset {
return !ValueTypeUtils.isInline(_type);
}
int? get offset => _offset;
bool get isFloat32 {
return _type == ValueType.Float && _width == BitWidth.width32;
}
}
class _StackPointer {
int stackPosition;
bool isVector;
_StackPointer(this.stackPosition, this.isVector);
}
class _KeysHash {
final List<int> keys;
const _KeysHash(this.keys);
@override
bool operator ==(Object other) {
if (other is _KeysHash) {
if (keys.length != other.keys.length) return false;
for (var i = 0; i < keys.length; i++) {
if (keys[i] != other.keys[i]) return false;
}
return true;
}
return false;
}
@override
int get hashCode {
var result = 17;
for (var i = 0; i < keys.length; i++) {
result = result * 23 + keys[i];
}
return result;
}
}
+481
View File
@@ -0,0 +1,481 @@
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;
}
+190
View File
@@ -0,0 +1,190 @@
import 'dart:typed_data';
/// Represents the number of bits a value occupies.
enum BitWidth { width8, width16, width32, width64 }
class BitWidthUtil {
static int toByteWidth(BitWidth self) {
return 1 << self.index;
}
static BitWidth width(num value) {
if (value is int) {
var v = value.toInt().abs();
if (v >> 7 == 0) return BitWidth.width8;
if (v >> 15 == 0) return BitWidth.width16;
if (v >> 31 == 0) return BitWidth.width32;
return BitWidth.width64;
}
return value == _toF32(value as double)
? BitWidth.width32
: BitWidth.width64;
}
static BitWidth uwidth(num value) {
if (value.toInt() == value) {
var v = value.toInt().abs();
if (v >> 8 == 0) return BitWidth.width8;
if (v >> 16 == 0) return BitWidth.width16;
if (v >> 32 == 0) return BitWidth.width32;
return BitWidth.width64;
}
return value == _toF32(value as double)
? BitWidth.width32
: BitWidth.width64;
}
static BitWidth fromByteWidth(int value) {
if (value == 1) {
return BitWidth.width8;
}
if (value == 2) {
return BitWidth.width16;
}
if (value == 4) {
return BitWidth.width32;
}
if (value == 8) {
return BitWidth.width64;
}
throw Exception('Unexpected value $value');
}
static int paddingSize(int bufSize, int scalarSize) {
return (~bufSize + 1) & (scalarSize - 1);
}
static double _toF32(double value) {
var bdata = ByteData(4);
bdata.setFloat32(0, value);
return bdata.getFloat32(0);
}
static BitWidth max(BitWidth self, BitWidth other) {
if (self.index < other.index) {
return other;
}
return self;
}
}
/// Represents all internal FlexBuffer types.
enum ValueType {
Null,
Int,
UInt,
Float,
Key,
String,
IndirectInt,
IndirectUInt,
IndirectFloat,
Map,
Vector,
VectorInt,
VectorUInt,
VectorFloat,
VectorKey,
@Deprecated(
'VectorString is deprecated due to a flaw in the binary format (https://github.com/google/flatbuffers/issues/5627)')
VectorString,
VectorInt2,
VectorUInt2,
VectorFloat2,
VectorInt3,
VectorUInt3,
VectorFloat3,
VectorInt4,
VectorUInt4,
VectorFloat4,
Blob,
Bool,
VectorBool
}
class ValueTypeUtils {
static int toInt(ValueType self) {
if (self == ValueType.VectorBool) return 36;
return self.index;
}
static ValueType fromInt(int value) {
if (value == 36) return ValueType.VectorBool;
return ValueType.values[value];
}
static bool isInline(ValueType self) {
return self == ValueType.Bool || toInt(self) <= toInt(ValueType.Float);
}
static bool isNumber(ValueType self) {
return toInt(self) >= toInt(ValueType.Int) &&
toInt(self) <= toInt(ValueType.Float);
}
static bool isIndirectNumber(ValueType self) {
return toInt(self) >= toInt(ValueType.IndirectInt) &&
toInt(self) <= toInt(ValueType.IndirectFloat);
}
static bool isTypedVectorElement(ValueType self) {
return self == ValueType.Bool ||
(toInt(self) >= toInt(ValueType.Int) &&
toInt(self) <= toInt(ValueType.String));
}
static bool isTypedVector(ValueType self) {
return self == ValueType.VectorBool ||
(toInt(self) >= toInt(ValueType.VectorInt) &&
toInt(self) <= toInt(ValueType.VectorString));
}
static bool isFixedTypedVector(ValueType self) {
return (toInt(self) >= toInt(ValueType.VectorInt2) &&
toInt(self) <= toInt(ValueType.VectorFloat4));
}
static bool isAVector(ValueType self) {
return (isTypedVector(self) ||
isFixedTypedVector(self) ||
self == ValueType.Vector);
}
static ValueType toTypedVector(ValueType self, int length) {
if (length == 0) {
return ValueTypeUtils.fromInt(
toInt(self) - toInt(ValueType.Int) + toInt(ValueType.VectorInt));
}
if (length == 2) {
return ValueTypeUtils.fromInt(
toInt(self) - toInt(ValueType.Int) + toInt(ValueType.VectorInt2));
}
if (length == 3) {
return ValueTypeUtils.fromInt(
toInt(self) - toInt(ValueType.Int) + toInt(ValueType.VectorInt3));
}
if (length == 4) {
return ValueTypeUtils.fromInt(
toInt(self) - toInt(ValueType.Int) + toInt(ValueType.VectorInt4));
}
throw Exception('unexpected length ' + length.toString());
}
static ValueType typedVectorElementType(ValueType self) {
return ValueTypeUtils.fromInt(
toInt(self) - toInt(ValueType.VectorInt) + toInt(ValueType.Int));
}
static ValueType fixedTypedVectorElementType(ValueType self) {
return ValueTypeUtils.fromInt(
(toInt(self) - toInt(ValueType.VectorInt2)) % 3 + toInt(ValueType.Int));
}
static int fixedTypedVectorElementSize(ValueType self) {
return (toInt(self) - toInt(ValueType.VectorInt2)) ~/ 3 + 2;
}
static int packedType(ValueType self, BitWidth bitWidth) {
return bitWidth.index | (toInt(self) << 2);
}
}