|  | // Copyright (c) 2013, 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 '../protoc.dart'; | 
|  |  | 
|  | class ProtobufField { | 
|  | static final RegExp _hexLiteralRegex = | 
|  | RegExp(r'^0x[0-9a-f]+$', multiLine: false, caseSensitive: false); | 
|  | static final RegExp _integerLiteralRegex = RegExp(r'^[+-]?[0-9]+$'); | 
|  | static final RegExp _decimalLiteralRegexA = RegExp( | 
|  | r'^[+-]?([0-9]*)\.[0-9]+(e[+-]?[0-9]+)?$', | 
|  | multiLine: false, | 
|  | caseSensitive: false); | 
|  | static final RegExp _decimalLiteralRegexB = RegExp( | 
|  | r'^[+-]?[0-9]+e[+-]?[0-9]+$', | 
|  | multiLine: false, | 
|  | caseSensitive: false); | 
|  |  | 
|  | final FieldDescriptorProto descriptor; | 
|  |  | 
|  | /// Dart names within a [GeneratedMessage] or `null` for an extension. | 
|  | final FieldNames? memberNames; | 
|  |  | 
|  | final String fullName; | 
|  | final BaseType baseType; | 
|  | final ProtobufContainer parent; | 
|  |  | 
|  | ProtobufField.message( | 
|  | FieldNames names, ProtobufContainer parent, GenerationContext ctx) | 
|  | : this._(names.descriptor, names, parent, ctx); | 
|  |  | 
|  | ProtobufField.extension(FieldDescriptorProto descriptor, | 
|  | ProtobufContainer parent, GenerationContext ctx) | 
|  | : this._(descriptor, null, parent, ctx); | 
|  |  | 
|  | ProtobufField._(this.descriptor, FieldNames? dartNames, this.parent, | 
|  | GenerationContext ctx) | 
|  | : memberNames = dartNames, | 
|  | fullName = '${parent.fullName}.${descriptor.name}', | 
|  | baseType = BaseType(descriptor, ctx); | 
|  |  | 
|  | /// The index of this field in MessageGenerator.fieldList. | 
|  | /// | 
|  | /// `null` for an extension. | 
|  | int? get index => memberNames?.index; | 
|  |  | 
|  | String? get quotedProtoName => | 
|  | (_unCamelCase(descriptor.jsonName) == descriptor.name) | 
|  | ? null | 
|  | : "'${descriptor.name}'"; | 
|  |  | 
|  | /// The position of this field as it appeared in the original | 
|  | /// DescriptorProto. | 
|  | /// | 
|  | /// `null` for an extension. | 
|  | int? get sourcePosition => memberNames?.sourcePosition; | 
|  |  | 
|  | /// Whether the field is to be encoded with [deprecated = true] encoding. | 
|  | bool get isDeprecated => descriptor.options.deprecated; | 
|  |  | 
|  | bool get isRequired => | 
|  | descriptor.label == FieldDescriptorProto_Label.LABEL_REQUIRED; | 
|  |  | 
|  | bool get isRepeated => | 
|  | descriptor.label == FieldDescriptorProto_Label.LABEL_REPEATED; | 
|  |  | 
|  | /// Whether a numeric field is repeated and must be encoded with packed | 
|  | /// encoding. | 
|  | /// | 
|  | /// In proto3 repeated fields are encoded as packed by default. proto2 | 
|  | /// requires `[packed=true]` option. | 
|  | bool get isPacked { | 
|  | if (!isRepeated) { | 
|  | return false; // only repeated fields can be packed | 
|  | } | 
|  |  | 
|  | if (!baseType.isPackable) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | switch (parent.fileGen!.syntax) { | 
|  | case ProtoSyntax.proto3: | 
|  | if (!descriptor.hasOptions()) { | 
|  | return true; // packed by default in proto3 | 
|  | } else { | 
|  | return !descriptor.options.hasPacked() || descriptor.options.packed; | 
|  | } | 
|  | case ProtoSyntax.proto2: | 
|  | if (!descriptor.hasOptions()) { | 
|  | return false; // not packed by default in proto3 | 
|  | } else { | 
|  | return descriptor.options.packed; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Whether the field has the `overrideGetter` annotation set to true. | 
|  | bool get overridesGetter => _hasBooleanOption(Dart_options.overrideGetter); | 
|  |  | 
|  | /// Whether the field has the `overrideSetter` annotation set to true. | 
|  | bool get overridesSetter => _hasBooleanOption(Dart_options.overrideSetter); | 
|  |  | 
|  | /// Whether the field has the `overrideHasMethod` annotation set to true. | 
|  | bool get overridesHasMethod => | 
|  | _hasBooleanOption(Dart_options.overrideHasMethod); | 
|  |  | 
|  | /// Whether the field has the `overrideClearMethod` annotation set to true. | 
|  | bool get overridesClearMethod => | 
|  | _hasBooleanOption(Dart_options.overrideClearMethod); | 
|  |  | 
|  | /// Whether this field uses the Int64 from the fixnum package. | 
|  | bool get needsFixnumImport => | 
|  | baseType.unprefixed == '$fixnumImportPrefix.Int64'; | 
|  |  | 
|  | /// Whether this field is a map field definition: | 
|  | /// `map<key_type, value_type> map_field = N`. | 
|  | bool get isMapField { | 
|  | if (!isRepeated || !baseType.isMessage) return false; | 
|  | final generator = baseType.generator as MessageGenerator; | 
|  | return generator._descriptor.options.mapEntry; | 
|  | } | 
|  |  | 
|  | /// Whether this field should have a `hazzer` generated. | 
|  | bool get hasPresence { | 
|  | // NB. Map fields are represented as repeated message fields | 
|  | if (isRepeated) return false; | 
|  | return true; | 
|  | // TODO(sigurdm): to provide the correct semantics for non-optional proto3 | 
|  | // fields would need something like the following: | 
|  | // return baseType.isMessage || | 
|  | //   descriptor.proto3Optional || | 
|  | //   parent.fileGen.descriptor.syntax == "proto2"; | 
|  | // | 
|  | // This change would break any accidental uses of the proto3 hazzers, and | 
|  | // would require some clean-up. | 
|  | // | 
|  | // We could consider keeping hazzers for proto3-oneof fields. There they | 
|  | // seem useful and not breaking proto3 semantics, and dart protobuf uses it | 
|  | // for example in package:protobuf/src/protobuf/mixins/well_known.dart. | 
|  | } | 
|  |  | 
|  | /// Returns the type to use for the Dart field type. | 
|  | String getDartType() { | 
|  | if (isMapField) { | 
|  | final keyType = getDartMapKeyType(); | 
|  | final valueType = getDartMapValueType(); | 
|  | return '$protobufImportPrefix.PbMap<$keyType, $valueType>'; | 
|  | } | 
|  | if (isRepeated) return baseType.getRepeatedDartType(parent.fileGen!); | 
|  | return baseType.getDartType(parent.fileGen!); | 
|  | } | 
|  |  | 
|  | /// Only for map fields: returns the type to use for Dart map field key type. | 
|  | String getDartMapKeyType() { | 
|  | assert(isMapField); | 
|  | return (baseType.generator as MessageGenerator) | 
|  | ._fieldList[0] | 
|  | .baseType | 
|  | .getDartType(parent.fileGen!); | 
|  | } | 
|  |  | 
|  | /// Only for map fields: returns the type to use for Dart map field value | 
|  | /// type. | 
|  | String getDartMapValueType() { | 
|  | assert(isMapField); | 
|  | return (baseType.generator as MessageGenerator) | 
|  | ._fieldList[1] | 
|  | .baseType | 
|  | .getDartType(parent.fileGen!); | 
|  | } | 
|  |  | 
|  | /// Returns the tag number of the underlying proto field. | 
|  | int get number => descriptor.number; | 
|  |  | 
|  | /// Returns the constant in PbFieldType corresponding to this type. | 
|  | String get typeConstant { | 
|  | var prefix = 'O'; | 
|  | if (isRequired) { | 
|  | prefix = 'Q'; | 
|  | } else if (isPacked) { | 
|  | prefix = 'K'; | 
|  | } else if (isRepeated) { | 
|  | prefix = 'P'; | 
|  | } | 
|  | return '$protobufImportPrefix.PbFieldType.$prefix${baseType.typeConstantSuffix}'; | 
|  | } | 
|  |  | 
|  | static String _formatArguments( | 
|  | List<String> positionals, Map<String, String?> named) { | 
|  | final args = positionals.toList(); | 
|  | named.forEach((key, value) { | 
|  | if (value != null) { | 
|  | args.add('$key: $value'); | 
|  | } | 
|  | }); | 
|  | return args.join(', '); | 
|  | } | 
|  |  | 
|  | /// Returns Dart code adding this field to a BuilderInfo object. | 
|  | /// The call will start with ".." and a method name. | 
|  | void generateBuilderInfoCall(IndentingWriter out, String package) { | 
|  | assert(descriptor.hasJsonName()); | 
|  |  | 
|  | final omitFieldNames = ConditionalConstDefinition('omit_field_names'); | 
|  | out.addSuffix( | 
|  | omitFieldNames.constFieldName, omitFieldNames.constDefinition); | 
|  | final quotedName = omitFieldNames.createTernary(descriptor.jsonName); | 
|  |  | 
|  | final type = baseType.getDartType(parent.fileGen!); | 
|  |  | 
|  | String invocation; | 
|  |  | 
|  | final args = <String>[]; | 
|  | final named = <String, String?>{'protoName': quotedProtoName}; | 
|  | args.add('$number'); | 
|  | args.add(quotedName); | 
|  |  | 
|  | if (isMapField) { | 
|  | final generator = baseType.generator as MessageGenerator; | 
|  | final key = generator._fieldList[0]; | 
|  | final value = generator._fieldList[1]; | 
|  |  | 
|  | // Key type is an integer type or string. No need to specify the default | 
|  | // value as the library knows the defaults for integer and string fields. | 
|  | final keyType = key.baseType.getDartType(parent.fileGen!); | 
|  |  | 
|  | // Value type can be anything other than another map. | 
|  | final valueType = value.baseType.getDartType(parent.fileGen!); | 
|  |  | 
|  | invocation = 'm<$keyType, $valueType>'; | 
|  |  | 
|  | named['entryClassName'] = "'${generator.messageName}'"; | 
|  | named['keyFieldType'] = key.typeConstant; | 
|  | named['valueFieldType'] = value.typeConstant; | 
|  | if (value.baseType.isMessage || value.baseType.isGroup) { | 
|  | named['valueCreator'] = '$valueType.create'; | 
|  | named['valueDefaultOrMaker'] = value.generateDefaultFunction(); | 
|  | } | 
|  | if (value.baseType.isEnum) { | 
|  | named['valueOf'] = '$valueType.valueOf'; | 
|  | named['enumValues'] = '$valueType.values'; | 
|  | named['valueDefaultOrMaker'] = value.generateDefaultFunction(); | 
|  | named['defaultEnumValue'] = value.generateDefaultFunction(); | 
|  | } | 
|  | if (package != '') { | 
|  | named['packageName'] = | 
|  | 'const $protobufImportPrefix.PackageName(\'$package\')'; | 
|  | } | 
|  | } else if (isRepeated) { | 
|  | if (typeConstant == '$protobufImportPrefix.PbFieldType.PS') { | 
|  | invocation = 'pPS'; | 
|  | } else { | 
|  | args.add(typeConstant); | 
|  | if (baseType.isMessage || baseType.isGroup || baseType.isEnum) { | 
|  | invocation = 'pc<$type>'; | 
|  | } else { | 
|  | invocation = 'p<$type>'; | 
|  | } | 
|  |  | 
|  | if (baseType.isMessage || baseType.isGroup) { | 
|  | named['subBuilder'] = '$type.create'; | 
|  | } else if (baseType.isEnum) { | 
|  | named['valueOf'] = '$type.valueOf'; | 
|  | named['enumValues'] = '$type.values'; | 
|  | named['defaultEnumValue'] = generateDefaultFunction(); | 
|  | } | 
|  | } | 
|  | } else { | 
|  | // Singular field. | 
|  | final makeDefault = generateDefaultFunction(); | 
|  |  | 
|  | if (baseType.isEnum) { | 
|  | args.add(typeConstant); | 
|  | named['defaultOrMaker'] = makeDefault; | 
|  | named['valueOf'] = '$type.valueOf'; | 
|  | named['enumValues'] = '$type.values'; | 
|  | invocation = 'e<$type>'; | 
|  | } else if (makeDefault == null) { | 
|  | switch (type) { | 
|  | case '$coreImportPrefix.String': | 
|  | if (typeConstant == '$protobufImportPrefix.PbFieldType.OS') { | 
|  | invocation = 'aOS'; | 
|  | } else if (typeConstant == '$protobufImportPrefix.PbFieldType.QS') { | 
|  | invocation = 'aQS'; | 
|  | } else { | 
|  | invocation = 'a<$type>'; | 
|  | args.add(typeConstant); | 
|  | } | 
|  | break; | 
|  | case '$coreImportPrefix.bool': | 
|  | if (typeConstant == '$protobufImportPrefix.PbFieldType.OB') { | 
|  | invocation = 'aOB'; | 
|  | } else { | 
|  | invocation = 'a<$type>'; | 
|  | args.add(typeConstant); | 
|  | } | 
|  | break; | 
|  | default: | 
|  | invocation = 'a<$type>'; | 
|  | args.add(typeConstant); | 
|  | break; | 
|  | } | 
|  | } else { | 
|  | if (makeDefault == '$fixnumImportPrefix.Int64.ZERO' && | 
|  | type == '$fixnumImportPrefix.Int64' && | 
|  | typeConstant == '$protobufImportPrefix.PbFieldType.O6') { | 
|  | invocation = 'aInt64'; | 
|  | } else { | 
|  | if (baseType.isMessage || baseType.isGroup) { | 
|  | named['subBuilder'] = '$type.create'; | 
|  | } | 
|  | if (baseType.isMessage) { | 
|  | invocation = isRequired ? 'aQM<$type>' : 'aOM<$type>'; | 
|  | } else { | 
|  | invocation = 'a<$type>'; | 
|  | named['defaultOrMaker'] = makeDefault; | 
|  | args.add(typeConstant); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | final result = '..$invocation(${_formatArguments(args, named)})'; | 
|  | out.println(result); | 
|  | } | 
|  |  | 
|  | /// Returns a Dart expression that evaluates to this field's default value. | 
|  | /// | 
|  | /// Returns "null" if unavailable, in which case FieldSet._getDefault() | 
|  | /// should be called instead. | 
|  | String getDefaultExpr() { | 
|  | if (isRepeated) return 'null'; | 
|  | switch (descriptor.type) { | 
|  | case FieldDescriptorProto_Type.TYPE_BOOL: | 
|  | return _getDefaultAsBoolExpr('false')!; | 
|  | case FieldDescriptorProto_Type.TYPE_INT32: | 
|  | case FieldDescriptorProto_Type.TYPE_UINT32: | 
|  | case FieldDescriptorProto_Type.TYPE_SINT32: | 
|  | case FieldDescriptorProto_Type.TYPE_FIXED32: | 
|  | case FieldDescriptorProto_Type.TYPE_SFIXED32: | 
|  | return _getDefaultAsInt32Expr('0')!; | 
|  | case FieldDescriptorProto_Type.TYPE_STRING: | 
|  | return _getDefaultAsStringExpr("''")!; | 
|  | default: | 
|  | return 'null'; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Returns a function expression that returns the field's default value. | 
|  | String? generateDefaultFunction() { | 
|  | assert(!isRepeated); | 
|  | switch (descriptor.type) { | 
|  | case FieldDescriptorProto_Type.TYPE_BOOL: | 
|  | return _getDefaultAsBoolExpr(null); | 
|  | case FieldDescriptorProto_Type.TYPE_FLOAT: | 
|  | case FieldDescriptorProto_Type.TYPE_DOUBLE: | 
|  | if (!descriptor.hasDefaultValue()) { | 
|  | return null; | 
|  | } else if ('0.0' == descriptor.defaultValue || | 
|  | '0' == descriptor.defaultValue) { | 
|  | return null; | 
|  | } else if (descriptor.defaultValue == 'inf') { | 
|  | return '$coreImportPrefix.double.infinity'; | 
|  | } else if (descriptor.defaultValue == '-inf') { | 
|  | return '$coreImportPrefix.double.negativeInfinity'; | 
|  | } else if (descriptor.defaultValue == 'nan') { | 
|  | return '$coreImportPrefix.double.nan'; | 
|  | } else if (_hexLiteralRegex.hasMatch(descriptor.defaultValue)) { | 
|  | return '(${descriptor.defaultValue}).toDouble()'; | 
|  | } else if (_integerLiteralRegex.hasMatch(descriptor.defaultValue)) { | 
|  | return '${descriptor.defaultValue}.0'; | 
|  | } else if (_decimalLiteralRegexA.hasMatch(descriptor.defaultValue) || | 
|  | _decimalLiteralRegexB.hasMatch(descriptor.defaultValue)) { | 
|  | return descriptor.defaultValue; | 
|  | } | 
|  | throw _invalidDefaultValue; | 
|  | case FieldDescriptorProto_Type.TYPE_INT32: | 
|  | case FieldDescriptorProto_Type.TYPE_UINT32: | 
|  | case FieldDescriptorProto_Type.TYPE_SINT32: | 
|  | case FieldDescriptorProto_Type.TYPE_FIXED32: | 
|  | case FieldDescriptorProto_Type.TYPE_SFIXED32: | 
|  | return _getDefaultAsInt32Expr(null); | 
|  | case FieldDescriptorProto_Type.TYPE_INT64: | 
|  | case FieldDescriptorProto_Type.TYPE_UINT64: | 
|  | case FieldDescriptorProto_Type.TYPE_SINT64: | 
|  | case FieldDescriptorProto_Type.TYPE_FIXED64: | 
|  | case FieldDescriptorProto_Type.TYPE_SFIXED64: | 
|  | var value = '0'; | 
|  | if (descriptor.hasDefaultValue()) value = descriptor.defaultValue; | 
|  | if (value == '0') return '$fixnumImportPrefix.Int64.ZERO'; | 
|  | return "$protobufImportPrefix.parseLongInt('$value')"; | 
|  | case FieldDescriptorProto_Type.TYPE_STRING: | 
|  | return _getDefaultAsStringExpr(null); | 
|  | case FieldDescriptorProto_Type.TYPE_BYTES: | 
|  | if (!descriptor.hasDefaultValue() || descriptor.defaultValue.isEmpty) { | 
|  | return null; | 
|  | } | 
|  | final byteList = descriptor.defaultValue.codeUnits | 
|  | .map((b) => '0x${b.toRadixString(16)}') | 
|  | .join(','); | 
|  | return '() => <$coreImportPrefix.int>[$byteList]'; | 
|  | case FieldDescriptorProto_Type.TYPE_GROUP: | 
|  | case FieldDescriptorProto_Type.TYPE_MESSAGE: | 
|  | return '${baseType.getDartType(parent.fileGen!)}.getDefault'; | 
|  | case FieldDescriptorProto_Type.TYPE_ENUM: | 
|  | final className = baseType.getDartType(parent.fileGen!); | 
|  | final gen = baseType.generator as EnumGenerator; | 
|  | if (descriptor.hasDefaultValue() && | 
|  | descriptor.defaultValue.isNotEmpty) { | 
|  | return '$className.${descriptor.defaultValue}'; | 
|  | } else if (gen._canonicalValues.isNotEmpty) { | 
|  | return '$className.${gen.dartNames[gen._canonicalValues[0].name]}'; | 
|  | } | 
|  | return null; | 
|  | default: | 
|  | throw _typeNotImplemented('generatedDefaultFunction'); | 
|  | } | 
|  | } | 
|  |  | 
|  | String? _getDefaultAsBoolExpr(String? noDefault) { | 
|  | if (descriptor.hasDefaultValue() && 'false' != descriptor.defaultValue) { | 
|  | return descriptor.defaultValue; | 
|  | } | 
|  | return noDefault; | 
|  | } | 
|  |  | 
|  | String? _getDefaultAsStringExpr(String? noDefault) { | 
|  | if (!descriptor.hasDefaultValue() || descriptor.defaultValue.isEmpty) { | 
|  | return noDefault; | 
|  | } | 
|  |  | 
|  | return quoted(descriptor.defaultValue); | 
|  | } | 
|  |  | 
|  | String? _getDefaultAsInt32Expr(String? noDefault) { | 
|  | if (descriptor.hasDefaultValue() && '0' != descriptor.defaultValue) { | 
|  | return descriptor.defaultValue; | 
|  | } | 
|  | return noDefault; | 
|  | } | 
|  |  | 
|  | bool _hasBooleanOption(Extension extension) => | 
|  | descriptor.options.getExtension(extension) as bool? ?? false; | 
|  |  | 
|  | String get _invalidDefaultValue => 'dart-protoc-plugin:' | 
|  | ' invalid default value (${descriptor.defaultValue})' | 
|  | ' found in field $fullName'; | 
|  |  | 
|  | String _typeNotImplemented(String methodName) => 'dart-protoc-plugin:' | 
|  | ' $methodName not implemented for type (${descriptor.type})' | 
|  | ' found in field $fullName'; | 
|  |  | 
|  | static final RegExp _upperCase = RegExp('[A-Z]'); | 
|  |  | 
|  | static String _unCamelCase(String name) { | 
|  | return name.replaceAllMapped( | 
|  | _upperCase, (match) => '_${match.group(0)!.toLowerCase()}'); | 
|  | } | 
|  | } |