blob: 5d0a21561276179ef7378f4732ad3057a264e408 [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.list_multimap;
/// The Built Collection builder for [BuiltListMultimap].
///
/// It implements the mutating part of the [ListMultimap] interface.
///
/// See the
/// [Built Collection library documentation](#built_collection/built_collection)
/// for the general properties of Built Collections.
class ListMultimapBuilder<K, V> {
// BuiltLists copied from another instance so they can be reused directly for
// keys without changes.
Map<K, BuiltList<V>> _builtMap;
// Instance that _builtMap belongs to. If present, _builtMap must not be
// mutated.
_BuiltListMultimap<K, V> _builtMapOwner;
// ListBuilders for keys that are being changed.
Map<K, ListBuilder<V>> _builderMap;
/// Instantiates with elements from a [Map], [ListMultimap] or
/// [BuiltListMultimap].
///
/// Must be called with a generic type parameter.
///
/// Wrong:
/// `new ListMultimapBuilder({1: ['1'], 2: ['2'], 3: ['3']})`.
///
/// Right:
/// `new ListMultimapBuilder<int, String>({1: ['1'], 2: ['2'], 3: ['3']})`,
///
/// Rejects nulls. Rejects keys and values of the wrong type.
factory ListMultimapBuilder([multimap = const {}]) {
return new ListMultimapBuilder<K, V>._uninitialized()..replace(multimap);
}
/// Converts to a [BuiltListMultimap].
///
/// The `ListMultimapBuilder` can be modified again and used to create any
/// number of `BuiltListMultimap`s.
BuiltListMultimap<K, V> build() {
if (_builtMapOwner == null) {
for (final key in _builderMap.keys) {
final builtList = _builderMap[key].build();
if (builtList.isEmpty) {
_builtMap.remove(key);
} else {
_builtMap[key] = builtList;
}
}
_builtMapOwner = new _BuiltListMultimap<K, V>.withSafeMap(_builtMap);
}
return _builtMapOwner;
}
/// Applies a function to `this`.
void update(updates(ListMultimapBuilder<K, V> builder)) {
updates(this);
}
/// Replaces all elements with elements from a [Map], [ListMultimap] or
/// [BuiltListMultimap].
void replace(dynamic multimap) {
if (multimap is _BuiltListMultimap<K, V>) {
_setOwner(multimap);
} else if (multimap is Map ||
multimap is ListMultimap ||
multimap is BuiltListMultimap) {
_setWithCopyAndCheck(multimap.keys, (k) => multimap[k]);
} else {
throw new ArgumentError(
'expected Map, ListMultimap or BuiltListMultimap, '
'got ${multimap.runtimeType}');
}
}
/// As [Map.fromIterable] but adds.
///
/// Additionally, you may specify [values] instead of [value]. This new
/// parameter allows you to supply a function that returns an [Iterable]
/// of values.
///
/// [key] and [value] default to the identity function. [values] is ignored
/// if not specified.
void addIterable<T>(Iterable<T> iterable,
{K key(T element), V value(T element), Iterable<V> values(T element)}) {
if (value != null && values != null) {
throw new ArgumentError('expected value or values to be set, got both');
}
if (key == null) key = (T x) => x as K;
if (values != null) {
for (final element in iterable) {
this.addValues(key(element), values(element));
}
} else {
if (value == null) value = (T x) => x as V;
for (final element in iterable) {
this.add(key(element), value(element));
}
}
}
// Based on ListMultimap.
/// As [ListMultimap.add].
void add(K key, V value) {
_makeWriteableCopy();
_checkKey(key);
_checkValue(value);
_getValuesBuilder(key).add(value);
}
/// As [ListMultimap.addValues].
void addValues(K key, Iterable<V> values) {
// _disown is called in add.
values.forEach((value) {
add(key, value);
});
}
/// As [ListMultimap.addAll].
void addAll(ListMultimap<K, V> other) {
// _disown is called in add.
other.forEach((key, value) {
add(key, value);
});
}
/// As [ListMultimap.remove] but returns nothing.
void remove(Object key, V value) {
if (key is K) {
_makeWriteableCopy();
_getValuesBuilder(key).remove(value);
}
}
/// As [ListMultimap.removeAll] but returns nothing.
void removeAll(Object key) {
if (key is K) {
_makeWriteableCopy();
_builtMap = _builtMap;
_builderMap[key] = new ListBuilder<V>();
}
}
/// As [ListMultimap.clear].
void clear() {
_makeWriteableCopy();
_builtMap.clear();
_builderMap.clear();
}
// Internal.
ListBuilder<V> _getValuesBuilder(K key) {
var result = _builderMap[key];
if (result == null) {
final builtValues = _builtMap[key];
if (builtValues == null) {
result = new ListBuilder<V>();
} else {
result = builtValues.toBuilder();
}
_builderMap[key] = result;
}
return result;
}
void _makeWriteableCopy() {
if (_builtMapOwner != null) {
_builtMap = new Map<K, BuiltList<V>>.from(_builtMap);
_builtMapOwner = null;
}
}
ListMultimapBuilder._uninitialized() {
_checkGenericTypeParameter();
}
void _setOwner(_BuiltListMultimap<K, V> builtListMultimap) {
_builtMapOwner = builtListMultimap;
_builtMap = builtListMultimap._map;
_builderMap = new Map<K, ListBuilder<V>>();
}
void _setWithCopyAndCheck(Iterable keys, Function lookup) {
_builtMapOwner = null;
_builtMap = new Map<K, BuiltList<V>>();
_builderMap = new Map<K, ListBuilder<V>>();
for (final key in keys) {
if (key is K) {
for (final value in lookup(key)) {
if (value is V) {
add(key, value);
} else {
throw new ArgumentError(
'map contained invalid value: $value, for key $key');
}
}
} else {
throw new ArgumentError('map contained invalid key: $key');
}
}
}
void _checkGenericTypeParameter() {
if (K == dynamic) {
throw new UnsupportedError('explicit key type required, '
'for example "new ListMultimapBuilder<int, int>"');
}
if (V == dynamic) {
throw new UnsupportedError('explicit value type required, '
'for example "new ListMultimapBuilder<int, int>"');
}
}
void _checkKey(K key) {
if (identical(key, null)) {
throw new ArgumentError('null key');
}
}
void _checkValue(V value) {
if (identical(value, null)) {
throw new ArgumentError('null value');
}
}
}