diff options
Diffstat (limited to 'csharp/ProtocolBuffers/Descriptors/FieldDescriptor.cs')
-rw-r--r-- | csharp/ProtocolBuffers/Descriptors/FieldDescriptor.cs | 386 |
1 files changed, 359 insertions, 27 deletions
diff --git a/csharp/ProtocolBuffers/Descriptors/FieldDescriptor.cs b/csharp/ProtocolBuffers/Descriptors/FieldDescriptor.cs index 0cfc723d..1750ce1d 100644 --- a/csharp/ProtocolBuffers/Descriptors/FieldDescriptor.cs +++ b/csharp/ProtocolBuffers/Descriptors/FieldDescriptor.cs @@ -5,43 +5,215 @@ using Google.ProtocolBuffers.Collections; using Google.ProtocolBuffers.DescriptorProtos; namespace Google.ProtocolBuffers.Descriptors { - public class FieldDescriptor : IndexedDescriptorBase<FieldDescriptorProto, FieldOptions> { + public class FieldDescriptor : IndexedDescriptorBase<FieldDescriptorProto, FieldOptions>, IComparable<FieldDescriptor> { - private readonly EnumDescriptor enumType; - private readonly MessageDescriptor parent; + private readonly MessageDescriptor extensionScope; + private EnumDescriptor enumType; + private MessageDescriptor messageType; + private MessageDescriptor containingType; + private object defaultValue; + private FieldType fieldType; + private MappedType mappedType; - internal FieldDescriptor(FieldDescriptorProto proto, - FileDescriptor file, - MessageDescriptor parent, - int index, - bool isExtension) : base(proto, file, index) { - enumType = null; - this.parent = parent; + internal FieldDescriptor(FieldDescriptorProto proto, FileDescriptor file, + MessageDescriptor parent, int index, bool isExtension) + : base(proto, file, ComputeFullName(file, parent, proto.Name), index) { + + if (proto.HasType) { + fieldType = GetFieldTypeFromProtoType(proto.Type); + //type = proto.Type; + } + + if (FieldNumber <= 0) { + throw new DescriptorValidationException(this, + "Field numbers must be positive integers."); + } + + if (isExtension) { + if (!proto.HasExtendee) { + throw new DescriptorValidationException(this, + "FieldDescriptorProto.Extendee not set for extension field."); + } + containingType = null; // Will be filled in when cross-linking + if (parent != null) { + extensionScope = parent; + } else { + extensionScope = null; + } + } else { + if (proto.HasExtendee) { + throw new DescriptorValidationException(this, + "FieldDescriptorProto.Extendee set for non-extension field."); + } + containingType = parent; + extensionScope = null; + } + + file.DescriptorPool.AddSymbol(this); + } + + /// <summary> + /// Maps a field type as included in the .proto file to a FieldType. + /// </summary> + private static FieldType GetFieldTypeFromProtoType(FieldDescriptorProto.Types.Type type) { + switch (type) { + case FieldDescriptorProto.Types.Type.TYPE_DOUBLE: return FieldType.Double; + case FieldDescriptorProto.Types.Type.TYPE_FLOAT: return FieldType.Float; + case FieldDescriptorProto.Types.Type.TYPE_INT64: return FieldType.Int64; + case FieldDescriptorProto.Types.Type.TYPE_UINT64: return FieldType.UInt64; + case FieldDescriptorProto.Types.Type.TYPE_INT32: return FieldType.Int32; + case FieldDescriptorProto.Types.Type.TYPE_FIXED64: return FieldType.Fixed64; + case FieldDescriptorProto.Types.Type.TYPE_FIXED32: return FieldType.Fixed32; + case FieldDescriptorProto.Types.Type.TYPE_BOOL: return FieldType.Bool; + case FieldDescriptorProto.Types.Type.TYPE_STRING: return FieldType.String; + case FieldDescriptorProto.Types.Type.TYPE_GROUP: return FieldType.Group; + case FieldDescriptorProto.Types.Type.TYPE_MESSAGE: return FieldType.Message; + case FieldDescriptorProto.Types.Type.TYPE_BYTES: return FieldType.Bytes; + case FieldDescriptorProto.Types.Type.TYPE_UINT32: return FieldType.UInt32; + case FieldDescriptorProto.Types.Type.TYPE_ENUM: return FieldType.Enum; + case FieldDescriptorProto.Types.Type.TYPE_SFIXED32: return FieldType.SFixed32; + case FieldDescriptorProto.Types.Type.TYPE_SFIXED64: return FieldType.SFixed64; + case FieldDescriptorProto.Types.Type.TYPE_SINT32: return FieldType.SInt32; + case FieldDescriptorProto.Types.Type.TYPE_SINT64: return FieldType.SInt64; + default: + throw new ArgumentException("Invalid type specified"); + } + } + + /// <summary> + /// Returns the default value for a mapped type. + /// </summary> + private static object GetDefaultValueForMappedType(MappedType type) { + switch (type) { + case MappedType.Int32: return 0; + case MappedType.Int64: return (long) 0; + case MappedType.UInt32: return (uint) 0; + case MappedType.UInt64: return (ulong) 0; + case MappedType.Single: return (float) 0; + case MappedType.Double: return (double) 0; + case MappedType.Boolean: return false; + case MappedType.String: return ""; + case MappedType.ByteString: return ByteString.Empty; + case MappedType.Message: return null; + case MappedType.Enum: return null; + default: + throw new ArgumentException("Invalid type specified"); + } } public bool IsRequired { - get; - set; + get { return Proto.Label == FieldDescriptorProto.Types.Label.LABEL_REQUIRED; } + } + + public bool IsOptional { + get { return Proto.Label == FieldDescriptorProto.Types.Label.LABEL_OPTIONAL; } } - public MappedType MappedType { get; set; } + public bool IsRepeated { + get { return Proto.Label == FieldDescriptorProto.Types.Label.LABEL_REPEATED; } + } - public bool IsRepeated { get; set; } + /// <valule> + /// Indicates whether or not the field had an explicitly-defined default value. + /// </value> + public bool HasDefaultValue { + get { return Proto.HasDefaultValue; } + } - public FieldType FieldType { get; set; } - public int FieldNumber { get; set; } + /// <value> + /// The field's default value. Valid for all types except messages + /// and groups. For all other types, the object returned is of the + /// same class that would be returned by IMessage[this]. + /// For repeated fields this will always be an empty list. For message fields it will + /// always be null. For singular values, it will depend on the descriptor. + /// </value> + public object DefaultValue { + get { + if (MappedType == MappedType.Message) { + throw new InvalidOperationException("FieldDescriptor.DefaultValue called on an embedded message field."); + } + return defaultValue; + } + } - public bool IsExtension { get; set; } + /// <value> + /// Indicates whether or not this field is an extension. + /// </value> + public bool IsExtension { + get { return Proto.HasExtendee; } + } + /* + * Get the field's containing type. For extensions, this is the type being + * extended, not the location where the extension was defined. See + * {@link #getExtensionScope()}. + */ + /// <summary> + /// Get the field's containing type. For extensions, this is the type being + /// extended, not the location where the extension was defined. See + /// <see cref="ExtensionScope" />. + /// </summary> public MessageDescriptor ContainingType { - get { return parent; } + get { return containingType; } + } + + /// <summary> + /// For extensions defined nested within message types, gets + /// the outer type. Not valid for non-extension fields. + /// </summary> + /// <example> + /// <code> + /// message Foo { + /// extensions 1000 to max; + /// } + /// extend Foo { + /// optional int32 baz = 1234; + /// } + /// message Bar { + /// extend Foo { + /// optional int32 qux = 4321; + /// } + /// } + /// </code> + /// The containing type for both <c>baz</c> and <c>qux</c> is <c>Foo</c>. + /// However, the extension scope for <c>baz</c> is <c>null</c> while + /// the extension scope for <c>qux</c> is <c>Bar</c>. + /// </example> + public MessageDescriptor ExtensionScope { + get { + if (!IsExtension) { + throw new InvalidOperationException("This field is not an extension."); + } + return extensionScope; + } } - public bool IsOptional { get; set; } + public MappedType MappedType { + get { return mappedType; } + } - public MessageDescriptor MessageType { get; set; } + public FieldType FieldType { + get { return fieldType; } + } - public MessageDescriptor ExtensionScope { get; set; } + public int FieldNumber { + get { return Proto.Number; } + } + + /// <summary> + /// Compares this descriptor with another one, ordering in "canonical" order + /// which simply means ascending order by field number. <paramref name="other"/> + /// must be a field of the same type, i.e. the <see cref="ContainingType"/> of + /// both fields must be the same. + /// </summary> + public int CompareTo(FieldDescriptor other) { + if (other.containingType != containingType) { + throw new ArgumentException("FieldDescriptors can only be compared to other FieldDescriptors " + + "for fields of the same message type."); + } + return FieldNumber - other.FieldNumber; + } + /// <summary> /// For enum fields, returns the field's type. @@ -53,15 +225,18 @@ namespace Google.ProtocolBuffers.Descriptors { } return enumType; } - } + } /// <summary> - /// The default value for this field. For repeated fields - /// this will always be an empty list. For message fields it will - /// always be null. For singular values, it will depend on the descriptor. + /// For embedded message and group fields, returns the field's type. /// </summary> - public object DefaultValue { - get { throw new NotImplementedException(); } + public MessageDescriptor MessageType { + get { + if (MappedType != MappedType.Message) { + throw new InvalidOperationException("MessageType is only valid for enum fields."); + } + return messageType; + } } /// <summary> @@ -70,6 +245,7 @@ namespace Google.ProtocolBuffers.Descriptors { /// </summary> public static readonly IDictionary<FieldType, MappedType> FieldTypeToWireFormatMap = MapFieldTypes(); + private static IDictionary<FieldType, MappedType> MapFieldTypes() { var map = new Dictionary<FieldType, MappedType>(); foreach (FieldInfo field in typeof(FieldType).GetFields(BindingFlags.Static | BindingFlags.Public)) { @@ -79,5 +255,161 @@ namespace Google.ProtocolBuffers.Descriptors { } return Dictionaries.AsReadOnly(map); } + + /// <summary> + /// Look up and cross-link all field types etc. + /// </summary> + internal void CrossLink() { + if (Proto.HasExtendee) { + IDescriptor extendee = File.DescriptorPool.LookupSymbol(Proto.Extendee, this); + if (!(extendee is MessageDescriptor)) { + throw new DescriptorValidationException(this, "\"" + Proto.Extendee + "\" is not a message type."); + } + containingType = (MessageDescriptor) extendee; + + if (!containingType.IsExtensionNumber(FieldNumber)) { + throw new DescriptorValidationException(this, + "\"" + containingType.FullName + "\" does not declare " + FieldNumber + " as an extension number."); + } + } + + if (Proto.HasTypeName) { + IDescriptor typeDescriptor = + File.DescriptorPool.LookupSymbol(Proto.TypeName, this); + + if (!Proto.HasType) { + // Choose field type based on symbol. + if (typeDescriptor is MessageDescriptor) { + fieldType = FieldType.Message; + mappedType = MappedType.Message; + } else if (typeDescriptor is EnumDescriptor) { + fieldType = FieldType.Enum; + mappedType = MappedType.Enum; + } else { + throw new DescriptorValidationException(this, "\"" + Proto.TypeName + "\" is not a type."); + } + } + + if (MappedType == MappedType.Message) { + if (!(typeDescriptor is MessageDescriptor)) { + throw new DescriptorValidationException(this, "\"" + Proto.TypeName + "\" is not a message type."); + } + messageType = (MessageDescriptor) typeDescriptor; + + if (Proto.HasDefaultValue) { + throw new DescriptorValidationException(this, "Messages can't have default values."); + } + } else if (MappedType == Descriptors.MappedType.Enum) { + if (!(typeDescriptor is EnumDescriptor)) { + throw new DescriptorValidationException(this, "\"" + Proto.TypeName + "\" is not an enum type."); + } + enumType = (EnumDescriptor)typeDescriptor; + } else { + throw new DescriptorValidationException(this, "Field with primitive type has type_name."); + } + } else { + if (MappedType == MappedType.Message || MappedType == MappedType.Enum) { + throw new DescriptorValidationException(this, "Field with message or enum type missing type_name."); + } + } + + // We don't attempt to parse the default value until here because for + // enums we need the enum type's descriptor. + if (Proto.HasDefaultValue) { + if (IsRepeated) { + throw new DescriptorValidationException(this, "Repeated fields cannot have default values."); + } + + try { + // TODO(jonskeet): Check signage for Int32 and Int64. + switch (FieldType) { + case FieldType.Int32: + case FieldType.SInt32: + case FieldType.SFixed32: + defaultValue = TextFormat.ParseInt32(Proto.DefaultValue); + break; + case FieldType.UInt32: + case FieldType.Fixed32: + defaultValue = TextFormat.ParseUInt32(Proto.DefaultValue); + break; + case FieldType.Int64: + case FieldType.SInt64: + case FieldType.SFixed64: + defaultValue = TextFormat.ParseInt64(Proto.DefaultValue); + break; + case FieldType.UInt64: + case FieldType.Fixed64: + defaultValue = TextFormat.ParseUInt64(Proto.DefaultValue); + break; + case FieldType.Float: + defaultValue = float.Parse(Proto.DefaultValue); + break; + case FieldType.Double: + defaultValue = double.Parse(Proto.DefaultValue); + break; + case FieldType.Bool: + defaultValue = bool.Parse(Proto.DefaultValue); // TODO(jonskeet): Check this will work + break; + case FieldType.String: + defaultValue = Proto.DefaultValue; + break; + case FieldType.Bytes: + try { + defaultValue = TextFormat.UnescapeBytes(Proto.DefaultValue); + } catch (FormatException e) { + throw new DescriptorValidationException(this, "Couldn't parse default value: " + e.Message); + } + break; + case FieldType.Enum: + defaultValue = enumType.FindValueByName(Proto.DefaultValue); + if (defaultValue == null) { + throw new DescriptorValidationException(this, "Unknown enum default value: \"" + Proto.DefaultValue + "\""); + } + break; + case FieldType.Message: + case FieldType.Group: + throw new DescriptorValidationException(this, "Message type had default value."); + } + } catch (FormatException e) { + DescriptorValidationException validationException = + new DescriptorValidationException(this, "Could not parse default value: \"" + Proto.DefaultValue + "\"", e); + throw validationException; + } + } else { + // Determine the default default for this field. + if (IsRepeated) { + // FIXME(jonskeet): Find out the correct list type and use that instead. + defaultValue = new List<object>(); + } else { + switch (MappedType) { + case MappedType.Enum: + // We guarantee elsewhere that an enum type always has at least + // one possible value. + defaultValue = enumType.Values[0]; + break; + case MappedType.Message: + defaultValue = null; + break; + default: + defaultValue = GetDefaultValueForMappedType(MappedType); + break; + } + } + } + + if (!IsExtension) { + File.DescriptorPool.AddFieldByNumber(this); + } + + if (containingType != null && containingType.Options.MessageSetWireFormat) { + if (IsExtension) { + if (!IsOptional || FieldType != FieldType.Message) { + throw new DescriptorValidationException(this, "Extensions of MessageSets must be optional messages."); + } + } else { + throw new DescriptorValidationException(this, "MessageSets cannot have fields, only extensions."); + } + } + } } } |