| // Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:collection'; |
| import 'dart:convert'; |
| import 'dart:typed_data'; |
| |
| part 'closed_map.dart'; |
| part 'explanations.dart'; |
| part 'growable_map.dart'; |
| part 'iterables.dart'; |
| part 'type.dart'; |
| part 'typed_map.dart'; |
| |
| class JsonBufferBuilder { |
| /// The root map of the buffer. |
| /// |
| /// It's a "growable map": entries can be added to it. |
| late final Map<String, Object?> map; |
| |
| Uint8List _buffer; |
| |
| /// The next free location to write to in the buffer. |
| int _nextFree; |
| |
| /// Whether writes are allowed. |
| final bool _allowWrites; |
| |
| /// [_Pointer]s to [TypedMapSchema]s used by [_TypedMap]s, so that the |
| /// same schema is only written once. |
| final Map<TypedMapSchema, _Pointer> _pointersBySchema = Map.identity(); |
| |
| /// [TypedMapSchema] used by [_TypedMap]s by [_Pointer], so that the same |
| /// schema is only read once. |
| final Map<_Pointer, TypedMapSchema> _schemasByPointer = {}; |
| |
| /// [_Pointer]s to `String`s, so that the same `String` is only written once. |
| final Map<String, _Pointer> _pointersByString = {}; |
| |
| JsonBufferBuilder.deserialize(this._buffer) |
| : _allowWrites = false, |
| _nextFree = _buffer.length { |
| map = _readGrowableMap<Object?>(0); |
| } |
| |
| JsonBufferBuilder() |
| : _allowWrites = true, |
| _buffer = Uint8List(_initialBufferSize), |
| _nextFree = 0 { |
| map = createGrowableMap<Object?>(); |
| } |
| |
| /// The JSON data. |
| /// |
| /// The buffer is _not_ copied, unpredictable behavior will result if it is |
| /// mutated. |
| Uint8List serialize() => Uint8List.sublistView(_buffer, 0, _nextFree); |
| |
| /// The number of bytes written. |
| int get length => _nextFree; |
| |
| /// Reads the value at [_Pointer], which must have been written with |
| /// [_writeAny]. |
| Object? _readAny(_Pointer pointer) { |
| final type = _readType(pointer); |
| return _read(type, pointer + _typeSize); |
| } |
| |
| /// Reads the value of type [Type] at [_Pointer]. |
| Object? _read(Type type, _Pointer pointer) { |
| switch (type) { |
| case Type.nil: |
| return null; |
| case Type.type: |
| return _readType(pointer); |
| case Type.pointer: |
| return _readPointer(pointer); |
| case Type.uint32: |
| return _readUint32(pointer); |
| case Type.boolean: |
| return _readBoolean(pointer); |
| case Type.stringPointer: |
| return _readString(_readPointer(pointer)); |
| case Type.closedMapPointer: |
| return _readClosedMap(_readPointer(pointer)); |
| case Type.growableMapPointer: |
| return _readGrowableMap<Object?>(_readPointer(pointer)); |
| case Type.typedMapPointer: |
| return _readTypedMap(_readPointer(pointer)); |
| } |
| } |
| |
| /// Writes the type of [value] then writes the value using [_writeAnyOfType]. |
| void _writeAny(_Pointer pointer, Object? value) { |
| _explanations?.push('_writeAny $pointer $value'); |
| final type = Type._of(value); |
| _writeType(pointer, type); |
| _writeAnyOfType(type, pointer + _typeSize, value); |
| _explanations?.pop(); |
| } |
| |
| /// Writes [value] of type [type] to [pointer]. |
| /// |
| /// The type is assumed to be known statically and is not written. |
| /// |
| /// If [value] fits in four bytes, it is written directly. |
| /// |
| /// Otherwise, a pointer is written. It might be a pointer to an existing |
| /// value if there is one, or a new value may be written as well. |
| void _writeAnyOfType(Type type, _Pointer pointer, Object? value) { |
| _explanations?.push('_writeAnyOfType $type $pointer $value'); |
| switch (type) { |
| case Type.nil: |
| // Nothing to write. |
| break; |
| |
| case Type.type: |
| _writeType(pointer, value as Type); |
| |
| case Type.pointer: |
| _writePointer(pointer, value as _Pointer); |
| |
| case Type.uint32: |
| _writeUint32(pointer, value as int); |
| |
| case Type.boolean: |
| _writeBoolean(pointer, value as bool); |
| |
| case Type.stringPointer: |
| _writePointer(pointer, _pointerToString(value as String)); |
| |
| case Type.closedMapPointer: |
| _writePointer(pointer, _addClosedMap(value as Map<String, Object?>)); |
| |
| case Type.growableMapPointer: |
| _writePointer( |
| pointer, _pointerToGrowableMap(value as _GrowableMap<Object?>)); |
| |
| case Type.typedMapPointer: |
| _writePointer(pointer, _pointerToTypedMap(value as _TypedMap)); |
| } |
| _explanations?.pop(); |
| } |
| |
| /// Returns a [_Pointer] to the `String` [string]. |
| /// |
| /// Returns the [_Pointer] of an existing equal `String` if there is one, |
| /// otherwise adds it. |
| _Pointer _pointerToString(String string) => |
| _pointersByString[string] ??= _addString(string); |
| |
| /// Adds the `String` [string], returns a [_Pointer] to it. |
| _Pointer _addString(String string) { |
| _explanations?.push('__pointerToString $string'); |
| final bytes = utf8.encode(string); |
| final length = bytes.length; |
| final pointer = _reserve(_lengthSize + length); |
| _writeLength(pointer, length); |
| _setRange(pointer + _lengthSize, pointer + _lengthSize + length, bytes); |
| _explanations?.pop(); |
| return pointer; |
| } |
| |
| /// Reads the String at [pointer]. |
| String _readString(_Pointer pointer) { |
| final length = _readLength(pointer); |
| return utf8.decode( |
| _buffer.sublist(pointer + _lengthSize, pointer + _lengthSize + length)); |
| } |
| |
| /// Writes [type] at [pointer]. |
| void _writeType(_Pointer pointer, Type type) { |
| _explanations?.push('_writeType $type'); |
| _setByte(pointer, type.index); |
| _explanations?.pop(); |
| } |
| |
| /// Reads the `Type` at [pointer]. |
| Type _readType(_Pointer pointer) { |
| return Type.values[_buffer[pointer]]; |
| } |
| |
| /// Writes [length] at [pointer]. |
| void _writeLength(_Pointer pointer, int length, |
| {bool allowOverwrite = false}) { |
| _explanations?.push('_writeLength $length'); |
| __writeUint32(pointer, length, allowOverwrite: allowOverwrite); |
| _explanations?.pop(); |
| } |
| |
| /// Reads the length at [_Pointer]. |
| _Pointer _readLength(_Pointer pointer) => _readUint32(pointer); |
| |
| /// Adds [pointer] to the buffer, returns a new [_Pointer] to it. |
| _Pointer _addPointerTo(_Pointer pointer) { |
| _explanations?.push('_addPointerToPointer $pointer'); |
| final result = _reserve(_pointerSize); |
| _writePointer(result, pointer); |
| _explanations?.pop(); |
| return result; |
| } |
| |
| /// Writes [pointerValue] at [pointer]. |
| void _writePointer(_Pointer pointer, _Pointer pointerValue) { |
| _explanations?.push('_writePointer $pointerValue'); |
| __writeUint32(pointer, pointerValue); |
| _explanations?.pop(); |
| } |
| |
| /// Reads the [_Pointer] at [_Pointer]. |
| _Pointer _readPointer(_Pointer pointer) => _readUint32(pointer); |
| |
| /// Writes [value] at [pointer]. |
| void _writeUint32(_Pointer pointer, int value) { |
| _explanations?.push('_writeUint32 $value'); |
| __writeUint32(pointer, value); |
| _explanations?.pop(); |
| } |
| |
| void __writeUint32(_Pointer pointer, int value, |
| {bool allowOverwrite = false}) { |
| _setFourBytes(pointer, value & 0xff, (value >> 8) & 0xff, |
| (value >> 16) & 0xff, (value >> 24) & 0xff, |
| allowOverwrite: allowOverwrite); |
| } |
| |
| /// Reads the uint32 at [_Pointer]. |
| int _readUint32(_Pointer pointer) => |
| _buffer[pointer] + |
| (_buffer[pointer + 1] << 8) + |
| (_buffer[pointer + 2] << 16) + |
| (_buffer[pointer + 3] << 24); |
| |
| /// Writes [boolean] at [pointer]. |
| void _writeBoolean(_Pointer pointer, bool boolean) { |
| _explanations?.push('_writeBoolean $boolean'); |
| _setByte(pointer, boolean ? 1 : 0); |
| _explanations?.pop(); |
| } |
| |
| /// Reads the `bool` at [_Pointer]. |
| bool _readBoolean(_Pointer pointer) { |
| switch (_buffer[pointer]) { |
| case 0: |
| return false; |
| case 1: |
| return true; |
| default: |
| throw StateError('Not a valid boolean: ${_buffer[pointer]}'); |
| } |
| } |
| |
| /// Reads the bit at [pointer], index [bitIndex]. |
| bool _readBit(_Pointer pointer, int bitIndex) { |
| return ((_buffer[pointer] >> bitIndex) & 1) == 1; |
| } |
| |
| /// Sets the byte at [pointer] to [uint8]. |
| /// |
| /// If [_explanations] is being used, [allowOverwrite] controls whether |
| /// multiple writes to the same byte will be allowed or will throw. |
| void _setByte(_Pointer pointer, int uint8, {bool allowOverwrite = false}) { |
| _checkAllowWrites(); |
| _explanations?.explain(pointer, allowOverwrite: allowOverwrite); |
| _buffer[pointer] = uint8; |
| } |
| |
| /// Sets the four bytes at [pointer] to [b1] [b2] [b3] [b4]. |
| /// |
| /// If [_explanations] is being used, [allowOverwrite] controls whether |
| /// multiple writes to the same byte will be allowed or will throw. |
| void _setFourBytes(_Pointer pointer, int b1, int b2, int b3, int b4, |
| {bool allowOverwrite = false}) { |
| _setByte(pointer, b1, allowOverwrite: allowOverwrite); |
| _setByte(pointer + 1, b2, allowOverwrite: allowOverwrite); |
| _setByte(pointer + 2, b3, allowOverwrite: allowOverwrite); |
| _setByte(pointer + 3, b4, allowOverwrite: allowOverwrite); |
| } |
| |
| /// Sets the rang of bytes [from] until [to] to [bytes]. |
| void _setRange(_Pointer from, _Pointer to, Uint8List bytes) { |
| _checkAllowWrites(); |
| _explanations?.explainRange(from, to); |
| _buffer.setRange(from, to, bytes); |
| } |
| |
| /// Reserves [bytes] number of bytes. |
| /// |
| /// Increases `_nextFree` accordingly. Expands the buffer if necessary. |
| /// |
| /// Returns a [_Pointer] to the reserved space. |
| _Pointer _reserve(int bytes) { |
| _checkAllowWrites(); |
| final result = _nextFree; |
| _nextFree += bytes; |
| while (_nextFree > _buffer.length) { |
| // TODO(davidmorgan): pass desired size to avoid multiple copies. |
| _expand(); |
| } |
| return result; |
| } |
| |
| /// Copies into a new buffer that's twice as big. |
| void _expand() { |
| final oldBuffer = _buffer; |
| _buffer = Uint8List(_buffer.length * 2); |
| _buffer.setRange(0, oldBuffer.length, oldBuffer); |
| } |
| |
| /// Throws if writes are not allowed. |
| void _checkAllowWrites() { |
| if (!_allowWrites) throw StateError('This JsonBufferBuilder is read-only.'); |
| } |
| |
| /// Explanations of each byte in the buffer. |
| /// |
| /// Run tests with `dart -Ddebug_json_buffer=true test -c source` to enable. |
| /// |
| /// Then, [JsonBufferBuilder#toString] prints additional information for |
| /// each byte. |
| final _Explanations? _explanations = |
| const bool.fromEnvironment('debug_json_buffer') ? _Explanations() : null; |
| |
| @override |
| String toString() { |
| if (_explanations == null) return 'JsonBufferBuilder($_nextFree, $_buffer)'; |
| final result = StringBuffer('JsonBufferBuilder($_nextFree):\n'); |
| for (var i = 0; i != _nextFree; ++i) { |
| final value = _buffer[i]; |
| final explanation = _explanations._explanationsByPointer[i]; |
| if (explanation == null) { |
| result.writeln('$i: $value'); |
| } else { |
| result.writeln('$i: $value, $explanation'); |
| } |
| } |
| return result.toString(); |
| } |
| } |