blob: 71e454835770f70ce761278c937e46f382b27b4f [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 built_collection.map;
/// 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;
Map<K, V> _map;
_BuiltMap<K, V> _mapOwner;
/// Instantiates with elements from a [Map] or [BuiltMap].
///
/// Must be called with a generic type parameter.
///
/// Wrong: `new MapBuilder({1: '1', 2: '2', 3: '3'})`.
///
/// Right: `new MapBuilder<int, String>({1: '1', 2: '2', 3: '3'})`,
///
/// Rejects nulls. Rejects keys and values of the wrong type.
factory MapBuilder([map = const {}]) {
return new 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() {
if (_mapOwner == null) {
_mapOwner = new _BuiltMap<K, V>.withSafeMap(_mapFactory, _map);
}
return _mapOwner;
}
/// Applies a function to `this`.
void update(updates(MapBuilder<K, V> builder)) {
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) {
final replacement = _createMap();
map.forEach((Object key, Object value) {
replacement[key as K] = value as V;
});
_setSafeMap(replacement);
} else if (map is Map) {
final replacement = _createMap();
map.forEach((Object key, Object value) {
replacement[key as K] = value as V;
});
_setSafeMap(replacement);
} else {
throw new 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) {
if (base == null) {
throw new ArgumentError.notNull('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 key(T element), V value(T element)}) {
if (key == null) key = (T x) => x as K;
if (value == null) value = (T x) => x as V;
for (final 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 ifAbsent()) {
_checkKey(key);
return _safeMap.putIfAbsent(key, () {
final 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 predicate(K key, V value)) {
_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 update(V value), {V ifAbsent()}) =>
_safeMap.update(key, update, ifAbsent: ifAbsent);
/// As [Map.updateAll].
void updateAllValues(V update(K key, V value)) {
_safeMap.updateAll(update);
}
// Internal.
MapBuilder._uninitialized() {
_checkGenericTypeParameter();
}
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() : new Map<K, V>();
void _checkGenericTypeParameter() {
if (K == dynamic) {
throw new UnsupportedError(
'explicit key type required, for example "new MapBuilder<int, int>"');
}
if (V == dynamic) {
throw new UnsupportedError('explicit value type required, '
'for example "new MapBuilder<int, int>"');
}
}
void _checkKey(K key) {
if (identical(key, null)) {
throw new ArgumentError('null key');
}
}
void _checkKeys(Iterable keys) {
for (final key in keys) {
if (key is! K) {
throw new ArgumentError('invalid key: $key');
}
}
}
void _checkValue(V value) {
if (identical(value, null)) {
throw new ArgumentError('null value');
}
}
void _checkValues(Iterable values) {
for (final value in values) {
if (value is! V) {
throw new ArgumentError('invalid value: $value');
}
}
}
}