Field accessor implementations complete (hopefully)

This commit is contained in:
Jon Skeet 2008-08-14 20:35:20 +01:00
parent 023d7398d6
commit b84310e110
12 changed files with 437 additions and 10 deletions

View File

@ -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)]

View File

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

View File

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

View File

@ -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];
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
}
}

View File

@ -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; }

View File

@ -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" />