blob: 32f46e574754956890acbf6c4782975bcde5f0db [file] [log] [blame]
// Copyright (c) 2018, 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 'data_sink.dart';
import 'data_source.dart';
abstract class IndexedSource<E> {
E? read(E readValue());
/// Reshapes the cache to a [Map<E, int>] using [_getValue] if provided or
/// leaving the cache entry as is otherwise.
Map<T, int> reshape<T>([T Function(E? value)? getValue]);
}
abstract class IndexedSink<E> {
void write(E value, void writeValue(E value));
}
/// Data sink helper that canonicalizes [E?] values using indices.
///
/// Writes a list index in place of already indexed values. This list index
/// is the order in which we discover the indexable elements. On deserialization
/// the indexable elements but be visited in the same order they were seen here
/// so that the indices are maintained. Since the read order is assumed to be
/// consistent, the actual data is written at the first occurrence of the
/// indexable element.
class OrderedIndexedSink<E extends Object> implements IndexedSink<E> {
final DataSink _sink;
final Map<E?, int> cache;
OrderedIndexedSink._(this._sink, this.cache);
factory OrderedIndexedSink(DataSink sink, {Map<E?, int>? cache}) {
// [cache] slot 0 is pre-allocated to `null`.
cache ??= {null: 0};
return OrderedIndexedSink._(sink, cache);
}
/// Write a reference to [value] to the data sink.
///
/// If [value] has not been canonicalized yet, [writeValue] is called to
/// serialize the [value] itself.
@override
void write(E? value, void writeValue(E value)) {
const int pending = -1;
int? index = cache[value];
if (index == null) {
index = cache.length;
_sink.writeInt(index);
cache[value] = pending; // Increments length to allocate slot.
writeValue(value!); // `null` would have been found in slot 0.
cache[value] = index;
} else if (index == pending) {
throw ArgumentError("Cyclic dependency on cached value: $value");
} else {
_sink.writeInt(index);
}
}
}
/// Data source helper reads canonicalized [E?] values through indices.
///
/// Reads indexable elements treating their read order as their index. Since the
/// read order is consistent with the write order, when a new index is
/// discovered we assume the data is written immediately after. Subsequent
/// occurrences of that index then refer to the same value. Indices will appear
/// in ascending order.
class OrderedIndexedSource<E extends Object> implements IndexedSource<E> {
final DataSource _source;
final List<E?> cache;
OrderedIndexedSource._(this._source, this.cache);
factory OrderedIndexedSource(DataSource source, {List<E?>? cache}) {
// [cache] slot 0 is pre-allocated to `null`.
cache ??= [null];
return OrderedIndexedSource._(source, cache);
}
/// Reads a reference to an [E] value from the data source.
///
/// If the value hasn't yet been read, [readValue] is called to deserialize
/// the value itself.
@override
E? read(E readValue()) {
int index = _source.readInt();
if (index >= cache.length) {
assert(index == cache.length);
cache.add(null); // placeholder.
E value = readValue();
cache[index] = value;
return value;
} else {
E? value = cache[index];
if (value == null && index != 0) {
throw StateError('Unfilled index $index of $E');
}
return value;
}
}
@override
Map<T, int> reshape<T>([T Function(E? value)? getValue]) {
var newCache = <T, int>{};
for (int i = 0; i < cache.length; i++) {
final newKey = getValue == null ? cache[i] as T : getValue(cache[i]);
newCache[newKey] = i;
}
return newCache;
}
}