167 lines
5.9 KiB
C#
167 lines
5.9 KiB
C#
|
#if !NO_RUNTIME
|
|||
|
using System;
|
|||
|
using System.Reflection;
|
|||
|
|
|||
|
using ProtoBuf.Meta;
|
|||
|
|
|||
|
namespace ProtoBuf.Serializers
|
|||
|
{
|
|||
|
sealed class NullDecorator : ProtoDecoratorBase
|
|||
|
{
|
|||
|
private readonly Type expectedType;
|
|||
|
public const int Tag = 1;
|
|||
|
public NullDecorator(TypeModel model, IProtoSerializer tail) : base(tail)
|
|||
|
{
|
|||
|
if (!tail.ReturnsValue)
|
|||
|
throw new NotSupportedException("NullDecorator only supports implementations that return values");
|
|||
|
|
|||
|
Type tailType = tail.ExpectedType;
|
|||
|
if (Helpers.IsValueType(tailType))
|
|||
|
{
|
|||
|
expectedType = model.MapType(typeof(Nullable<>)).MakeGenericType(tailType);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
expectedType = tailType;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public override Type ExpectedType => expectedType;
|
|||
|
|
|||
|
public override bool ReturnsValue => true;
|
|||
|
|
|||
|
public override bool RequiresOldValue => true;
|
|||
|
|
|||
|
#if FEAT_COMPILER
|
|||
|
protected override void EmitRead(Compiler.CompilerContext ctx, Compiler.Local valueFrom)
|
|||
|
{
|
|||
|
using (Compiler.Local oldValue = ctx.GetLocalWithValue(expectedType, valueFrom))
|
|||
|
using (Compiler.Local token = new Compiler.Local(ctx, ctx.MapType(typeof(SubItemToken))))
|
|||
|
using (Compiler.Local field = new Compiler.Local(ctx, ctx.MapType(typeof(int))))
|
|||
|
{
|
|||
|
ctx.LoadReaderWriter();
|
|||
|
ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("StartSubItem"));
|
|||
|
ctx.StoreValue(token);
|
|||
|
|
|||
|
Compiler.CodeLabel next = ctx.DefineLabel(), processField = ctx.DefineLabel(), end = ctx.DefineLabel();
|
|||
|
|
|||
|
ctx.MarkLabel(next);
|
|||
|
|
|||
|
ctx.EmitBasicRead("ReadFieldHeader", ctx.MapType(typeof(int)));
|
|||
|
ctx.CopyValue();
|
|||
|
ctx.StoreValue(field);
|
|||
|
ctx.LoadValue(Tag); // = 1 - process
|
|||
|
ctx.BranchIfEqual(processField, true);
|
|||
|
ctx.LoadValue(field);
|
|||
|
ctx.LoadValue(1); // < 1 - exit
|
|||
|
ctx.BranchIfLess(end, false);
|
|||
|
|
|||
|
// default: skip
|
|||
|
ctx.LoadReaderWriter();
|
|||
|
ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("SkipField"));
|
|||
|
ctx.Branch(next, true);
|
|||
|
|
|||
|
// process
|
|||
|
ctx.MarkLabel(processField);
|
|||
|
if (Tail.RequiresOldValue)
|
|||
|
{
|
|||
|
if (Helpers.IsValueType(expectedType))
|
|||
|
{
|
|||
|
ctx.LoadAddress(oldValue, expectedType);
|
|||
|
ctx.EmitCall(expectedType.GetMethod("GetValueOrDefault", Helpers.EmptyTypes));
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
ctx.LoadValue(oldValue);
|
|||
|
}
|
|||
|
}
|
|||
|
Tail.EmitRead(ctx, null);
|
|||
|
// note we demanded always returns a value
|
|||
|
if (Helpers.IsValueType(expectedType))
|
|||
|
{
|
|||
|
ctx.EmitCtor(expectedType, Tail.ExpectedType); // re-nullable<T> it
|
|||
|
}
|
|||
|
ctx.StoreValue(oldValue);
|
|||
|
ctx.Branch(next, false);
|
|||
|
|
|||
|
// outro
|
|||
|
ctx.MarkLabel(end);
|
|||
|
|
|||
|
ctx.LoadValue(token);
|
|||
|
ctx.LoadReaderWriter();
|
|||
|
ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("EndSubItem"));
|
|||
|
ctx.LoadValue(oldValue); // load the old value
|
|||
|
}
|
|||
|
}
|
|||
|
protected override void EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom)
|
|||
|
{
|
|||
|
using (Compiler.Local valOrNull = ctx.GetLocalWithValue(expectedType, valueFrom))
|
|||
|
using (Compiler.Local token = new Compiler.Local(ctx, ctx.MapType(typeof(SubItemToken))))
|
|||
|
{
|
|||
|
ctx.LoadNullRef();
|
|||
|
ctx.LoadReaderWriter();
|
|||
|
ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("StartSubItem"));
|
|||
|
ctx.StoreValue(token);
|
|||
|
|
|||
|
if (Helpers.IsValueType(expectedType))
|
|||
|
{
|
|||
|
ctx.LoadAddress(valOrNull, expectedType);
|
|||
|
ctx.LoadValue(expectedType.GetProperty("HasValue"));
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
ctx.LoadValue(valOrNull);
|
|||
|
}
|
|||
|
Compiler.CodeLabel @end = ctx.DefineLabel();
|
|||
|
ctx.BranchIfFalse(@end, false);
|
|||
|
if (Helpers.IsValueType(expectedType))
|
|||
|
{
|
|||
|
ctx.LoadAddress(valOrNull, expectedType);
|
|||
|
ctx.EmitCall(expectedType.GetMethod("GetValueOrDefault", Helpers.EmptyTypes));
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
ctx.LoadValue(valOrNull);
|
|||
|
}
|
|||
|
Tail.EmitWrite(ctx, null);
|
|||
|
|
|||
|
ctx.MarkLabel(@end);
|
|||
|
|
|||
|
ctx.LoadValue(token);
|
|||
|
ctx.LoadReaderWriter();
|
|||
|
ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("EndSubItem"));
|
|||
|
}
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
public override object Read(object value, ProtoReader source)
|
|||
|
{
|
|||
|
SubItemToken tok = ProtoReader.StartSubItem(source);
|
|||
|
int field;
|
|||
|
while ((field = source.ReadFieldHeader()) > 0)
|
|||
|
{
|
|||
|
if (field == Tag)
|
|||
|
{
|
|||
|
value = Tail.Read(value, source);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
source.SkipField();
|
|||
|
}
|
|||
|
}
|
|||
|
ProtoReader.EndSubItem(tok, source);
|
|||
|
return value;
|
|||
|
}
|
|||
|
|
|||
|
public override void Write(object value, ProtoWriter dest)
|
|||
|
{
|
|||
|
SubItemToken token = ProtoWriter.StartSubItem(null, dest);
|
|||
|
if (value != null)
|
|||
|
{
|
|||
|
Tail.Write(value, dest);
|
|||
|
}
|
|||
|
ProtoWriter.EndSubItem(token, dest);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
#endif
|