blob: 3954f0687072dcb00e82d6eee18a0378f7ae1ace [file] [log] [blame]
Austin Schuh272c6132020-11-14 16:37:52 -08001import '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}