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