blob: eb55a797669ae0386a9e21ff3f5ffe22c9eca5de [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 '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();
}