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 int _byteWidth; 15 ValueType _valueType; 16 int _length; 17 18 Reference._(this._buffer, this._offset, this._parentWidth, int packedType, this._path) { 19 _byteWidth = 1 << (packedType & 3); 20 _valueType = ValueTypeUtils.fromInt(packedType >> 2); 21 } 22 23 /// Use this method to access the root value of a FlexBuffer. 24 static Reference fromBuffer(ByteBuffer buffer) { 25 final len = buffer.lengthInBytes; 26 if (len < 3) { 27 throw UnsupportedError('Buffer needs to be bigger than 3'); 28 } 29 final byteData = ByteData.view(buffer); 30 final byteWidth = byteData.getUint8(len - 1); 31 final packedType = byteData.getUint8(len - 2); 32 final offset = len - byteWidth - 2; 33 return Reference._(ByteData.view(buffer), offset, BitWidthUtil.fromByteWidth(byteWidth), packedType, "/"); 34 } 35 36 /// Returns true if the underlying value is null. 37 bool get isNull => _valueType == ValueType.Null; 38 /// Returns true if the underlying value can be represented as [num]. 39 bool get isNum => ValueTypeUtils.isNumber(_valueType) || ValueTypeUtils.isIndirectNumber(_valueType); 40 /// Returns true if the underlying value was encoded as a float (direct or indirect). 41 bool get isDouble => _valueType == ValueType.Float || _valueType == ValueType.IndirectFloat; 42 /// Returns true if the underlying value was encoded as an int or uint (direct or indirect). 43 bool get isInt => isNum && !isDouble; 44 /// Returns true if the underlying value was encoded as a string or a key. 45 bool get isString => _valueType == ValueType.String || _valueType == ValueType.Key; 46 /// Returns true if the underlying value was encoded as a bool. 47 bool get isBool => _valueType == ValueType.Bool; 48 /// Returns true if the underlying value was encoded as a blob. 49 bool get isBlob => _valueType == ValueType.Blob; 50 /// Returns true if the underlying value points to a vector. 51 bool get isVector => ValueTypeUtils.isAVector(_valueType); 52 /// Returns true if the underlying value points to a map. 53 bool get isMap => _valueType == ValueType.Map; 54 55 /// If this [isBool], returns the bool value. Otherwise, returns null. 56 bool get boolValue { 57 if(_valueType == ValueType.Bool) { 58 return _readInt(_offset, _parentWidth) != 0; 59 } 60 return null; 61 } 62 63 /// Returns an [int], if the underlying value can be represented as an int. 64 /// 65 /// Otherwise returns [null]. 66 int get intValue { 67 if (_valueType == ValueType.Int) { 68 return _readInt(_offset, _parentWidth); 69 } 70 if (_valueType == ValueType.UInt) { 71 return _readUInt(_offset, _parentWidth); 72 } 73 if (_valueType == ValueType.IndirectInt) { 74 return _readInt(_indirect, BitWidthUtil.fromByteWidth(_byteWidth)); 75 } 76 if (_valueType == ValueType.IndirectUInt) { 77 return _readUInt(_indirect, BitWidthUtil.fromByteWidth(_byteWidth)); 78 } 79 return null; 80 } 81 82 /// Returns [double], if the underlying value [isDouble]. 83 /// 84 /// Otherwise returns [null]. 85 double get doubleValue { 86 if (_valueType == ValueType.Float) { 87 return _readFloat(_offset, _parentWidth); 88 } 89 if (_valueType == ValueType.IndirectFloat) { 90 return _readFloat(_indirect, BitWidthUtil.fromByteWidth(_byteWidth)); 91 } 92 return null; 93 } 94 95 /// Returns [num], if the underlying value is numeric, be it int uint, or float (direct or indirect). 96 /// 97 /// Otherwise returns [null]. 98 num get numValue => doubleValue ?? intValue; 99 100 /// Returns [String] value or null otherwise. 101 /// 102 /// This method performers a utf8 decoding, as FlexBuffers format stores strings in utf8 encoding. 103 String get stringValue { 104 if (_valueType == ValueType.String || _valueType == ValueType.Key) { 105 return utf8.decode(_buffer.buffer.asUint8List(_indirect, length)); 106 } 107 return null; 108 } 109 110 /// Returns [Uint8List] value or null otherwise. 111 Uint8List get blobValue { 112 if (_valueType == ValueType.Blob) { 113 return _buffer.buffer.asUint8List(_indirect, length); 114 } 115 return null; 116 } 117 118 /// Can be used with an [int] or a [String] value for key. 119 /// If the underlying value in FlexBuffer is a vector, then use [int] for access. 120 /// If the underlying value in FlexBuffer is a map, then use [String] for access. 121 /// Returns [Reference] value. Throws an exception when [key] is not applicable. 122 Reference operator [](Object key) { 123 if (key is int && ValueTypeUtils.isAVector(_valueType)) { 124 final index = key; 125 if(index >= length || index < 0) { 126 throw ArgumentError('Key: [$key] is not applicable on: $_path of: $_valueType length: $length'); 127 } 128 final elementOffset = _indirect + index * _byteWidth; 129 final reference = Reference._(_buffer, elementOffset, BitWidthUtil.fromByteWidth(_byteWidth), 0, "$_path[$index]"); 130 reference._byteWidth = 1; 131 if (ValueTypeUtils.isTypedVector(_valueType)) { 132 reference._valueType = ValueTypeUtils.typedVectorElementType(_valueType); 133 return reference; 134 } 135 if(ValueTypeUtils.isFixedTypedVector(_valueType)) { 136 reference._valueType = ValueTypeUtils.fixedTypedVectorElementType(_valueType); 137 return reference; 138 } 139 final packedType = _buffer.getUint8(_indirect + length * _byteWidth + index); 140 return Reference._(_buffer, elementOffset, BitWidthUtil.fromByteWidth(_byteWidth), packedType, "$_path[$index]"); 141 } 142 if (key is String && _valueType == ValueType.Map) { 143 final index = _keyIndex(key); 144 if (index != null) { 145 return _valueForIndexWithKey(index, key); 146 } 147 } 148 throw ArgumentError('Key: [$key] is not applicable on: $_path of: $_valueType'); 149 } 150 151 /// Get an iterable if the underlying flexBuffer value is a vector. 152 /// Otherwise throws an exception. 153 Iterable<Reference> get vectorIterable { 154 if(isVector == false) { 155 throw UnsupportedError('Value is not a vector. It is: $_valueType'); 156 } 157 return _VectorIterator(this); 158 } 159 160 /// Get an iterable for keys if the underlying flexBuffer value is a map. 161 /// Otherwise throws an exception. 162 Iterable<String> get mapKeyIterable { 163 if(isMap == false) { 164 throw UnsupportedError('Value is not a map. It is: $_valueType'); 165 } 166 return _MapKeyIterator(this); 167 } 168 169 /// Get an iterable for values if the underlying flexBuffer value is a map. 170 /// Otherwise throws an exception. 171 Iterable<Reference> get mapValueIterable { 172 if(isMap == false) { 173 throw UnsupportedError('Value is not a map. It is: $_valueType'); 174 } 175 return _MapValueIterator(this); 176 } 177 178 /// Returns the length of the the underlying FlexBuffer value. 179 /// If the underlying value is [null] the length is 0. 180 /// If the underlying value is a number, or a bool, the length is 1. 181 /// If the underlying value is a vector, or map, the length reflects number of elements / element pairs. 182 /// 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). 183 int get length { 184 if (_length != null) { 185 return _length; 186 } 187 // needs to be checked before more generic isAVector 188 if(ValueTypeUtils.isFixedTypedVector(_valueType)) { 189 _length = ValueTypeUtils.fixedTypedVectorElementSize(_valueType); 190 } else if(_valueType == ValueType.Blob || ValueTypeUtils.isAVector(_valueType) || _valueType == ValueType.Map){ 191 _length = _readUInt(_indirect - _byteWidth, BitWidthUtil.fromByteWidth(_byteWidth)); 192 } else if (_valueType == ValueType.Null) { 193 _length = 0; 194 } else if (_valueType == ValueType.String) { 195 final indirect = _indirect; 196 var size_byte_width = _byteWidth; 197 var size = _readUInt(indirect - size_byte_width, BitWidthUtil.fromByteWidth(size_byte_width)); 198 while (_buffer.getInt8(indirect + size) != 0) { 199 size_byte_width <<= 1; 200 size = _readUInt(indirect - size_byte_width, BitWidthUtil.fromByteWidth(size_byte_width)); 201 } 202 _length = size; 203 } else if (_valueType == ValueType.Key) { 204 final indirect = _indirect; 205 var size = 1; 206 while (_buffer.getInt8(indirect + size) != 0) { 207 size += 1; 208 } 209 _length = size; 210 } else { 211 _length = 1; 212 } 213 return _length; 214 } 215 216 217 /// Returns a minified JSON representation of the underlying FlexBuffer value. 218 /// 219 /// This method involves materializing the entire object tree, which may be 220 /// expensive. It is more efficient to work with [Reference] and access only the needed data. 221 /// Blob values are represented as base64 encoded string. 222 String get json { 223 if(_valueType == ValueType.Bool) { 224 return boolValue ? 'true' : 'false'; 225 } 226 if (_valueType == ValueType.Null) { 227 return 'null'; 228 } 229 if(ValueTypeUtils.isNumber(_valueType)) { 230 return jsonEncode(numValue); 231 } 232 if (_valueType == ValueType.String) { 233 return jsonEncode(stringValue); 234 } 235 if (_valueType == ValueType.Blob) { 236 return jsonEncode(base64Encode(blobValue)); 237 } 238 if (ValueTypeUtils.isAVector(_valueType)) { 239 final result = StringBuffer(); 240 result.write('['); 241 for (var i = 0; i < length; i++) { 242 result.write(this[i].json); 243 if (i < length - 1) { 244 result.write(','); 245 } 246 } 247 result.write(']'); 248 return result.toString(); 249 } 250 if (_valueType == ValueType.Map) { 251 final result = StringBuffer(); 252 result.write('{'); 253 for (var i = 0; i < length; i++) { 254 result.write(jsonEncode(_keyForIndex(i))); 255 result.write(':'); 256 result.write(_valueForIndex(i).json); 257 if (i < length - 1) { 258 result.write(','); 259 } 260 } 261 result.write('}'); 262 return result.toString(); 263 } 264 throw UnsupportedError('Type: $_valueType is not supported for JSON conversion'); 265 } 266 267 /// Computes the indirect offset of the value. 268 /// 269 /// To optimize for the more common case of being called only once, this 270 /// value is not cached. Callers that need to use it more than once should 271 /// cache the return value in a local variable. 272 int get _indirect { 273 final step = _readUInt(_offset, _parentWidth); 274 return _offset - step; 275 } 276 277 int _readInt(int offset, BitWidth width) { 278 _validateOffset(offset, width); 279 if (width == BitWidth.width8) { 280 return _buffer.getInt8(offset); 281 } 282 if (width == BitWidth.width16) { 283 return _buffer.getInt16(offset, Endian.little); 284 } 285 if (width == BitWidth.width32) { 286 return _buffer.getInt32(offset, Endian.little); 287 } 288 return _buffer.getInt64(offset, Endian.little); 289 } 290 291 int _readUInt(int offset, BitWidth width) { 292 _validateOffset(offset, width); 293 if (width == BitWidth.width8) { 294 return _buffer.getUint8(offset); 295 } 296 if (width == BitWidth.width16) { 297 return _buffer.getUint16(offset, Endian.little); 298 } 299 if (width == BitWidth.width32) { 300 return _buffer.getUint32(offset, Endian.little); 301 } 302 return _buffer.getUint64(offset, Endian.little); 303 } 304 305 double _readFloat(int offset, BitWidth width) { 306 _validateOffset(offset, width); 307 if (width.index < BitWidth.width32.index) { 308 throw StateError('Bad width: $width'); 309 } 310 311 if (width == BitWidth.width32) { 312 return _buffer.getFloat32(offset, Endian.little); 313 } 314 315 return _buffer.getFloat64(offset, Endian.little); 316 } 317 318 void _validateOffset(int offset, BitWidth width) { 319 if (_offset < 0 || _buffer.lengthInBytes <= offset + width.index || offset & (BitWidthUtil.toByteWidth(width) - 1) != 0) { 320 throw StateError('Bad offset: $offset, width: $width'); 321 } 322 } 323 324 int _keyIndex(String key) { 325 final input = utf8.encode(key); 326 final keysVectorOffset = _indirect - _byteWidth * 3; 327 final indirectOffset = keysVectorOffset - _readUInt(keysVectorOffset, BitWidthUtil.fromByteWidth(_byteWidth)); 328 final byteWidth = _readUInt(keysVectorOffset + _byteWidth, BitWidthUtil.fromByteWidth(_byteWidth)); 329 var low = 0; 330 var high = length - 1; 331 while (low <= high) { 332 final mid = (high + low) >> 1; 333 final dif = _diffKeys(input, mid, indirectOffset, byteWidth); 334 if (dif == 0) return mid; 335 if (dif < 0) { 336 high = mid - 1; 337 } else { 338 low = mid + 1; 339 } 340 } 341 return null; 342 } 343 344 int _diffKeys(List<int> input, int index, int indirect_offset, int byteWidth) { 345 final keyOffset = indirect_offset + index * byteWidth; 346 final keyIndirectOffset = keyOffset - _readUInt(keyOffset, BitWidthUtil.fromByteWidth(byteWidth)); 347 for (var i = 0; i < input.length; i++) { 348 final dif = input[i] - _buffer.getUint8(keyIndirectOffset + i); 349 if (dif != 0) { 350 return dif; 351 } 352 } 353 return (_buffer.getUint8(keyIndirectOffset + input.length) == 0) ? 0 : -1; 354 } 355 356 Reference _valueForIndexWithKey(int index, String key) { 357 final indirect = _indirect; 358 final elementOffset = indirect + index * _byteWidth; 359 final packedType = _buffer.getUint8(indirect + length * _byteWidth + index); 360 return Reference._(_buffer, elementOffset, BitWidthUtil.fromByteWidth(_byteWidth), packedType, "$_path/$key"); 361 } 362 363 Reference _valueForIndex(int index) { 364 final indirect = _indirect; 365 final elementOffset = indirect + index * _byteWidth; 366 final packedType = _buffer.getUint8(indirect + length * _byteWidth + index); 367 return Reference._(_buffer, elementOffset, BitWidthUtil.fromByteWidth(_byteWidth), packedType, "$_path/[$index]"); 368 } 369 370 String _keyForIndex(int index) { 371 final keysVectorOffset = _indirect - _byteWidth * 3; 372 final indirectOffset = keysVectorOffset - _readUInt(keysVectorOffset, BitWidthUtil.fromByteWidth(_byteWidth)); 373 final byteWidth = _readUInt(keysVectorOffset + _byteWidth, BitWidthUtil.fromByteWidth(_byteWidth)); 374 final keyOffset = indirectOffset + index * byteWidth; 375 final keyIndirectOffset = keyOffset - _readUInt(keyOffset, BitWidthUtil.fromByteWidth(byteWidth)); 376 var length = 0; 377 while (_buffer.getUint8(keyIndirectOffset + length) != 0) { 378 length += 1; 379 } 380 return utf8.decode(_buffer.buffer.asUint8List(keyIndirectOffset, length)); 381 } 382 383} 384 385class _VectorIterator with IterableMixin<Reference> implements Iterator<Reference> { 386 final Reference _vector; 387 int index; 388 389 _VectorIterator(this._vector) { 390 index = -1; 391 } 392 393 @override 394 Reference get current => _vector[index]; 395 396 @override 397 bool moveNext() { 398 index++; 399 return index < _vector.length; 400 } 401 402 @override 403 Iterator<Reference> get iterator => this; 404} 405 406class _MapKeyIterator with IterableMixin<String> implements Iterator<String> { 407 final Reference _map; 408 int index; 409 410 _MapKeyIterator(this._map) { 411 index = -1; 412 } 413 414 @override 415 String get current => _map._keyForIndex(index); 416 417 @override 418 bool moveNext() { 419 index++; 420 return index < _map.length; 421 } 422 423 @override 424 Iterator<String> get iterator => this; 425} 426 427class _MapValueIterator with IterableMixin<Reference> implements Iterator<Reference> { 428 final Reference _map; 429 int index; 430 431 _MapValueIterator(this._map) { 432 index = -1; 433 } 434 435 @override 436 Reference get current => _map._valueForIndex(index); 437 438 @override 439 bool moveNext() { 440 index++; 441 return index < _map.length; 442 } 443 444 @override 445 Iterator<Reference> get iterator => this; 446} 447