blob: d44948de550f4aa74099ab85d1649cdc390edcd0 [file] [log] [blame]
// Copyright (c) 2015, Google Inc. 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 '../map.dart';
/// The Built Collection builder for [BuiltMap].
///
/// It implements the mutating part of the [Map] interface.
///
/// See the
/// [Built Collection library documentation](#built_collection/built_collection)
/// for the general properties of Built Collections.
class MapBuilder<K, V> {
/// Used by [_createMap] to instantiate [_map]. The default value is `null`.
_MapFactory<K, V>? _mapFactory;
late Map<K, V> _map;
_BuiltMap<K, V>? _mapOwner;
/// Instantiates with elements from a [Map] or [BuiltMap].
factory MapBuilder([map = const {}]) {
return MapBuilder<K, V>._uninitialized()..replace(map);
}
/// Converts to a [BuiltMap].
///
/// The `MapBuilder` can be modified again and used to create any number
/// of `BuiltMap`s.
BuiltMap<K, V> build() {
_mapOwner ??= _BuiltMap<K, V>.withSafeMap(_mapFactory, _map);
return _mapOwner!;
}
/// Applies a function to `this`.
void update(Function(MapBuilder<K, V> builder) updates) {
updates(this);
}
/// Replaces all elements with elements from a [Map] or [BuiltMap].
void replace(Object map) {
if (map is _BuiltMap<K, V> && map._mapFactory == _mapFactory) {
_setOwner(map);
} else if (map is BuiltMap) {
var replacement = _createMap();
map.forEach((dynamic key, dynamic value) {
replacement[key as K] = value as V;
});
_setSafeMap(replacement);
} else if (map is Map) {
var replacement = _createMap();
map.forEach((dynamic key, dynamic value) {
replacement[key as K] = value as V;
});
_setSafeMap(replacement);
} else {
throw ArgumentError('expected Map or BuiltMap, got ${map.runtimeType}');
}
}
/// Uses `base` as the collection type for all maps created by this builder.
///
/// // Iterates over elements in ascending order.
/// new MapBuilder<int, String>()
/// ..withBase(() => new SplayTreeMap<int, String>());
///
/// // Uses custom equality.
/// new MapBuilder<int, String>()
/// ..withBase(() => new LinkedHashMap<int, String>(
/// equals: (int a, int b) => a % 255 == b % 255,
/// hashCode: (int n) => (n % 255).hashCode));
///
/// The map returned by `base` must be empty, mutable, and each call must
/// instantiate and return a new object.
///
/// Use [withDefaultBase] to reset `base` to the default value.
void withBase(_MapFactory<K, V> base) {
ArgumentError.checkNotNull(base, 'base');
_mapFactory = base;
_setSafeMap(_createMap()..addAll(_map));
}
/// As [withBase], but sets `base` back to the default value, which
/// instantiates `Map<K, V>`.
void withDefaultBase() {
_mapFactory = null;
_setSafeMap(_createMap()..addAll(_map));
}
/// As [Map.fromIterable] but adds.
///
/// [key] and [value] default to the identity function.
void addIterable<T>(Iterable<T> iterable,
{K Function(T)? key, V Function(T)? value}) {
key ??= (T x) => x as K;
value ??= (T x) => x as V;
for (var element in iterable) {
this[key(element)] = value(element);
}
}
// Based on Map.
/// As [Map].
V? operator [](Object? key) => _map[key];
/// As [Map].
void operator []=(K key, V value) {
_checkKey(key);
_checkValue(value);
_safeMap[key] = value;
}
/// As [Map.length].
int get length => _map.length;
/// As [Map.isEmpty].
bool get isEmpty => _map.isEmpty;
/// As [Map.isNotEmpty].
bool get isNotEmpty => _map.isNotEmpty;
/// As [Map.putIfAbsent].
V putIfAbsent(K key, V Function() ifAbsent) {
_checkKey(key);
return _safeMap.putIfAbsent(key, () {
var value = ifAbsent();
_checkValue(value);
return value;
});
}
/// As [Map.addAll].
void addAll(Map<K, V> other) {
_checkKeys(other.keys);
_checkValues(other.values);
_safeMap.addAll(other);
}
/// As [Map.remove].
V? remove(Object? key) => _safeMap.remove(key);
/// As [Map.removeWhere].
void removeWhere(bool Function(K, V) predicate) {
_safeMap.removeWhere(predicate);
}
/// As [Map.clear].
void clear() {
_safeMap.clear();
}
/// As [Map.addEntries].
void addEntries(Iterable<MapEntry<K, V>> newEntries) {
_safeMap.addEntries(newEntries);
}
/// As [Map.update].
V updateValue(K key, V Function(V) update, {V Function()? ifAbsent}) =>
_safeMap.update(key, update, ifAbsent: ifAbsent);
/// As [Map.updateAll].
void updateAllValues(V Function(K, V) update) {
_safeMap.updateAll(update);
}
// Internal.
MapBuilder._uninitialized();
MapBuilder._fromBuiltMap(_BuiltMap<K, V> map)
: _mapFactory = map._mapFactory,
_map = map._map,
_mapOwner = map;
void _setOwner(_BuiltMap<K, V> mapOwner) {
assert(mapOwner._mapFactory == _mapFactory,
"Can't reuse a built map that uses a different base");
_mapOwner = mapOwner;
_map = mapOwner._map;
}
void _setSafeMap(Map<K, V> map) {
_mapOwner = null;
_map = map;
}
Map<K, V> get _safeMap {
if (_mapOwner != null) {
_map = _createMap()..addAll(_map);
_mapOwner = null;
}
return _map;
}
Map<K, V> _createMap() => _mapFactory != null ? _mapFactory!() : <K, V>{};
void _checkKey(K key) {
if (isSoundMode) return;
if (null is K) return;
if (identical(key, null)) {
throw ArgumentError('null key');
}
}
void _checkKeys(Iterable<K> keys) {
if (isSoundMode) return;
if (null is K) return;
for (var key in keys) {
_checkKey(key);
}
}
void _checkValue(V value) {
if (isSoundMode) return;
if (null is V) return;
if (identical(value, null)) {
throw ArgumentError('null value');
}
}
void _checkValues(Iterable<V> values) {
if (isSoundMode) return;
if (null is V) return;
for (var value in values) {
_checkValue(value);
}
}
}