blob: 032b487e5366c43e93c321afe842259c1a2f50ef [file] [log] [blame]
// Copyright (c) 2011, 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 ListBase;
import 'dart:math' as math;
import 'internal.dart';
import 'utils.dart';
@pragma('dart2js:tryInline')
@pragma('vm:prefer-inline')
@pragma('wasm:prefer-inline')
PbList<E> newPbList<E>({CheckFunc<E>? check}) => PbList._(check: check);
@pragma('dart2js:tryInline')
@pragma('vm:prefer-inline')
@pragma('wasm:prefer-inline')
PbList<E> newUnmodifiablePbList<E>({CheckFunc<E>? check}) =>
PbList._unmodifiable();
/// A [ListBase] implementation used for protobuf `repeated` fields.
class PbList<E> extends ListBase<E> {
/// The actual list storing the elements.
///
/// Note: We want only one [List] implementation class to be stored here to
/// make sure the list operations are monomorphic and can be inlined. In
/// constructors make sure initializers for this field all return the same
/// implementation class. (e.g. `_GrowableList` on the VM)
final List<E> _wrappedList;
/// A growable list, to be used in `unmodifiable` constructor to avoid
/// allocating a list every time.
///
/// We can't use `const []` as it makes the `_wrappedList` field polymorphic.
static final _emptyList = <Never>[];
final CheckFunc<E>? _check;
bool _isReadOnly = false;
bool get isFrozen => _isReadOnly;
PbList._({CheckFunc<E>? check}) : _wrappedList = <E>[], _check = check;
PbList._unmodifiable()
: _wrappedList = _emptyList,
_check = null,
_isReadOnly = true;
@override
@pragma('dart2js:never-inline')
void add(E element) {
_checkModifiable('add');
if (_check != null) {
_check(element);
}
_wrappedList.add(element);
}
@pragma('dart2js:tryInline')
@pragma('vm:prefer-inline')
@pragma('wasm:prefer-inline')
void _addUnchecked(E element) {
_wrappedList.add(element);
}
@override
@pragma('dart2js:never-inline')
void addAll(Iterable<E> iterable) {
_checkModifiable('addAll');
if (_check != null) {
for (final e in iterable) {
_check(e);
_addUnchecked(e);
}
} else {
_wrappedList.addAll(iterable);
}
}
@override
Iterable<E> get reversed => _wrappedList.reversed;
@override
void sort([int Function(E a, E b)? compare]) {
_checkModifiable('sort');
_wrappedList.sort(compare);
}
@override
void shuffle([math.Random? random]) {
_checkModifiable('shuffle');
_wrappedList.shuffle(random);
}
@override
@pragma('dart2js:never-inline')
void clear() {
_checkModifiable('clear');
_wrappedList.clear();
}
@override
void insert(int index, E element) {
_checkModifiable('insert');
if (_check != null) {
_check(element);
}
_wrappedList.insert(index, element);
}
@override
void insertAll(int index, Iterable<E> iterable) {
_checkModifiable('insertAll');
if (_check != null) {
_wrappedList.insertAll(
index,
iterable.map((E e) {
_check(e);
return e;
}),
);
} else {
_wrappedList.insertAll(index, iterable);
}
}
@override
void setAll(int index, Iterable<E> iterable) {
_checkModifiable('setAll');
if (_check != null) {
_wrappedList.setAll(
index,
iterable.map((E e) {
_check(e);
return e;
}),
);
} else {
_wrappedList.setAll(index, iterable);
}
}
@override
bool remove(Object? element) {
_checkModifiable('remove');
return _wrappedList.remove(element);
}
@override
E removeAt(int index) {
_checkModifiable('removeAt');
return _wrappedList.removeAt(index);
}
@override
E removeLast() {
_checkModifiable('removeLast');
return _wrappedList.removeLast();
}
@override
void removeWhere(bool Function(E element) test) {
_checkModifiable('removeWhere');
return _wrappedList.removeWhere(test);
}
@override
void retainWhere(bool Function(E element) test) {
_checkModifiable('retainWhere');
return _wrappedList.retainWhere(test);
}
@override
void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) {
_checkModifiable('setRange');
if (_check != null) {
_wrappedList.setRange(
start,
end,
iterable.skip(skipCount).map((E e) {
_check(e);
return e;
}),
0,
);
} else {
_wrappedList.setRange(start, end, iterable, skipCount);
}
}
@override
void removeRange(int start, int end) {
_checkModifiable('removeRange');
_wrappedList.removeRange(start, end);
}
@override
void fillRange(int start, int end, [E? fill]) {
_checkModifiable('fillRange');
if (_check != null) {
_check(fill);
}
_wrappedList.fillRange(start, end, fill);
}
@override
void replaceRange(int start, int end, Iterable<E> newContents) {
_checkModifiable('replaceRange');
if (_check != null) {
_wrappedList.replaceRange(
start,
end,
newContents.map((E e) {
_check(e);
return e;
}),
);
} else {
_wrappedList.replaceRange(start, end, newContents);
}
}
@override
int get length => _wrappedList.length;
@override
bool get isEmpty => _wrappedList.isEmpty;
@override
bool get isNotEmpty => _wrappedList.isNotEmpty;
@override
@pragma('dart2js:never-inline')
Iterator<E> get iterator => _wrappedList.iterator;
@override
set length(int newLength) {
_checkModifiable('set length');
if (newLength > length) {
throw UnsupportedError('Extending protobuf lists is not supported');
}
_wrappedList.length = newLength;
}
@override
E operator [](int index) => _wrappedList[index];
@override
void operator []=(int index, E value) {
_checkModifiable('set element');
if (_check != null) {
_check(value);
}
_wrappedList[index] = value;
}
@override
bool operator ==(Object other) =>
other is PbList && areListsEqual(other, this);
@override
int get hashCode => HashUtils.hashObjects(_wrappedList);
void freeze() {
if (_isReadOnly) {
return;
}
_isReadOnly = true;
// Per spec `repeated map<..>` and `repeated repeated ..` are not allowed
// so we only check for messages
if (_wrappedList.isNotEmpty && _wrappedList[0] is GeneratedMessage) {
for (final elem in _wrappedList as Iterable<GeneratedMessage>) {
elem.freeze();
}
}
}
void _checkModifiable(String methodName) {
if (_isReadOnly) {
_readOnlyError(methodName);
}
}
static Never _readOnlyError(String methodName) {
throw UnsupportedError("'$methodName' on a read-only list");
}
PbList<E> _deepCopy() {
final newList = PbList<E>._(check: _check);
final wrappedList = _wrappedList;
final newWrappedList = newList._wrappedList;
if (wrappedList.isNotEmpty) {
if (wrappedList[0] is GeneratedMessage) {
for (final message in wrappedList) {
newWrappedList.add((message as GeneratedMessage).deepCopy() as E);
}
} else {
newWrappedList.addAll(wrappedList);
}
}
return newList;
}
}
extension PbListInternalExtension<E> on PbList<E> {
@pragma('dart2js:tryInline')
@pragma('vm:prefer-inline')
@pragma('wasm:prefer-inline')
void checkModifiable(String methodName) => _checkModifiable(methodName);
@pragma('dart2js:tryInline')
@pragma('vm:prefer-inline')
@pragma('wasm:prefer-inline')
void addUnchecked(E element) => _addUnchecked(element);
@pragma('dart2js:tryInline')
@pragma('vm:prefer-inline')
@pragma('wasm:prefer-inline')
PbList<E> deepCopy() => _deepCopy();
}