From b84310e1101af3c442e102f4c9ed03f3eff105fb Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Thu, 14 Aug 2008 20:35:20 +0100 Subject: [PATCH] Field accessor implementations complete (hopefully) --- .../DescriptorProtos/DescriptorProtoFile.cs | 4 +- .../Descriptors/EnumDescriptor.cs | 2 - .../ProtocolBuffers/FieldAccess/Delegates.cs | 3 +- .../FieldAccess/FieldAccessorTable.cs | 52 ++++++++- .../FieldAccess/RepeatedEnumAccessor.cs | 45 ++++++++ .../FieldAccess/RepeatedMessageAccessor.cs | 58 ++++++++++ .../FieldAccess/RepeatedPrimitiveAccessor.cs | 102 ++++++++++++++++++ .../FieldAccess/SingleEnumAccessor.cs | 39 +++++++ .../FieldAccess/SingleMessageAccessor.cs | 52 +++++++++ .../FieldAccess/SinglePrimitiveAccessor.cs | 79 ++++++++++++++ csharp/ProtocolBuffers/IMessage.cs | 5 +- csharp/ProtocolBuffers/ProtocolBuffers.csproj | 6 ++ 12 files changed, 437 insertions(+), 10 deletions(-) create mode 100644 csharp/ProtocolBuffers/FieldAccess/RepeatedEnumAccessor.cs create mode 100644 csharp/ProtocolBuffers/FieldAccess/RepeatedMessageAccessor.cs create mode 100644 csharp/ProtocolBuffers/FieldAccess/RepeatedPrimitiveAccessor.cs create mode 100644 csharp/ProtocolBuffers/FieldAccess/SingleEnumAccessor.cs create mode 100644 csharp/ProtocolBuffers/FieldAccess/SingleMessageAccessor.cs create mode 100644 csharp/ProtocolBuffers/FieldAccess/SinglePrimitiveAccessor.cs diff --git a/csharp/ProtocolBuffers/DescriptorProtos/DescriptorProtoFile.cs b/csharp/ProtocolBuffers/DescriptorProtos/DescriptorProtoFile.cs index becb23cc13..4b92791aba 100644 --- a/csharp/ProtocolBuffers/DescriptorProtos/DescriptorProtoFile.cs +++ b/csharp/ProtocolBuffers/DescriptorProtos/DescriptorProtoFile.cs @@ -84,7 +84,7 @@ namespace Google.ProtocolBuffers.DescriptorProtos { 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x0b, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x42, 0x59, 0x54, 0x45, 0x53, 0x10, 0x0c, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x10, 0x0d, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x10, 0x0e, 0x12, 0x11, 0x0a, 0x0d, - 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x46, 0x49, 0x58, 0x45, 0x44, 0x33, 0x32, 0x10, 0x0f, 0x12, 0x11, 0x0a, 0x0d, 0x54, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x46, 0x49, 0x58, 0x45, 0x44, 0x33, 0x32, 0x10, 0x14, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x46, 0x49, 0x58, 0x45, 0x44, 0x36, 0x34, 0x10, 0x10, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x10, 0x11, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x10, 0x12, 0x22, 0x43, 0x0a, 0x05, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x12, 0x0a, @@ -1908,7 +1908,7 @@ namespace Google.ProtocolBuffers.DescriptorProtos { [pbd::EnumDescriptorIndex(13)] TYPE_ENUM = 14, [pbd::EnumDescriptorIndex(14)] - TYPE_SFIXED32 = 15, + TYPE_SFIXED32 = 20, [pbd::EnumDescriptorIndex(15)] TYPE_SFIXED64 = 16, [pbd::EnumDescriptorIndex(16)] diff --git a/csharp/ProtocolBuffers/Descriptors/EnumDescriptor.cs b/csharp/ProtocolBuffers/Descriptors/EnumDescriptor.cs index 3dccc2871d..838075826b 100644 --- a/csharp/ProtocolBuffers/Descriptors/EnumDescriptor.cs +++ b/csharp/ProtocolBuffers/Descriptors/EnumDescriptor.cs @@ -43,8 +43,6 @@ namespace Google.ProtocolBuffers.Descriptors { /// Finds an enum value by number. If multiple enum values have the /// same number, this returns the first defined value with that number. /// - /// - /// internal EnumValueDescriptor FindValueByNumber(int number) { return File.DescriptorPool.FindEnumValueByNumber(this, number); } diff --git a/csharp/ProtocolBuffers/FieldAccess/Delegates.cs b/csharp/ProtocolBuffers/FieldAccess/Delegates.cs index 3dc0445de7..000ae2502e 100644 --- a/csharp/ProtocolBuffers/FieldAccess/Delegates.cs +++ b/csharp/ProtocolBuffers/FieldAccess/Delegates.cs @@ -7,6 +7,5 @@ namespace Google.ProtocolBuffers.FieldAccess { /// Declarations of delegate types used for field access. Can't /// use Func and Action (other than one parameter) as we can't guarantee .NET 3.5. /// - delegate bool HasFunction(TMessage message); - + internal delegate bool HasFunction(TMessage message); } diff --git a/csharp/ProtocolBuffers/FieldAccess/FieldAccessorTable.cs b/csharp/ProtocolBuffers/FieldAccess/FieldAccessorTable.cs index 7fec437ff4..231b974cc2 100644 --- a/csharp/ProtocolBuffers/FieldAccess/FieldAccessorTable.cs +++ b/csharp/ProtocolBuffers/FieldAccess/FieldAccessorTable.cs @@ -2,20 +2,68 @@ using Google.ProtocolBuffers.Descriptors; namespace Google.ProtocolBuffers.FieldAccess { + /// + /// Provides access to fields in generated messages via reflection. + /// This type is public to allow it to be used by generated messages, which + /// create appropriate instances in the .proto file description class. + /// TODO(jonskeet): See if we can hide it somewhere... + /// public class FieldAccessorTable { + readonly IFieldAccessor[] accessors; + readonly MessageDescriptor descriptor; public MessageDescriptor Descriptor { get { return descriptor; } } - public FieldAccessorTable(MessageDescriptor descriptor, String[] pascalCaseFieldNames, Type messageType, Type builderType) { + /// + /// Constructs a FieldAccessorTable for a particular message class. + /// Only one FieldAccessorTable should be constructed per class. + /// + /// The type's descriptor + /// The Pascal-case names of all the field-based properties in the message. + /// The .NET type representing the message + /// The .NET type representing the message's builder type + public FieldAccessorTable(MessageDescriptor descriptor, String[] propertyNames, Type messageType, Type builderType) { this.descriptor = descriptor; + accessors = new IFieldAccessor[descriptor.Fields.Count]; + for (int i=0; i < accessors.Length; i++) { + accessors[i] = CreateAccessor(descriptor.Fields[i], propertyNames[i], messageType, builderType); + } + } + + /// + /// Creates an accessor for a single field + /// + private static IFieldAccessor CreateAccessor(FieldDescriptor field, string name, Type messageType, Type builderType) { + if (field.IsRepeated) { + switch (field.MappedType) { + case MappedType.Message: return new RepeatedMessageAccessor(name, messageType, builderType); + case MappedType.Enum: return new RepeatedEnumAccessor(field, name, messageType, builderType); + default: return new RepeatedPrimitiveAccessor(name, messageType, builderType); + } + } else { + switch (field.MappedType) { + case MappedType.Message: return new SingleMessageAccessor(name, messageType, builderType); + case MappedType.Enum: return new SingleEnumAccessor(field, name, messageType, builderType); + default: return new SinglePrimitiveAccessor(name, messageType, builderType); + } + } } internal IFieldAccessor this[FieldDescriptor field] { - get { return null; } + get { + if (field.ContainingType != descriptor) { + throw new ArgumentException("FieldDescriptor does not match message type."); + } else if (field.IsExtension) { + // If this type had extensions, it would subclass ExtendableMessage, + // which overrides the reflection interface to handle extensions. + throw new ArgumentException("This type does not have extensions."); + } + return accessors[field.Index]; + } } } } diff --git a/csharp/ProtocolBuffers/FieldAccess/RepeatedEnumAccessor.cs b/csharp/ProtocolBuffers/FieldAccess/RepeatedEnumAccessor.cs new file mode 100644 index 0000000000..487241924a --- /dev/null +++ b/csharp/ProtocolBuffers/FieldAccess/RepeatedEnumAccessor.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Google.ProtocolBuffers.Collections; +using Google.ProtocolBuffers.Descriptors; + +namespace Google.ProtocolBuffers.FieldAccess { + + /// + /// Accessor for a repeated enum field. + /// + internal sealed class RepeatedEnumAccessor : RepeatedPrimitiveAccessor { + + private readonly EnumDescriptor enumDescriptor; + + internal RepeatedEnumAccessor(FieldDescriptor field, string name, Type messageType, Type builderType) + : base(name, messageType, builderType) { + + enumDescriptor = field.EnumType; + } + + public override object GetValue(IMessage message) { + List ret = new List(); + foreach (int rawValue in (IEnumerable) base.GetValue(message)) { + ret.Add(enumDescriptor.FindValueByNumber(rawValue)); + } + return Lists.AsReadOnly(ret); + } + + public override object GetRepeatedValue(IMessage message, int index) { + // Note: This relies on the fact that the CLR allows unboxing from an enum to + // its underlying value + int rawValue = (int) base.GetRepeatedValue(message, index); + return enumDescriptor.FindValueByNumber(rawValue); + } + + public override void AddRepeated(IBuilder builder, object value) { + base.AddRepeated(builder, ((EnumValueDescriptor) value).Number); + } + + public override void SetRepeated(IBuilder builder, int index, object value) { + base.SetRepeated(builder, index, ((EnumValueDescriptor) value).Number); + } + } +} diff --git a/csharp/ProtocolBuffers/FieldAccess/RepeatedMessageAccessor.cs b/csharp/ProtocolBuffers/FieldAccess/RepeatedMessageAccessor.cs new file mode 100644 index 0000000000..6f40938eea --- /dev/null +++ b/csharp/ProtocolBuffers/FieldAccess/RepeatedMessageAccessor.cs @@ -0,0 +1,58 @@ +using System; +using System.Reflection; +using Google.ProtocolBuffers.Descriptors; + +namespace Google.ProtocolBuffers.FieldAccess { + + /// + /// Accessor for a repeated message field. + /// + /// TODO(jonskeet): Try to extract the commonality between this and SingleMessageAccessor. + /// We almost want multiple inheritance... + /// + internal sealed class RepeatedMessageAccessor : RepeatedPrimitiveAccessor { + + /// + /// The static method to create a builder for the property type. For example, + /// in a message type "Foo", a field called "bar" might be of type "Baz". This + /// method is Baz.CreateBuilder. + /// + private readonly MethodInfo createBuilderMethod; + + internal RepeatedMessageAccessor(string name, Type messageType, Type builderType) + : base(name, messageType, builderType) { + createBuilderMethod = ClrType.GetMethod("CreateBuilder", BindingFlags.Public | BindingFlags.Static); + if (createBuilderMethod == null) { + throw new ArgumentException("No public static CreateBuilder method declared in " + ClrType.Name); + } + } + + /// + /// Creates a message of the appropriate CLR type from the given value, + /// which may already be of the right type or may be a dynamic message. + /// + private object CoerceType(object value) { + + // If it's already of the right type, we're done + if (ClrType.IsInstanceOfType(value)) { + return value; + } + + // No... so let's create a builder of the right type, and merge the value in. + IMessage message = (IMessage) value; + return CreateBuilder().MergeFrom(message).Build(); + } + + public override void SetRepeated(IBuilder builder, int index, object value) { + base.SetRepeated(builder, index, CoerceType(value)); + } + + public override IBuilder CreateBuilder() { + return (IBuilder) createBuilderMethod.Invoke(null, null); + } + + public override void AddRepeated(IBuilder builder, object value) { + base.AddRepeated(builder, CoerceType(value)); + } + } +} diff --git a/csharp/ProtocolBuffers/FieldAccess/RepeatedPrimitiveAccessor.cs b/csharp/ProtocolBuffers/FieldAccess/RepeatedPrimitiveAccessor.cs new file mode 100644 index 0000000000..232a1e28dc --- /dev/null +++ b/csharp/ProtocolBuffers/FieldAccess/RepeatedPrimitiveAccessor.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections; +using System.Reflection; +using Google.ProtocolBuffers.Descriptors; + +namespace Google.ProtocolBuffers.FieldAccess { + /// + /// Accesor for a repeated field of type int, ByteString etc. + /// + internal class RepeatedPrimitiveAccessor : IFieldAccessor { + + private readonly PropertyInfo messageProperty; + private readonly PropertyInfo builderProperty; + private readonly PropertyInfo hasProperty; + private readonly PropertyInfo countProperty; + private readonly MethodInfo clearMethod; + private readonly MethodInfo addMethod; + private readonly MethodInfo getElementMethod; + private readonly MethodInfo setElementMethod; + + /// + /// The CLR type of the field (int, the enum type, ByteString, the message etc). + /// This is taken from the return type of the method used to retrieve a single + /// value. + /// + protected Type ClrType { + get { return getElementMethod.ReturnType; } + } + + internal RepeatedPrimitiveAccessor(string name, Type messageType, Type builderType) { + messageProperty = messageType.GetProperty(name + "List"); + builderProperty = builderType.GetProperty(name + "List"); + hasProperty = messageType.GetProperty("Has" + name); + countProperty = messageType.GetProperty(name + "Count"); + clearMethod = builderType.GetMethod("Clear" + name); + addMethod = builderType.GetMethod("Add" + name); + getElementMethod = messageType.GetMethod("Get" + name, new Type[] { typeof(int) }); + setElementMethod = builderType.GetMethod("Set" + name, new Type[] { typeof(int) }); + if (messageProperty == null + || builderProperty == null + || hasProperty == null + || countProperty == null + || clearMethod == null + || addMethod == null + || getElementMethod == null + || setElementMethod == null) { + throw new ArgumentException("Not all required properties/methods available"); + } + } + + public bool Has(IMessage message) { + throw new InvalidOperationException(); + } + + public virtual IBuilder CreateBuilder() { + throw new InvalidOperationException(); + } + + public virtual object GetValue(IMessage message) { + return messageProperty.GetValue(message, null); + } + + public void SetValue(IBuilder builder, object value) { + // Add all the elements individually. This serves two purposes: + // 1) Verifies that each element has the correct type. + // 2) Insures that the caller cannot modify the list later on and + // have the modifications be reflected in the message. + Clear(builder); + foreach (object element in (IEnumerable) value) { + AddRepeated(builder, element); + } + } + + public void Clear(IBuilder builder) { + clearMethod.Invoke(builder, null); + } + + public int GetRepeatedCount(IMessage message) { + return (int) countProperty.GetValue(null, null); + } + + public virtual object GetRepeatedValue(IMessage message, int index) { + return getElementMethod.Invoke(message, new object[] {index } ); + } + + public virtual void SetRepeated(IBuilder builder, int index, object value) { + setElementMethod.Invoke(builder, new object[] {index, value} ); + } + + public virtual void AddRepeated(IBuilder builder, object value) { + addMethod.Invoke(builder, new object[] { value }); + } + + /// + /// The builder class's accessor already builds a read-only wrapper for + /// us, which is exactly what we want. + /// + public object GetRepeatedWrapper(IBuilder builder) { + return builderProperty.GetValue(builder, null); + } + } +} \ No newline at end of file diff --git a/csharp/ProtocolBuffers/FieldAccess/SingleEnumAccessor.cs b/csharp/ProtocolBuffers/FieldAccess/SingleEnumAccessor.cs new file mode 100644 index 0000000000..0e9e68f8d7 --- /dev/null +++ b/csharp/ProtocolBuffers/FieldAccess/SingleEnumAccessor.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using Google.ProtocolBuffers.Descriptors; + +namespace Google.ProtocolBuffers.FieldAccess { + /// + /// Accessor for fields representing a non-repeated enum value. + /// + internal sealed class SingleEnumAccessor : SinglePrimitiveAccessor { + + private readonly EnumDescriptor enumDescriptor; + + internal SingleEnumAccessor(FieldDescriptor field, string name, Type messageType, Type builderType) + : base(name, messageType, builderType) { + enumDescriptor = field.EnumType; + } + + /// + /// Returns an EnumValueDescriptor representing the value in the builder. + /// Note that if an enum has multiple values for the same number, the descriptor + /// for the first value with that number will be returned. + /// + public override object GetValue(IMessage message) { + // Note: This relies on the fact that the CLR allows unboxing from an enum to + // its underlying value + int rawValue = (int) base.GetValue(message); + return enumDescriptor.FindValueByNumber(rawValue); + } + + /// + /// Sets the value as an enum (via an int) in the builder, + /// from an EnumValueDescriptor parameter. + /// + public override void SetValue(IBuilder builder, object value) { + EnumValueDescriptor valueDescriptor = (EnumValueDescriptor) value; + base.SetValue(builder, valueDescriptor.Number); + } + } +} \ No newline at end of file diff --git a/csharp/ProtocolBuffers/FieldAccess/SingleMessageAccessor.cs b/csharp/ProtocolBuffers/FieldAccess/SingleMessageAccessor.cs new file mode 100644 index 0000000000..c9647b0722 --- /dev/null +++ b/csharp/ProtocolBuffers/FieldAccess/SingleMessageAccessor.cs @@ -0,0 +1,52 @@ +using System; +using System.Reflection; +using Google.ProtocolBuffers.Descriptors; + +namespace Google.ProtocolBuffers.FieldAccess { + /// + /// Accessor for fields representing a non-repeated message value. + /// + internal sealed class SingleMessageAccessor : SinglePrimitiveAccessor { + + /// + /// The static method to create a builder for the property type. For example, + /// in a message type "Foo", a field called "bar" might be of type "Baz". This + /// method is Baz.CreateBuilder. + /// + private readonly MethodInfo createBuilderMethod; + + + internal SingleMessageAccessor(string name, Type messageType, Type builderType) + : base(name, messageType, builderType) { + + createBuilderMethod = ClrType.GetMethod("CreateBuilder", BindingFlags.Public | BindingFlags.Static); + if (createBuilderMethod == null) { + throw new ArgumentException("No public static CreateBuilder method declared in " + ClrType.Name); + } + } + + /// + /// Creates a message of the appropriate CLR type from the given value, + /// which may already be of the right type or may be a dynamic message. + /// + private object CoerceType(object value) { + + // If it's already of the right type, we're done + if (ClrType.IsInstanceOfType(value)) { + return value; + } + + // No... so let's create a builder of the right type, and merge the value in. + IMessage message = (IMessage) value; + return CreateBuilder().MergeFrom(message).Build(); + } + + public override void SetValue(IBuilder builder, object value) { + base.SetValue(builder, CoerceType(value)); + } + + public override IBuilder CreateBuilder() { + return (IBuilder) createBuilderMethod.Invoke(null, null); + } + } +} \ No newline at end of file diff --git a/csharp/ProtocolBuffers/FieldAccess/SinglePrimitiveAccessor.cs b/csharp/ProtocolBuffers/FieldAccess/SinglePrimitiveAccessor.cs new file mode 100644 index 0000000000..4878e79b13 --- /dev/null +++ b/csharp/ProtocolBuffers/FieldAccess/SinglePrimitiveAccessor.cs @@ -0,0 +1,79 @@ +using System; +using System.Reflection; +using Google.ProtocolBuffers.Descriptors; + +namespace Google.ProtocolBuffers.FieldAccess { + /// + /// Access for a non-repeated field of a "primitive" type (i.e. not another message or an enum). + /// + internal class SinglePrimitiveAccessor : IFieldAccessor { + + private readonly PropertyInfo messageProperty; + private readonly PropertyInfo builderProperty; + private readonly PropertyInfo hasProperty; + private readonly MethodInfo clearMethod; + + /// + /// The CLR type of the field (int, the enum type, ByteString, the message etc). + /// As declared by the property. + /// + protected Type ClrType { + get { return messageProperty.PropertyType; } + } + + internal SinglePrimitiveAccessor(string name, Type messageType, Type builderType) { + messageProperty = messageType.GetProperty(name); + builderProperty = builderType.GetProperty(name); + hasProperty = messageType.GetProperty("Has" + name); + clearMethod = builderType.GetMethod("Clear" + name); + if (messageProperty == null || builderProperty == null || hasProperty == null || clearMethod == null) { + throw new ArgumentException("Not all required properties/methods available"); + } + } + + public bool Has(IMessage message) { + return (bool) hasProperty.GetValue(message, null); + } + + public void Clear(IBuilder builder) { + clearMethod.Invoke(builder, null); + } + + /// + /// Only valid for message types - this implementation throws InvalidOperationException. + /// + public virtual IBuilder CreateBuilder() { + throw new InvalidOperationException(); + } + + public virtual object GetValue(IMessage message) { + return messageProperty.GetValue(message, null); + } + + public virtual void SetValue(IBuilder builder, object value) { + builderProperty.SetValue(builder, value, null); + } + + #region Methods only related to repeated values + public int GetRepeatedCount(IMessage message) { + throw new InvalidOperationException(); + } + + public object GetRepeatedValue(IMessage message, int index) { + throw new InvalidOperationException(); + } + + public void SetRepeated(IBuilder builder, int index, object value) { + throw new InvalidOperationException(); + } + + public void AddRepeated(IBuilder builder, object value) { + throw new InvalidOperationException(); + } + + public object GetRepeatedWrapper(IBuilder builder) { + throw new InvalidOperationException(); + } + #endregion + } +} \ No newline at end of file diff --git a/csharp/ProtocolBuffers/IMessage.cs b/csharp/ProtocolBuffers/IMessage.cs index da9aa9f326..abb29f72ab 100644 --- a/csharp/ProtocolBuffers/IMessage.cs +++ b/csharp/ProtocolBuffers/IMessage.cs @@ -57,8 +57,9 @@ namespace Google.ProtocolBuffers { /// /// Obtains the value of the given field, or the default value if - /// it isn't set. For value type fields including enums, the boxed - /// value is returned. For embedded message fields, the sub-message + /// it isn't set. For value type fields, the boxed value is returned. + /// For enum fields, the EnumValueDescriptor for the enum is returned. + /// For embedded message fields, the sub-message /// is returned. For repeated fields, an IList<T> is returned. /// object this[FieldDescriptor field] { get; } diff --git a/csharp/ProtocolBuffers/ProtocolBuffers.csproj b/csharp/ProtocolBuffers/ProtocolBuffers.csproj index f0fb7ab318..706d2eaf90 100644 --- a/csharp/ProtocolBuffers/ProtocolBuffers.csproj +++ b/csharp/ProtocolBuffers/ProtocolBuffers.csproj @@ -69,9 +69,15 @@ + + + + + +