using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Google.Protobuf;
using Google.Protobuf.Descriptors;
using Google.Protobuf.FieldAccess;
namespace Google.Protobuf.FieldAccess
{
/// <summary>
/// Accessor for single fields.
/// </summary>
/// <typeparam name="T">The type of message containing the field.</typeparam>
internal sealed class SingleFieldAccessor<T> : FieldAccessorBase<T> where T : IMessage<T>
{
// All the work here is actually done in the constructor - it creates the appropriate delegates.
// There are various cases to consider, based on the property type (message, string/bytes, or "genuine" primitive)
// and proto2 vs proto3 for non-message types, as proto3 doesn't support "full" presence detection or default
// values.
private readonly Action<T, object> setValueDelegate;
private readonly Action<T> clearDelegate;
private readonly Func<T, bool> hasValueDelegate;
internal SingleFieldAccessor(FieldDescriptor descriptor, string name, bool supportsFieldPresence) : base(name)
{
PropertyInfo property = typeof(T).GetProperty(name);
// We know there *is* such a property, or the base class constructor would have thrown. We should be able to write
// to it though.
if (!property.CanWrite)
{
throw new ArgumentException("Not all required properties/methods available");
}
setValueDelegate = ReflectionUtil.CreateDowncastDelegate<T>(property.GetSetMethod());
var clrType = property.PropertyType;
if (typeof(IMessage).IsAssignableFrom(clrType))
{
// Message types are simple - we only need to detect nullity.
clearDelegate = message => SetValue(message, null);
hasValueDelegate = message => GetValue(message) == null;
}
if (supportsFieldPresence)
{
// Proto2: we expect a HasFoo property and a ClearFoo method.
// For strings and byte arrays, setting the property to null would have the equivalent effect,
// but we generate the method for consistency, which makes this simpler.
PropertyInfo hasProperty = typeof(T).GetProperty("Has" + name);
MethodInfo clearMethod = typeof(T).GetMethod("Clear" + name);
if (hasProperty == null || clearMethod == null || !hasProperty.CanRead)
{
throw new ArgumentException("Not all required properties/methods available");
}
hasValueDelegate = ReflectionUtil.CreateDelegateFunc<T, bool>(hasProperty.GetGetMethod());
clearDelegate = ReflectionUtil.CreateDelegateAction<T>(clearMethod);
}
else
{
/*
// TODO(jonskeet): Reimplement. We need a better way of working out default values.
// Proto3: for field detection, we just need the default value of the field (0, "", byte[0] etc)
// To clear a field, we set the value to that default.
object defaultValue = descriptor.DefaultValue;
hasValueDelegate = message => GetValue(message).Equals(defaultValue);
clearDelegate = message => SetValue(message, defaultValue);
*/
}
}
public override bool HasValue(T message)
{
return hasValueDelegate(message);
}
public override void Clear(T message)
{
clearDelegate(message);
}
public override void SetValue(T message, object value)
{
setValueDelegate(message, value);
}
}
}