| // 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(); |
| } |