Files
2026-08-27 10:56:38 -06:00

270 lines
8.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Reflection;
using ProtoBuf.Compiler;
using ProtoBuf.Meta;
namespace ProtoBuf.Serializers;
internal class MapDecorator<TDictionary, TKey, TValue> : ProtoDecoratorBase where TDictionary : class, IDictionary<TKey, TValue>
{
private readonly Type concreteType;
private readonly IProtoSerializer keyTail;
private readonly int fieldNumber;
private readonly WireType wireType;
private static readonly MethodInfo indexerSet = GetIndexerSetter();
private static readonly TKey DefaultKey = (((object)typeof(TKey) == typeof(string)) ? ((TKey)(object)"") : default(TKey));
private static readonly TValue DefaultValue = (((object)typeof(TValue) == typeof(string)) ? ((TValue)(object)"") : default(TValue));
public override Type ExpectedType => typeof(TDictionary);
public override bool ReturnsValue => true;
public override bool RequiresOldValue => AppendToCollection;
private bool AppendToCollection { get; }
internal MapDecorator(TypeModel model, Type concreteType, IProtoSerializer keyTail, IProtoSerializer valueTail, int fieldNumber, WireType wireType, WireType keyWireType, WireType valueWireType, bool overwriteList)
: base((DefaultValue == null) ? ((ProtoDecoratorBase)new TagDecorator(2, valueWireType, strict: false, valueTail)) : ((ProtoDecoratorBase)new DefaultValueDecorator(model, DefaultValue, new TagDecorator(2, valueWireType, strict: false, valueTail))))
{
this.wireType = wireType;
this.keyTail = new DefaultValueDecorator(model, DefaultKey, new TagDecorator(1, keyWireType, strict: false, keyTail));
this.fieldNumber = fieldNumber;
this.concreteType = concreteType ?? typeof(TDictionary);
if (keyTail.RequiresOldValue)
{
throw new InvalidOperationException("Key tail should not require the old value");
}
if (!keyTail.ReturnsValue)
{
throw new InvalidOperationException("Key tail should return a value");
}
if (!valueTail.ReturnsValue)
{
throw new InvalidOperationException("Value tail should return a value");
}
AppendToCollection = !overwriteList;
}
private static MethodInfo GetIndexerSetter()
{
PropertyInfo[] properties = typeof(TDictionary).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (PropertyInfo propertyInfo in properties)
{
if (propertyInfo.Name != "Item" || (object)propertyInfo.PropertyType != typeof(TValue))
{
continue;
}
ParameterInfo[] indexParameters = propertyInfo.GetIndexParameters();
if (indexParameters != null && indexParameters.Length == 1 && (object)indexParameters[0].ParameterType == typeof(TKey))
{
MethodInfo setMethod = propertyInfo.GetSetMethod(nonPublic: true);
if ((object)setMethod != null)
{
return setMethod;
}
}
}
throw new InvalidOperationException("Unable to resolve indexer for map");
}
public override object Read(object untyped, ProtoReader source)
{
TDictionary val = (AppendToCollection ? ((TDictionary)untyped) : null);
if (val == null)
{
val = (TDictionary)Activator.CreateInstance(concreteType);
}
do
{
TKey key = DefaultKey;
TValue val2 = DefaultValue;
SubItemToken token = ProtoReader.StartSubItem(source);
int num;
while ((num = source.ReadFieldHeader()) > 0)
{
switch (num)
{
case 1:
key = (TKey)keyTail.Read(null, source);
break;
case 2:
val2 = (TValue)Tail.Read(Tail.RequiresOldValue ? ((object)val2) : null, source);
break;
default:
source.SkipField();
break;
}
}
ProtoReader.EndSubItem(token, source);
val[key] = val2;
}
while (source.TryReadFieldHeader(fieldNumber));
return val;
}
public override void Write(object untyped, ProtoWriter dest)
{
foreach (KeyValuePair<TKey, TValue> item in (TDictionary)untyped)
{
ProtoWriter.WriteFieldHeader(fieldNumber, wireType, dest);
SubItemToken token = ProtoWriter.StartSubItem(null, dest);
if (item.Key != null)
{
keyTail.Write(item.Key, dest);
}
if (item.Value != null)
{
Tail.Write(item.Value, dest);
}
ProtoWriter.EndSubItem(token, dest);
}
}
protected override void EmitWrite(CompilerContext ctx, Local valueFrom)
{
Type typeFromHandle = typeof(KeyValuePair<TKey, TValue>);
MethodInfo moveNext;
MethodInfo current;
MethodInfo enumeratorInfo = ListDecorator.GetEnumeratorInfo(ctx.Model, ExpectedType, typeFromHandle, out moveNext, out current);
Type returnType = enumeratorInfo.ReturnType;
MethodInfo getMethod = typeFromHandle.GetProperty("Key").GetGetMethod();
MethodInfo getMethod2 = typeFromHandle.GetProperty("Value").GetGetMethod();
using Local local = ctx.GetLocalWithValue(ExpectedType, valueFrom);
using Local local2 = new Local(ctx, returnType);
using Local local3 = new Local(ctx, typeof(SubItemToken));
using Local local4 = new Local(ctx, typeFromHandle);
ctx.LoadAddress(local, ExpectedType);
ctx.EmitCall(enumeratorInfo, ExpectedType);
ctx.StoreValue(local2);
using (ctx.Using(local2))
{
CodeLabel label = ctx.DefineLabel();
CodeLabel label2 = ctx.DefineLabel();
ctx.Branch(label2, @short: false);
ctx.MarkLabel(label);
ctx.LoadAddress(local2, returnType);
ctx.EmitCall(current, returnType);
if ((object)typeFromHandle != ctx.MapType(typeof(object)) && (object)current.ReturnType == ctx.MapType(typeof(object)))
{
ctx.CastFromObject(typeFromHandle);
}
ctx.StoreValue(local4);
ctx.LoadValue(fieldNumber);
ctx.LoadValue((int)wireType);
ctx.LoadReaderWriter();
ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("WriteFieldHeader"));
ctx.LoadNullRef();
ctx.LoadReaderWriter();
ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("StartSubItem"));
ctx.StoreValue(local3);
ctx.LoadAddress(local4, typeFromHandle);
ctx.EmitCall(getMethod, typeFromHandle);
ctx.WriteNullCheckedTail(typeof(TKey), keyTail, null);
ctx.LoadAddress(local4, typeFromHandle);
ctx.EmitCall(getMethod2, typeFromHandle);
ctx.WriteNullCheckedTail(typeof(TValue), Tail, null);
ctx.LoadValue(local3);
ctx.LoadReaderWriter();
ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("EndSubItem"));
ctx.MarkLabel(label2);
ctx.LoadAddress(local2, returnType);
ctx.EmitCall(moveNext, returnType);
ctx.BranchIfTrue(label, @short: false);
}
}
protected override void EmitRead(CompilerContext ctx, Local valueFrom)
{
using Local local = (AppendToCollection ? ctx.GetLocalWithValue(ExpectedType, valueFrom) : new Local(ctx, typeof(TDictionary)));
using Local local2 = new Local(ctx, typeof(SubItemToken));
using Local local3 = new Local(ctx, typeof(TKey));
using Local local4 = new Local(ctx, typeof(TValue));
using Local local5 = new Local(ctx, ctx.MapType(typeof(int)));
if (!AppendToCollection)
{
ctx.LoadNullRef();
ctx.StoreValue(local);
}
if ((object)concreteType != null)
{
ctx.LoadValue(local);
CodeLabel label = ctx.DefineLabel();
ctx.BranchIfTrue(label, @short: true);
ctx.EmitCtor(concreteType);
ctx.StoreValue(local);
ctx.MarkLabel(label);
}
CodeLabel label2 = ctx.DefineLabel();
ctx.MarkLabel(label2);
if ((object)typeof(TKey) == typeof(string))
{
ctx.LoadValue("");
ctx.StoreValue(local3);
}
else
{
ctx.InitLocal(typeof(TKey), local3);
}
if ((object)typeof(TValue) == typeof(string))
{
ctx.LoadValue("");
ctx.StoreValue(local4);
}
else
{
ctx.InitLocal(typeof(TValue), local4);
}
ctx.LoadReaderWriter();
ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("StartSubItem"));
ctx.StoreValue(local2);
CodeLabel label3 = ctx.DefineLabel();
CodeLabel label4 = ctx.DefineLabel();
ctx.Branch(label3, @short: false);
ctx.MarkLabel(label4);
ctx.LoadValue(local5);
CodeLabel codeLabel = ctx.DefineLabel();
CodeLabel codeLabel2 = ctx.DefineLabel();
CodeLabel codeLabel3 = ctx.DefineLabel();
ctx.Switch(new CodeLabel[3] { codeLabel, codeLabel2, codeLabel3 });
ctx.MarkLabel(codeLabel);
ctx.LoadReaderWriter();
ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("SkipField"));
ctx.Branch(label3, @short: false);
ctx.MarkLabel(codeLabel2);
keyTail.EmitRead(ctx, null);
ctx.StoreValue(local3);
ctx.Branch(label3, @short: false);
ctx.MarkLabel(codeLabel3);
Tail.EmitRead(ctx, Tail.RequiresOldValue ? local4 : null);
ctx.StoreValue(local4);
ctx.MarkLabel(label3);
ctx.EmitBasicRead("ReadFieldHeader", ctx.MapType(typeof(int)));
ctx.CopyValue();
ctx.StoreValue(local5);
ctx.LoadValue(0);
ctx.BranchIfGreater(label4, @short: false);
ctx.LoadValue(local2);
ctx.LoadReaderWriter();
ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("EndSubItem"));
ctx.LoadAddress(local, ExpectedType);
ctx.LoadValue(local3);
ctx.LoadValue(local4);
ctx.EmitCall(indexerSet);
ctx.LoadReaderWriter();
ctx.LoadValue(fieldNumber);
ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("TryReadFieldHeader"));
ctx.BranchIfTrue(label2, @short: false);
if (ReturnsValue)
{
ctx.LoadValue(local);
}
}
}