|
|
|
@ -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."); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|