| // Copyright (c) 2015, 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. |
| |
| part of protobuf; |
| |
| class _ExtensionFieldSet { |
| final _FieldSet _parent; |
| final Map<int, Extension> _info = <int, Extension>{}; |
| final Map<int, dynamic> _values = <int, dynamic>{}; |
| bool _isReadOnly = false; |
| |
| _ExtensionFieldSet(this._parent); |
| |
| Extension? _getInfoOrNull(int? tagNumber) => _info[tagNumber]; |
| |
| dynamic _getFieldOrDefault(Extension fi) { |
| if (fi.isRepeated) return _getList(fi); |
| _validateInfo(fi); |
| // TODO(skybrian) seems unnecessary to add info? |
| // I think this was originally here for repeated extensions. |
| _addInfoUnchecked(fi); |
| var value = _getFieldOrNull(fi); |
| if (value == null) { |
| _checkNotInUnknown(fi); |
| return fi.makeDefault!(); |
| } |
| return value; |
| } |
| |
| bool _hasField(int tagNumber) { |
| var value = _values[tagNumber]; |
| if (value == null) return false; |
| if (value is List) return value.isNotEmpty; |
| return true; |
| } |
| |
| /// Ensures that the list exists and an extension is present. |
| /// |
| /// If it doesn't exist, creates the list and saves the extension. |
| /// Suitable for public API and decoders. |
| List<T?> _ensureRepeatedField<T>(Extension<T?> fi) { |
| assert(!_isReadOnly); |
| assert(fi.isRepeated); |
| assert(fi.extendee == '' || fi.extendee == _parent._messageName); |
| |
| var list = _values[fi.tagNumber]; |
| if (list != null) return list as List<T>; |
| |
| return _addInfoAndCreateList(fi) as List<T?>; |
| } |
| |
| List<T?> _getList<T>(Extension<T?> fi) { |
| var value = _values[fi.tagNumber]; |
| if (value != null) return value as List<T>; |
| _checkNotInUnknown(fi); |
| if (_isReadOnly) return List<T>.unmodifiable(const []); |
| return _addInfoAndCreateList(fi) as List<T?>; |
| } |
| |
| List _addInfoAndCreateList(Extension fi) { |
| _validateInfo(fi); |
| var newList = fi._createRepeatedField(_parent._message!); |
| _addInfoUnchecked(fi); |
| _setFieldUnchecked(fi, newList); |
| return newList; |
| } |
| |
| dynamic _getFieldOrNull(Extension extension) => _values[extension.tagNumber]; |
| |
| void _clearFieldAndInfo(Extension fi) { |
| _clearField(fi); |
| _info.remove(fi.tagNumber); |
| } |
| |
| void _clearField(Extension fi) { |
| _ensureWritable(); |
| _validateInfo(fi); |
| if (_parent._hasObservers) _parent._eventPlugin!.beforeClearField(fi); |
| _values.remove(fi.tagNumber); |
| } |
| |
| /// Sets a value for a non-repeated extension that has already been added. |
| /// Does error-checking. |
| void _setField(int tagNumber, value) { |
| var fi = _getInfoOrNull(tagNumber); |
| if (fi == null) { |
| throw ArgumentError( |
| 'tag $tagNumber not defined in $_parent._messageName'); |
| } |
| if (fi.isRepeated) { |
| throw ArgumentError(_parent._setFieldFailedMessage( |
| fi, value, 'repeating field (use get + .add())')); |
| } |
| _ensureWritable(); |
| _parent._validateField(fi, value); |
| _setFieldUnchecked(fi, value); |
| } |
| |
| /// Sets a non-repeated value and extension. |
| /// Overwrites any existing extension. |
| void _setFieldAndInfo(Extension fi, value) { |
| _ensureWritable(); |
| if (fi.isRepeated) { |
| throw ArgumentError(_parent._setFieldFailedMessage( |
| fi, value, 'repeating field (use get + .add())')); |
| } |
| _ensureWritable(); |
| _validateInfo(fi); |
| _parent._validateField(fi, value); |
| _addInfoUnchecked(fi); |
| _setFieldUnchecked(fi, value); |
| } |
| |
| void _ensureWritable() { |
| if (_isReadOnly) frozenMessageModificationHandler(_parent._messageName); |
| } |
| |
| void _validateInfo(Extension fi) { |
| if (fi.extendee != _parent._messageName) { |
| throw ArgumentError( |
| 'Extension $fi not legal for message ${_parent._messageName}'); |
| } |
| } |
| |
| void _addInfoUnchecked(Extension fi) { |
| assert(fi.extendee == _parent._messageName); |
| _info[fi.tagNumber] = fi; |
| } |
| |
| void _setFieldUnchecked(Extension fi, value) { |
| if (_parent._hasObservers) { |
| _parent._eventPlugin!.beforeSetField(fi, value); |
| } |
| _values[fi.tagNumber] = value; |
| } |
| |
| // Bulk operations |
| |
| Iterable<int> get _tagNumbers => _values.keys; |
| Iterable<Extension> get _infos => _info.values; |
| |
| bool get _hasValues => _values.isNotEmpty; |
| |
| bool _equalValues(_ExtensionFieldSet? other) => |
| other != null && _areMapsEqual(_values, other._values); |
| |
| void _clearValues() => _values.clear(); |
| |
| /// Makes a shallow copy of all values from [original] to this. |
| /// |
| /// Repeated fields are copied. |
| /// Extensions cannot contain map fields. |
| void _shallowCopyValues(_ExtensionFieldSet original) { |
| for (var tagNumber in original._tagNumbers) { |
| var extension = original._getInfoOrNull(tagNumber)!; |
| _addInfoUnchecked(extension); |
| |
| final value = original._getFieldOrNull(extension); |
| if (value == null) continue; |
| if (extension.isRepeated) { |
| assert(value is PbListBase); |
| _ensureRepeatedField(extension).addAll(value); |
| } else { |
| _setFieldUnchecked(extension, value); |
| } |
| } |
| } |
| |
| void _markReadOnly() { |
| if (_isReadOnly) return; |
| _isReadOnly = true; |
| for (var field in _info.values) { |
| if (field.isRepeated) { |
| final entries = _values[field.tagNumber]; |
| if (entries == null) continue; |
| if (field.isGroupOrMessage) { |
| for (var subMessage in entries as List<GeneratedMessage>) { |
| subMessage.freeze(); |
| } |
| } |
| _values[field.tagNumber] = entries.toFrozenPbList(); |
| } else if (field.isGroupOrMessage) { |
| final entry = _values[field.tagNumber]; |
| if (entry != null) { |
| (entry as GeneratedMessage).freeze(); |
| } |
| } |
| } |
| } |
| |
| void _checkNotInUnknown(Extension extension) { |
| if (_parent._hasUnknownFields && |
| _parent._unknownFields!.hasField(extension.tagNumber)) { |
| throw StateError( |
| 'Trying to get $extension that is present as an unknown field. ' |
| 'Parse the message with this extension in the extension registry or ' |
| 'use `ExtensionRegistry.reparseMessage`.'); |
| } |
| } |
| } |