blob: 1b87485fb9baf8ab4f62002615d859a8d2faa6a6 [file] [log] [blame]
// 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.
part of 'json_buffer_builder.dart';
/// Methods for writing and reading "growable maps".
///
/// A "growable map" is a `Map<String, Object?>` in a byte buffer that is
/// implemented as a linked list of entries, so it can accept new entries.
extension GrowableMaps on JsonBufferBuilder {
// Layout:
//
// Empty map is [null pointer].
//
// Map with one entry is [pointer to first node], and that first node is
// [null pointer, entry].
//
// Then each additional entry adds a [pointer, entry] and sets the null
// pointer in the last entry to the new entry.
//
// Each entry is: [key (pointer to string), value type, value].
//
// Values are stored with `_writeAny`.
static const _keySize = _pointerSize;
static const _valueSize = _typeSize + _pointerSize;
static const _entrySize = _pointerSize + _keySize + _valueSize;
/// Creates a "growable map".
///
/// It can have new values added to it, hence "growable".
///
/// It is linked to the `JsonBufferBuilder` that creates it: it can be added
/// to any collection in the same `JsonBufferBuilder` without copying.
/// Adding to a collection in a different `JsonBufferBuilder` is an error.
///
/// The caller is responsible for adding a reference to the returned value
/// somewhere in the buffer. Otherwise, it won't be reachable from the
/// root [map].
Map<String, V> createGrowableMap<V>() {
_explanations?.push('addGrowableMap');
final pointer = _reserve(_pointerSize + _lengthSize);
// Initially a "growable map" is just a null pointer and zero size, so
// there is nothing to write.
_explanations?.pop();
return _readGrowableMap<V>(pointer, null);
}
/// Returns the [_Pointer] to [map].
///
/// The [map] must have been created in this buffer using
/// [createGrowableMap]. Otherwise, [UnsupportedError] is thrown.
_Pointer _pointerToGrowableMap(_GrowableMap<Object?> map) {
_checkGrowableMapOwnership(map);
return map.pointer;
}
/// Throws if [map is backed by a different buffer to `this`.
void _checkGrowableMapOwnership(_GrowableMap map) {
if (map.buffer != this) {
throw UnsupportedError(
'Maps created with `createGrowableMap` can only '
'be added to the JsonBufferBuilder instance that created them.',
);
}
}
/// Returns the [_GrowableMap] at [pointer].
Map<String, V> _readGrowableMap<V>(
_Pointer pointer,
Map<String, Object?>? parent,
) {
return _GrowableMap<V>(this, pointer, parent);
}
}
class _GrowableMap<V>
with MapMixin<String, V>, _EntryMapMixin<String, V>
implements MapInBuffer {
@override
final JsonBufferBuilder buffer;
@override
final _Pointer pointer;
@override
final Map<String, Object?>? parent;
int _length;
_Pointer? _lastPointer;
_GrowableMap(this.buffer, this.pointer, this.parent)
: _length = buffer._readLength(pointer + _pointerSize);
@override
int get length => _length;
@override
V? operator [](Object? key) {
// TODO(davidmorgan): these maps could be large, we probably need a more
// efficient lookup than linear search.
final iterator = entries.iterator as _GrowableMapEntryIterator<V>;
while (iterator.moveNext()) {
if (iterator.current.key == key) return iterator.current.value;
}
return null;
}
@override
late final Iterable<String> keys = _IteratorFunctionIterable(
() => _GrowableMapKeyIterator(buffer, this, pointer),
length: length,
);
@override
late final Iterable<V> values = _IteratorFunctionIterable(
() => _GrowableMapValueIterator<V>(buffer, this, pointer),
length: length,
);
@override
late final Iterable<MapEntry<String, V>> entries = _IteratorFunctionIterable(
() => _GrowableMapEntryIterator(buffer, this, pointer),
length: length,
);
/// Add [value] to the map with key [key].
///
/// This implementation does not correctly handle repeated adds with the
/// same [key]. Repeated adds will _not_ updated the associated value but
/// _will_ cause an increase in [length] and duplicates in the [keys]
/// iterable.
@override
void operator []=(String key, V value) {
buffer._explanations?.push('GrowableMap[]= $key $value');
// If `_lastPointer` is not set yet, walk the map to find the end of it.
if (_lastPointer == null) {
final iterator = _GrowableMapEntryIterator<V>(buffer, this, pointer);
_lastPointer = pointer;
while (iterator.moveNext()) {
_lastPointer = iterator._pointer;
}
}
// Reserve and write the new node.
final newPointer = buffer._reserve(GrowableMaps._entrySize);
final entryPointer = newPointer + _pointerSize;
buffer._writePointer(entryPointer, buffer._pointerToString(key));
buffer._writeAny(entryPointer + _pointerSize, value);
// Point to the new node in the previous node.
buffer._writePointer(_lastPointer!, newPointer);
// Update `_lastPointer` to the new node.
_lastPointer = newPointer;
// Update length.
++_length;
buffer._writeLength(pointer + _pointerSize, length, allowOverwrite: true);
buffer._explanations?.pop();
}
@override
V remove(Object? key) {
throw UnsupportedError('JsonBufferBuilder growable maps are append only.');
}
@override
void clear() {
throw UnsupportedError('JsonBufferBuilder growable maps are append only.');
}
@override
bool operator ==(Object other) =>
other is _GrowableMap &&
other.buffer == buffer &&
other.pointer == pointer;
@override
int get hashCode => Object.hash(buffer, pointer);
void buildDigest(ByteConversionSink byteSink) {
var iterator = _GrowableMapPointerIterator(buffer, null, pointer);
while (iterator.moveNext()) {
buffer._buildDigest(
iterator.current.$1,
byteSink,
type: Type.stringPointer,
);
buffer._buildDigest(iterator.current.$2, byteSink);
}
}
}
/// `Iterator` that reads a "growable map" in a [JsonBufferBuilder].
abstract class _GrowableMapIterator<T> implements Iterator<T> {
final JsonBufferBuilder _buffer;
final _GrowableMap? _parent;
_Pointer _pointer;
_GrowableMapIterator(this._buffer, this._parent, this._pointer);
@override
T get current;
String get _currentKey =>
_buffer._readString(_buffer._readPointer(_pointer + _pointerSize));
Object? get _currentValue => _buffer._readAny(
_pointer + _pointerSize + GrowableMaps._keySize,
parent: _parent,
);
@override
bool moveNext() {
_pointer = _buffer._readPointer(_pointer);
return _pointer != 0;
}
}
class _GrowableMapKeyIterator extends _GrowableMapIterator<String> {
_GrowableMapKeyIterator(super._buffer, super._parent, super._pointer);
@override
String get current => _currentKey;
}
class _GrowableMapValueIterator<V> extends _GrowableMapIterator<V> {
_GrowableMapValueIterator(super._buffer, super._parent, super._pointer);
@override
V get current => _currentValue as V;
}
class _GrowableMapEntryIterator<V>
extends _GrowableMapIterator<MapEntry<String, V>> {
_GrowableMapEntryIterator(super._buffer, super._parent, super._pointer);
@override
MapEntry<String, V> get current => MapEntry(_currentKey, _currentValue as V);
}
class _GrowableMapPointerIterator
extends _GrowableMapIterator<(int keyPointer, int valuePointer)> {
_GrowableMapPointerIterator(super._buffer, super._parent, super._pointer);
@override
(int keyPointer, int valuePointer) get current => (
_pointer + _pointerSize,
_pointer + _pointerSize + GrowableMaps._keySize,
);
}