#if !NO_RUNTIME using System; using ProtoBuf.Meta; #if FEAT_IKVM using Type = IKVM.Reflection.Type; using IKVM.Reflection; #else using System.Reflection; #endif namespace ProtoBuf.Serializers { sealed class SurrogateSerializer : IProtoTypeSerializer { bool IProtoTypeSerializer.HasCallbacks(ProtoBuf.Meta.TypeModel.CallbackType callbackType) { return false; } #if FEAT_COMPILER void IProtoTypeSerializer.EmitCallback(Compiler.CompilerContext ctx, Compiler.Local valueFrom, ProtoBuf.Meta.TypeModel.CallbackType callbackType) { } void IProtoTypeSerializer.EmitCreateInstance(Compiler.CompilerContext ctx) { throw new NotSupportedException(); } #endif bool IProtoTypeSerializer.CanCreateInstance() { return false; } #if !FEAT_IKVM object IProtoTypeSerializer.CreateInstance(ProtoReader source) { throw new NotSupportedException(); } void IProtoTypeSerializer.Callback(object value, ProtoBuf.Meta.TypeModel.CallbackType callbackType, SerializationContext context) { } #endif public bool ReturnsValue { get { return false; } } public bool RequiresOldValue { get { return true; } } public Type ExpectedType { get { return forType; } } private readonly Type forType, declaredType; private readonly MethodInfo toTail, fromTail; IProtoTypeSerializer rootTail; public SurrogateSerializer(TypeModel model, Type forType, Type declaredType, IProtoTypeSerializer rootTail) { Helpers.DebugAssert(forType != null, "forType"); Helpers.DebugAssert(declaredType != null, "declaredType"); Helpers.DebugAssert(rootTail != null, "rootTail"); Helpers.DebugAssert(rootTail.RequiresOldValue, "RequiresOldValue"); Helpers.DebugAssert(!rootTail.ReturnsValue, "ReturnsValue"); Helpers.DebugAssert(declaredType == rootTail.ExpectedType || Helpers.IsSubclassOf(declaredType, rootTail.ExpectedType)); this.forType = forType; this.declaredType = declaredType; this.rootTail = rootTail; toTail = GetConversion(model, true); fromTail = GetConversion(model, false); } private static bool HasCast(TypeModel model, Type type, Type from, Type to, out MethodInfo op) { #if WINRT System.Collections.Generic.List list = new System.Collections.Generic.List(); foreach (var item in type.GetRuntimeMethods()) { if (item.IsStatic) list.Add(item); } MethodInfo[] found = list.ToArray(); #else const BindingFlags flags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; MethodInfo[] found = type.GetMethods(flags); #endif ParameterInfo[] paramTypes; Type convertAttributeType = null; for (int i = 0; i < found.Length; i++) { MethodInfo m = found[i]; if (m.ReturnType != to) continue; paramTypes = m.GetParameters(); if(paramTypes.Length == 1 && paramTypes[0].ParameterType == from) { if (convertAttributeType == null) { convertAttributeType = model.MapType(typeof(ProtoConverterAttribute), false); if (convertAttributeType == null) { // attribute isn't defined in the source assembly: stop looking break; } } if (m.IsDefined(convertAttributeType, true)) { op = m; return true; } } } for(int i = 0 ; i < found.Length ; i++) { MethodInfo m = found[i]; if ((m.Name != "op_Implicit" && m.Name != "op_Explicit") || m.ReturnType != to) { continue; } paramTypes = m.GetParameters(); if(paramTypes.Length == 1 && paramTypes[0].ParameterType == from) { op = m; return true; } } op = null; return false; } public MethodInfo GetConversion(TypeModel model, bool toTail) { Type to = toTail ? declaredType : forType; Type from = toTail ? forType : declaredType; MethodInfo op; if (HasCast(model, declaredType, from, to, out op) || HasCast(model, forType, from, to, out op)) { return op; } throw new InvalidOperationException("No suitable conversion operator found for surrogate: " + forType.FullName + " / " + declaredType.FullName); } #if !FEAT_IKVM public void Write(object value, ProtoWriter writer) { rootTail.Write(toTail.Invoke(null, new object[] { value }), writer); } public object Read(object value, ProtoReader source) { // convert the incoming value object[] args = { value }; value = toTail.Invoke(null, args); // invoke the tail and convert the outgoing value args[0] = rootTail.Read(value, source); return fromTail.Invoke(null, args); } #endif #if FEAT_COMPILER void IProtoSerializer.EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom) { Helpers.DebugAssert(valueFrom != null); // don't support stack-head for this using (Compiler.Local converted = new Compiler.Local(ctx, declaredType)) // declare/re-use local { ctx.LoadValue(valueFrom); // load primary onto stack ctx.EmitCall(toTail); // static convert op, primary-to-surrogate ctx.StoreValue(converted); // store into surrogate local rootTail.EmitRead(ctx, converted); // downstream processing against surrogate local ctx.LoadValue(converted); // load from surrogate local ctx.EmitCall(fromTail); // static convert op, surrogate-to-primary ctx.StoreValue(valueFrom); // store back into primary } } void IProtoSerializer.EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) { ctx.LoadValue(valueFrom); ctx.EmitCall(toTail); rootTail.EmitWrite(ctx, null); } #endif } } #endif