| // Copyright (c) 2012, 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; |
| |
| /// An object representing a protobuf message field. |
| class FieldInfo<T> { |
| FrozenPbList<T>? _emptyList; |
| |
| /// Name of this field as the `json_name` reported by protoc. |
| /// |
| /// This will typically be in camel case. |
| final String name; |
| |
| /// The name of this field as written in the proto-definition. |
| /// |
| /// This will typically consist of words separated with underscores. |
| final String protoName; |
| |
| final int tagNumber; |
| final int? index; // index of the field's value. Null for extensions. |
| final int type; |
| |
| // Constructs the default value of a field. |
| // (Only used for repeated fields where check is null.) |
| final MakeDefaultFunc? makeDefault; |
| |
| // Creates an empty message or group when decoding a message. |
| // Not used for other types. |
| // see GeneratedMessage._getEmptyMessage |
| final CreateBuilderFunc? subBuilder; |
| |
| // List of all enum enumValues. |
| // (Not used for other types.) |
| final List<ProtobufEnum>? enumValues; |
| |
| // Default enum value, if type is a PbList<ProtobufEnum> or a |
| // PbMap<[anything], ProtobufEnum>. |
| final ProtobufEnum? defaultEnumValue; |
| |
| // Looks up the enum value given its integer code. |
| // (Not used for other types.) |
| // see GeneratedMessage._getValueOfFunc |
| final ValueOfFunc? valueOf; |
| |
| // Verifies an item being added to a repeated field |
| // (Not used for non-repeated fields.) |
| final CheckFunc<T>? check; |
| |
| FieldInfo(this.name, this.tagNumber, this.index, this.type, |
| {dynamic defaultOrMaker, |
| this.subBuilder, |
| this.valueOf, |
| this.enumValues, |
| this.defaultEnumValue, |
| String? protoName}) |
| : makeDefault = findMakeDefault(type, defaultOrMaker), |
| check = null, |
| protoName = protoName ?? _unCamelCase(name), |
| assert(type != 0), |
| assert(!_isGroupOrMessage(type) || |
| subBuilder != null || |
| _isMapField(type)), |
| assert(!_isEnum(type) || valueOf != null); |
| |
| // Represents a field that has been removed by a program transformation. |
| FieldInfo.dummy(this.index) |
| : name = '<removed field>', |
| protoName = '<removed field>', |
| tagNumber = 0, |
| type = 0, |
| makeDefault = null, |
| valueOf = null, |
| check = null, |
| enumValues = null, |
| defaultEnumValue = null, |
| subBuilder = null; |
| |
| FieldInfo.repeated(this.name, this.tagNumber, this.index, this.type, |
| this.check, this.subBuilder, |
| {this.valueOf, this.enumValues, this.defaultEnumValue, String? protoName}) |
| : makeDefault = (() => PbList<T>(check: check!)), |
| protoName = protoName ?? _unCamelCase(name) { |
| ArgumentError.checkNotNull(name, 'name'); |
| ArgumentError.checkNotNull(tagNumber, 'tagNumber'); |
| assert(_isRepeated(type)); |
| assert(check != null); |
| assert(!_isEnum(type) || valueOf != null); |
| } |
| |
| static MakeDefaultFunc? findMakeDefault(int type, dynamic defaultOrMaker) { |
| if (defaultOrMaker == null) return PbFieldType._defaultForType(type); |
| if (defaultOrMaker is MakeDefaultFunc) return defaultOrMaker; |
| return () => defaultOrMaker; |
| } |
| |
| /// Returns `true` if this represents a dummy field standing in for a field |
| /// that has been removed by a program transformation. |
| bool get _isDummy => tagNumber == 0; |
| |
| bool get isRequired => _isRequired(type); |
| bool get isRepeated => _isRepeated(type); |
| bool get isGroupOrMessage => _isGroupOrMessage(type); |
| bool get isEnum => _isEnum(type); |
| bool get isMapField => _isMapField(type); |
| |
| /// Returns a read-only default value for a field. |
| /// (Unlike getField, doesn't create a repeated field.) |
| dynamic get readonlyDefault { |
| if (isRepeated) { |
| return _emptyList ??= FrozenPbList._([]); |
| } |
| return makeDefault!(); |
| } |
| |
| /// Returns true if the field's value is okay to transmit. |
| /// That is, it doesn't contain any required fields that aren't initialized. |
| bool _hasRequiredValues(value) { |
| if (value == null) return !isRequired; // missing is okay if optional |
| if (!_isGroupOrMessage(type)) return true; // primitive and present |
| |
| if (!isRepeated) { |
| // A required message: recurse. |
| GeneratedMessage message = value; |
| return message._fieldSet._hasRequiredValues(); |
| } |
| |
| List<GeneratedMessage> list = value; |
| if (list.isEmpty) return true; |
| |
| // For message types that (recursively) contain no required fields, |
| // short-circuit the loop. |
| if (!list[0]._fieldSet._hasRequiredFields) return true; |
| |
| // Recurse on each item in the list. |
| return list.every((GeneratedMessage m) => m._fieldSet._hasRequiredValues()); |
| } |
| |
| /// Appends the dotted path to each required field that's missing a value. |
| void _appendInvalidFields(List<String> problems, value, String prefix) { |
| if (value == null) { |
| if (isRequired) problems.add('$prefix$name'); |
| } else if (!_isGroupOrMessage(type)) { |
| // primitive and present |
| } else if (!isRepeated) { |
| // Required message/group: recurse. |
| GeneratedMessage message = value; |
| message._fieldSet._appendInvalidFields(problems, '$prefix$name.'); |
| } else { |
| final list = value as List<GeneratedMessage>; |
| if (list.isEmpty) return; |
| |
| // For message types that (recursively) contain no required fields, |
| // short-circuit the loop. |
| if (!list[0]._fieldSet._hasRequiredFields) return; |
| |
| // Recurse on each item in the list. |
| var position = 0; |
| for (var message in list) { |
| message._fieldSet |
| ._appendInvalidFields(problems, '$prefix$name[$position].'); |
| position++; |
| } |
| } |
| } |
| |
| /// Creates a repeated field to be attached to the given message. |
| /// |
| /// Delegates actual list creation to the message, so that it can |
| /// be overridden by a mixin. |
| List<T?> _createRepeatedField(GeneratedMessage m) { |
| assert(isRepeated); |
| return m.createRepeatedField<T>(tagNumber, this); |
| } |
| |
| /// Same as above, but allow a tighter typed List to be created. |
| List<S> _createRepeatedFieldWithType<S extends T>(GeneratedMessage m) { |
| assert(isRepeated); |
| return m.createRepeatedField<S>(tagNumber, this as FieldInfo<S>); |
| } |
| |
| /// Convenience method to thread this FieldInfo's reified type parameter to |
| /// _FieldSet._ensureRepeatedField. |
| List<T?> _ensureRepeatedField(BuilderInfo meta, _FieldSet fs) { |
| return fs._ensureRepeatedField<T>(meta, this); |
| } |
| |
| @override |
| String toString() => name; |
| } |
| |
| final RegExp _upperCase = RegExp('[A-Z]'); |
| |
| String _unCamelCase(String name) { |
| return name.replaceAllMapped( |
| _upperCase, (match) => '_${match.group(0)!.toLowerCase()}'); |
| } |
| |
| class MapFieldInfo<K, V> extends FieldInfo<PbMap<K, V>?> { |
| final int? keyFieldType; |
| final int? valueFieldType; |
| |
| /// Creates a new empty instance of the value type. |
| /// |
| /// `null` if the value type is not a Message type. |
| final CreateBuilderFunc? valueCreator; |
| |
| final BuilderInfo mapEntryBuilderInfo; |
| |
| MapFieldInfo( |
| String name, |
| int tagNumber, |
| int index, |
| int type, |
| this.keyFieldType, |
| this.valueFieldType, |
| this.mapEntryBuilderInfo, |
| this.valueCreator, |
| {ProtobufEnum? defaultEnumValue, |
| String? protoName}) |
| : super(name, tagNumber, index, type, |
| defaultOrMaker: () => |
| PbMap<K, V>(keyFieldType, valueFieldType, mapEntryBuilderInfo), |
| defaultEnumValue: defaultEnumValue, |
| protoName: protoName) { |
| ArgumentError.checkNotNull(name, 'name'); |
| ArgumentError.checkNotNull(tagNumber, 'tagNumber'); |
| assert(_isMapField(type)); |
| assert(!_isEnum(type) || valueOf != null); |
| } |
| |
| FieldInfo get valueFieldInfo => |
| mapEntryBuilderInfo.fieldInfo[PbMap._valueFieldNumber]!; |
| |
| Map<K, V> _ensureMapField(BuilderInfo meta, _FieldSet fs) { |
| return fs._ensureMapField<K, V>(meta, this); |
| } |
| |
| Map<K, V> _createMapField(GeneratedMessage m) { |
| assert(isMapField); |
| return m.createMapField<K, V>(tagNumber, this); |
| } |
| } |