| // 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 'dart:collection' show MapBase; |
| |
| import 'internal.dart'; |
| import 'utils.dart'; |
| |
| const mapKeyFieldNumber = 1; |
| const mapValueFieldNumber = 2; |
| |
| @pragma('dart2js:tryInline') |
| @pragma('vm:prefer-inline') |
| @pragma('wasm:prefer-inline') |
| PbMap<K, V> newPbMap<K, V>(int keyFieldType, int valueFieldType) => |
| PbMap<K, V>._( |
| keyFieldType, |
| valueFieldType, |
| getCheckFunction(keyFieldType), |
| getCheckFunction(valueFieldType), |
| ); |
| |
| @pragma('dart2js:tryInline') |
| @pragma('vm:prefer-inline') |
| @pragma('wasm:prefer-inline') |
| PbMap<K, V> newUnmodifiablePbMap<K, V>(int keyFieldType, int valueFieldType) => |
| PbMap<K, V>._unmodifiable(keyFieldType, valueFieldType); |
| |
| /// A [MapBase] implementation used for protobuf `map` fields. |
| class PbMap<K, V> extends MapBase<K, V> { |
| /// Key type of the map. Per proto2 and proto3 specs, this needs to be an |
| /// integer type or `string`, and the type cannot be `repeated`. |
| /// |
| /// The `int` value is interpreted the same way as [FieldInfo.type]. |
| final int keyFieldType; |
| |
| /// Value type of the map. Per proto2 and proto3 specs, this can be any type |
| /// other than `map`, and the type cannot be `repeated`. |
| /// |
| /// The `int` value is interpreted the same way as [FieldInfo.type]. |
| final int valueFieldType; |
| |
| /// The actual list storing the elements. |
| /// |
| /// Note: We want only one [Map] implementation class to be stored here to |
| /// make sure the map operations are monomorphic and can be inlined. In |
| /// constructors make sure initializers for this field all return the same |
| /// implementation class. |
| final Map<K, V> _wrappedMap; |
| |
| bool _isReadOnly = false; |
| |
| final CheckFunc<K>? _checkKey; |
| final CheckFunc<V>? _checkValue; |
| |
| PbMap._( |
| this.keyFieldType, |
| this.valueFieldType, |
| this._checkKey, |
| this._checkValue, |
| ) : _wrappedMap = <K, V>{}; |
| |
| PbMap._unmodifiable(this.keyFieldType, this.valueFieldType) |
| : _wrappedMap = <K, V>{}, |
| _isReadOnly = true, |
| _checkKey = null, |
| _checkValue = null; |
| |
| @override |
| V? operator [](Object? key) => _wrappedMap[key]; |
| |
| @override |
| void operator []=(K key, V value) { |
| if (_isReadOnly) { |
| throw UnsupportedError('Attempted to change a read-only map field'); |
| } |
| if (_checkKey != null) { |
| _checkKey(key); |
| } |
| if (_checkValue != null) { |
| _checkValue(value); |
| } |
| _wrappedMap[key] = value; |
| } |
| |
| /// A [PbMap] is equal to another [PbMap] with equal key/value pairs in any |
| /// order. |
| @override |
| bool operator ==(Object other) { |
| if (identical(other, this)) { |
| return true; |
| } |
| if (other is! PbMap) { |
| return false; |
| } |
| if (other.length != length) { |
| return false; |
| } |
| for (final key in keys) { |
| if (other[key] != this[key]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /// A [PbMap] is equal to another [PbMap] with equal key/value pairs in any |
| /// order. Then, the `hashCode` is guaranteed to be the same. |
| @override |
| int get hashCode { |
| return _wrappedMap.entries.fold( |
| 0, |
| (h, entry) => h ^ HashUtils.hash2(entry.key, entry.value), |
| ); |
| } |
| |
| @override |
| void clear() { |
| if (_isReadOnly) { |
| throw UnsupportedError('Attempted to change a read-only map field'); |
| } |
| _wrappedMap.clear(); |
| } |
| |
| @override |
| Iterable<K> get keys => _wrappedMap.keys; |
| |
| @override |
| V? remove(Object? key) { |
| if (_isReadOnly) { |
| throw UnsupportedError('Attempted to change a read-only map field'); |
| } |
| return _wrappedMap.remove(key); |
| } |
| |
| PbMap freeze() { |
| _isReadOnly = true; |
| if (PbFieldType.isGroupOrMessage(valueFieldType)) { |
| for (final subMessage in values as Iterable<GeneratedMessage>) { |
| subMessage.freeze(); |
| } |
| } |
| return this; |
| } |
| |
| PbMap<K, V> _deepCopy() { |
| final newMap = PbMap<K, V>._( |
| keyFieldType, |
| valueFieldType, |
| _checkKey, |
| _checkValue, |
| ); |
| final wrappedMap = _wrappedMap; |
| final newWrappedMap = newMap._wrappedMap; |
| if (PbFieldType.isGroupOrMessage(valueFieldType)) { |
| for (final entry in wrappedMap.entries) { |
| newWrappedMap[entry.key] = |
| (entry.value as GeneratedMessage).deepCopy() as V; |
| } |
| } else { |
| newWrappedMap.addAll(wrappedMap); |
| } |
| return newMap; |
| } |
| } |
| |
| extension PbMapInternalExtension<K, V> on PbMap<K, V> { |
| @pragma('dart2js:tryInline') |
| @pragma('vm:prefer-inline') |
| @pragma('wasm:prefer-inline') |
| PbMap<K, V> deepCopy() => _deepCopy(); |
| } |