Bladeren bron

Field accessor implementations complete (hopefully)

Jon Skeet 17 jaren geleden
bovenliggende
commit
b84310e110

+ 2 - 2
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)]

+ 0 - 2
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.
     /// </summary>
-    /// <param name="number"></param>
-    /// <returns></returns>
     internal EnumValueDescriptor FindValueByNumber(int number) {
       return File.DescriptorPool.FindEnumValueByNumber(this, number);
     }

+ 1 - 2
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.
   /// </summary>
-  delegate bool HasFunction<TMessage>(TMessage message);
-  
+  internal delegate bool HasFunction<TMessage>(TMessage message);  
 }

+ 50 - 2
csharp/ProtocolBuffers/FieldAccess/FieldAccessorTable.cs

@@ -2,20 +2,68 @@
 using Google.ProtocolBuffers.Descriptors;
 
 namespace Google.ProtocolBuffers.FieldAccess {
+  /// <summary>
+  /// 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...
+  /// </summary>
   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) {
+   /// <summary>
+    /// Constructs a FieldAccessorTable for a particular message class.
+    /// Only one FieldAccessorTable should be constructed per class.
+    /// </summary>
+    /// <param name="descriptor">The type's descriptor</param>
+    /// <param name="propertyNames">The Pascal-case names of all the field-based properties in the message.</param>
+    /// <param name="messageType">The .NET type representing the message</param>
+    /// <param name="builderType">The .NET type representing the message's builder type</param>
+    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);
+      }
+    }
+
+     /// <summary>
+     /// Creates an accessor for a single field
+     /// </summary>   
+    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];
+      }
     }
   }
 }

+ 45 - 0
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 {
+
+  /// <summary>
+  /// Accessor for a repeated enum field.
+  /// </summary>
+  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<EnumValueDescriptor> ret = new List<EnumValueDescriptor>();
+      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);
+    }
+  }
+}

+ 58 - 0
csharp/ProtocolBuffers/FieldAccess/RepeatedMessageAccessor.cs

@@ -0,0 +1,58 @@
+using System;
+using System.Reflection;
+using Google.ProtocolBuffers.Descriptors;
+
+namespace Google.ProtocolBuffers.FieldAccess {
+
+  /// <summary>
+  /// Accessor for a repeated message field.
+  /// 
+  /// TODO(jonskeet): Try to extract the commonality between this and SingleMessageAccessor.
+  /// We almost want multiple inheritance...
+  /// </summary>
+  internal sealed class RepeatedMessageAccessor : RepeatedPrimitiveAccessor {
+
+    /// <summary>
+    /// 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.
+    /// </summary>
+    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);
+      }
+    }
+
+    /// <summary>
+    /// 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.
+    /// </summary>
+    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));
+    }
+  }
+}

+ 102 - 0
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 {
+  /// <summary>
+  /// Accesor for a repeated field of type int, ByteString etc.
+  /// </summary>
+  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;
+
+    /// <summary>
+    /// 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.
+    /// </summary>
+    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 });
+    }
+
+    /// <summary>
+    /// The builder class's accessor already builds a read-only wrapper for
+    /// us, which is exactly what we want.
+    /// </summary>
+    public object GetRepeatedWrapper(IBuilder builder) {
+      return builderProperty.GetValue(builder, null);
+    }
+  }
+}

+ 39 - 0
csharp/ProtocolBuffers/FieldAccess/SingleEnumAccessor.cs

@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using Google.ProtocolBuffers.Descriptors;
+
+namespace Google.ProtocolBuffers.FieldAccess {
+  /// <summary>
+  /// Accessor for fields representing a non-repeated enum value.
+  /// </summary>
+  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;
+    }
+
+    /// <summary>
+    /// 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.
+    /// </summary>
+    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);
+    }
+
+    /// <summary>
+    /// Sets the value as an enum (via an int) in the builder,
+    /// from an EnumValueDescriptor parameter.
+    /// </summary>
+    public override void SetValue(IBuilder builder, object value) {
+      EnumValueDescriptor valueDescriptor = (EnumValueDescriptor) value;
+      base.SetValue(builder, valueDescriptor.Number);
+    }
+  }
+}

+ 52 - 0
csharp/ProtocolBuffers/FieldAccess/SingleMessageAccessor.cs

@@ -0,0 +1,52 @@
+using System;
+using System.Reflection;
+using Google.ProtocolBuffers.Descriptors;
+
+namespace Google.ProtocolBuffers.FieldAccess {
+  /// <summary>
+  /// Accessor for fields representing a non-repeated message value.
+  /// </summary>
+  internal sealed class SingleMessageAccessor : SinglePrimitiveAccessor {
+
+    /// <summary>
+    /// 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.
+    /// </summary>
+    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);
+      }
+    }
+
+    /// <summary>
+    /// 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.
+    /// </summary>
+    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);
+    }
+  }
+}

+ 79 - 0
csharp/ProtocolBuffers/FieldAccess/SinglePrimitiveAccessor.cs

@@ -0,0 +1,79 @@
+using System;
+using System.Reflection;
+using Google.ProtocolBuffers.Descriptors;
+
+namespace Google.ProtocolBuffers.FieldAccess {
+  /// <summary>
+  /// Access for a non-repeated field of a "primitive" type (i.e. not another message or an enum).
+  /// </summary>
+  internal class SinglePrimitiveAccessor : IFieldAccessor {
+
+    private readonly PropertyInfo messageProperty;
+    private readonly PropertyInfo builderProperty;
+    private readonly PropertyInfo hasProperty;
+    private readonly MethodInfo clearMethod;
+
+    /// <summary>
+    /// The CLR type of the field (int, the enum type, ByteString, the message etc).
+    /// As declared by the property.
+    /// </summary>
+    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);
+    }
+
+    /// <summary>
+    /// Only valid for message types - this implementation throws InvalidOperationException.
+    /// </summary>
+    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
+  }
+}

+ 3 - 2
csharp/ProtocolBuffers/IMessage.cs

@@ -57,8 +57,9 @@ namespace Google.ProtocolBuffers {
 
     /// <summary>
     /// 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&lt;T&gt; is returned.
     /// </summary>
     object this[FieldDescriptor field] { get; }

+ 6 - 0
csharp/ProtocolBuffers/ProtocolBuffers.csproj

@@ -69,9 +69,15 @@
     <Compile Include="Descriptors\ServiceDescriptor.cs" />
     <Compile Include="ExtensionInfo.cs" />
     <Compile Include="ExtensionRegistry.cs" />
+    <Compile Include="FieldAccess\SingleEnumAccessor.cs" />
+    <Compile Include="FieldAccess\SingleMessageAccessor.cs" />
+    <Compile Include="FieldAccess\SinglePrimitiveAccessor.cs" />
+    <Compile Include="FieldAccess\RepeatedPrimitiveAccessor.cs" />
+    <Compile Include="FieldAccess\RepeatedEnumAccessor.cs" />
     <Compile Include="FieldAccess\Delegates.cs" />
     <Compile Include="FieldAccess\IFieldAccessor.cs" />
     <Compile Include="FieldAccess\FieldAccessorTable.cs" />
+    <Compile Include="FieldAccess\RepeatedMessageAccessor.cs" />
     <Compile Include="FieldSet.cs" />
     <Compile Include="GeneratedBuilder.cs" />
     <Compile Include="GeneratedExtension.cs" />