123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553 |
- #if !NO_RUNTIME
- using System;
- using System.Collections;
- using ProtoBuf.Meta;
- #if FEAT_IKVM
- using Type = IKVM.Reflection.Type;
- using IKVM.Reflection;
- #else
- using System.Reflection;
- #endif
- namespace ProtoBuf.Serializers
- {
- class ListDecorator : ProtoDecoratorBase
- {
- internal static bool CanPack(WireType wireType)
- {
- switch (wireType)
- {
- case WireType.Fixed32:
- case WireType.Fixed64:
- case WireType.SignedVariant:
- case WireType.Variant:
- return true;
- default:
- return false;
- }
- }
- private readonly byte options;
- private const byte OPTIONS_IsList = 1,
- OPTIONS_SuppressIList = 2,
- OPTIONS_WritePacked = 4,
- OPTIONS_ReturnList = 8,
- OPTIONS_OverwriteList = 16,
- OPTIONS_SupportNull = 32;
- private readonly Type declaredType, concreteType;
- private readonly MethodInfo add;
- private readonly int fieldNumber;
- private bool IsList { get { return (options & OPTIONS_IsList) != 0; } }
- private bool SuppressIList { get { return (options & OPTIONS_SuppressIList) != 0; } }
- private bool WritePacked { get { return (options & OPTIONS_WritePacked) != 0; } }
- private bool SupportNull { get { return (options & OPTIONS_SupportNull) != 0; } }
- private bool ReturnList { get { return (options & OPTIONS_ReturnList) != 0; } }
- protected readonly WireType packedWireType;
- internal static ListDecorator Create(TypeModel model, Type declaredType, Type concreteType, IProtoSerializer tail, int fieldNumber, bool writePacked, WireType packedWireType, bool returnList, bool overwriteList, bool supportNull)
- {
- #if !NO_GENERICS
- MethodInfo builderFactory, add, addRange, finish;
- if (returnList && ImmutableCollectionDecorator.IdentifyImmutable(model, declaredType, out builderFactory, out add, out addRange, out finish))
- {
- return new ImmutableCollectionDecorator(
- model, declaredType, concreteType, tail, fieldNumber, writePacked, packedWireType, returnList, overwriteList, supportNull,
- builderFactory, add, addRange, finish);
- }
- #endif
- return new ListDecorator(model, declaredType, concreteType, tail, fieldNumber, writePacked, packedWireType, returnList, overwriteList, supportNull);
- }
- protected ListDecorator(TypeModel model, Type declaredType, Type concreteType, IProtoSerializer tail, int fieldNumber, bool writePacked, WireType packedWireType, bool returnList, bool overwriteList, bool supportNull)
- : base(tail)
- {
- if (returnList) options |= OPTIONS_ReturnList;
- if (overwriteList) options |= OPTIONS_OverwriteList;
- if (supportNull) options |= OPTIONS_SupportNull;
- if ((writePacked || packedWireType != WireType.None) && fieldNumber <= 0) throw new ArgumentOutOfRangeException("fieldNumber");
- if (!CanPack(packedWireType))
- {
- if (writePacked) throw new InvalidOperationException("Only simple data-types can use packed encoding");
- packedWireType = WireType.None;
- }
- this.fieldNumber = fieldNumber;
- if (writePacked) options |= OPTIONS_WritePacked;
- this.packedWireType = packedWireType;
- if (declaredType == null) throw new ArgumentNullException("declaredType");
- if (declaredType.IsArray) throw new ArgumentException("Cannot treat arrays as lists", "declaredType");
- this.declaredType = declaredType;
- this.concreteType = concreteType;
-
- // look for a public list.Add(typedObject) method
- if (RequireAdd)
- {
- bool isList;
- add = TypeModel.ResolveListAdd(model, declaredType, tail.ExpectedType, out isList);
- if (isList)
- {
- options |= OPTIONS_IsList;
- string fullName = declaredType.FullName;
- if (fullName != null && fullName.StartsWith("System.Data.Linq.EntitySet`1[["))
- { // see http://stackoverflow.com/questions/6194639/entityset-is-there-a-sane-reason-that-ilist-add-doesnt-set-assigned
- options |= OPTIONS_SuppressIList;
- }
- }
- if (add == null) throw new InvalidOperationException("Unable to resolve a suitable Add method for " + declaredType.FullName);
- }
- }
- protected virtual bool RequireAdd { get { return true; } }
- public override Type ExpectedType { get { return declaredType; } }
- public override bool RequiresOldValue { get { return AppendToCollection; } }
- public override bool ReturnsValue { get { return ReturnList; } }
- protected bool AppendToCollection
- {
- get { return (options & OPTIONS_OverwriteList) == 0; }
- }
- #if FEAT_COMPILER
- protected override void EmitRead(ProtoBuf.Compiler.CompilerContext ctx, ProtoBuf.Compiler.Local valueFrom)
- {
- /* This looks more complex than it is. Look at the non-compiled Read to
- * see what it is trying to do, but note that it needs to cope with a
- * few more scenarios. Note that it picks the **most specific** Add,
- * unlike the runtime version that uses IList when possible. The core
- * is just a "do {list.Add(readValue())} while {thereIsMore}"
- *
- * The complexity is due to:
- * - value types vs reference types (boxing etc)
- * - initialization if we need to pass in a value to the tail
- * - handling whether or not the tail *returns* the value vs updates the input
- */
- bool returnList = ReturnList;
-
- using (Compiler.Local list = AppendToCollection ? ctx.GetLocalWithValue(ExpectedType, valueFrom) : new Compiler.Local(ctx, declaredType))
- using (Compiler.Local origlist = (returnList && AppendToCollection && !Helpers.IsValueType(ExpectedType)) ? new Compiler.Local(ctx, ExpectedType) : null)
- {
- if (!AppendToCollection)
- { // always new
- ctx.LoadNullRef();
- ctx.StoreValue(list);
- }
- else if (returnList && origlist != null)
- { // need a copy
- ctx.LoadValue(list);
- ctx.StoreValue(origlist);
- }
- if (concreteType != null)
- {
- ctx.LoadValue(list);
- Compiler.CodeLabel notNull = ctx.DefineLabel();
- ctx.BranchIfTrue(notNull, true);
- ctx.EmitCtor(concreteType);
- ctx.StoreValue(list);
- ctx.MarkLabel(notNull);
- }
- bool castListForAdd = !add.DeclaringType.IsAssignableFrom(declaredType);
- EmitReadList(ctx, list, Tail, add, packedWireType, castListForAdd);
- if (returnList)
- {
- if (AppendToCollection && origlist != null)
- {
- // remember ^^^^ we had a spare copy of the list on the stack; now we'll compare
- ctx.LoadValue(origlist);
- ctx.LoadValue(list); // [orig] [new-value]
- Compiler.CodeLabel sameList = ctx.DefineLabel(), allDone = ctx.DefineLabel();
- ctx.BranchIfEqual(sameList, true);
- ctx.LoadValue(list);
- ctx.Branch(allDone, true);
- ctx.MarkLabel(sameList);
- ctx.LoadNullRef();
- ctx.MarkLabel(allDone);
- }
- else
- {
- ctx.LoadValue(list);
- }
- }
- }
- }
- internal static void EmitReadList(ProtoBuf.Compiler.CompilerContext ctx, Compiler.Local list, IProtoSerializer tail, MethodInfo add, WireType packedWireType, bool castListForAdd)
- {
- using (Compiler.Local fieldNumber = new Compiler.Local(ctx, ctx.MapType(typeof(int))))
- {
- Compiler.CodeLabel readPacked = packedWireType == WireType.None ? new Compiler.CodeLabel() : ctx.DefineLabel();
- if (packedWireType != WireType.None)
- {
- ctx.LoadReaderWriter();
- ctx.LoadValue(typeof(ProtoReader).GetProperty("WireType"));
- ctx.LoadValue((int)WireType.String);
- ctx.BranchIfEqual(readPacked, false);
- }
- ctx.LoadReaderWriter();
- ctx.LoadValue(typeof(ProtoReader).GetProperty("FieldNumber"));
- ctx.StoreValue(fieldNumber);
- Compiler.CodeLabel @continue = ctx.DefineLabel();
- ctx.MarkLabel(@continue);
- EmitReadAndAddItem(ctx, list, tail, add, castListForAdd);
- ctx.LoadReaderWriter();
- ctx.LoadValue(fieldNumber);
- ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("TryReadFieldHeader"));
- ctx.BranchIfTrue(@continue, false);
- if (packedWireType != WireType.None)
- {
- Compiler.CodeLabel allDone = ctx.DefineLabel();
- ctx.Branch(allDone, false);
- ctx.MarkLabel(readPacked);
- ctx.LoadReaderWriter();
- ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("StartSubItem"));
- Compiler.CodeLabel testForData = ctx.DefineLabel(), noMoreData = ctx.DefineLabel();
- ctx.MarkLabel(testForData);
- ctx.LoadValue((int)packedWireType);
- ctx.LoadReaderWriter();
- ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("HasSubValue"));
- ctx.BranchIfFalse(noMoreData, false);
- EmitReadAndAddItem(ctx, list, tail, add, castListForAdd);
- ctx.Branch(testForData, false);
- ctx.MarkLabel(noMoreData);
- ctx.LoadReaderWriter();
- ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("EndSubItem"));
- ctx.MarkLabel(allDone);
- }
-
- }
- }
- private static void EmitReadAndAddItem(Compiler.CompilerContext ctx, Compiler.Local list, IProtoSerializer tail, MethodInfo add, bool castListForAdd)
- {
- ctx.LoadAddress(list, list.Type); // needs to be the reference in case the list is value-type (static-call)
- if (castListForAdd) ctx.Cast(add.DeclaringType);
- Type itemType = tail.ExpectedType;
- bool tailReturnsValue = tail.ReturnsValue;
- if (tail.RequiresOldValue)
- {
- if (Helpers.IsValueType(itemType) || !tailReturnsValue)
- {
- // going to need a variable
- using (Compiler.Local item = new Compiler.Local(ctx, itemType))
- {
- if (Helpers.IsValueType(itemType))
- { // initialise the struct
- ctx.LoadAddress(item, itemType);
- ctx.EmitCtor(itemType);
- }
- else
- { // assign null
- ctx.LoadNullRef();
- ctx.StoreValue(item);
- }
- tail.EmitRead(ctx, item);
- if (!tailReturnsValue) { ctx.LoadValue(item); }
- }
- }
- else
- { // no variable; pass the null on the stack and take the value *off* the stack
- ctx.LoadNullRef();
- tail.EmitRead(ctx, null);
- }
- }
- else
- {
- if (tailReturnsValue)
- { // out only (on the stack); just emit it
- tail.EmitRead(ctx, null);
- }
- else
- { // doesn't take anything in nor return anything! WTF?
- throw new InvalidOperationException();
- }
- }
- // our "Add" is chosen either to take the correct type, or to take "object";
- // we may need to box the value
-
- Type addParamType = add.GetParameters()[0].ParameterType;
- if(addParamType != itemType) {
- if (addParamType == ctx.MapType(typeof(object)))
- {
- ctx.CastToObject(itemType);
- }
- #if !NO_GENERICS
- else if(Helpers.GetUnderlyingType(addParamType) == itemType)
- { // list is nullable
- ConstructorInfo ctor = Helpers.GetConstructor(addParamType, new Type[] {itemType}, false);
- ctx.EmitCtor(ctor); // the itemType on the stack is now a Nullable<ItemType>
- }
- #endif
- else
- {
- throw new InvalidOperationException("Conflicting item/add type");
- }
- }
- ctx.EmitCall(add, list.Type);
- if (add.ReturnType != ctx.MapType(typeof(void)))
- {
- ctx.DiscardValue();
- }
- }
- #endif
- #if WINRT || COREFX
- private static readonly TypeInfo ienumeratorType = typeof(IEnumerator).GetTypeInfo(), ienumerableType = typeof (IEnumerable).GetTypeInfo();
- #else
- private static readonly System.Type ienumeratorType = typeof (IEnumerator), ienumerableType = typeof (IEnumerable);
- #endif
- protected MethodInfo GetEnumeratorInfo(TypeModel model, out MethodInfo moveNext, out MethodInfo current)
- {
- #if WINRT || COREFX
- TypeInfo enumeratorType = null, iteratorType, expectedType = ExpectedType.GetTypeInfo();
- #else
- Type enumeratorType = null, iteratorType, expectedType = ExpectedType;
- #endif
- // try a custom enumerator
- MethodInfo getEnumerator = Helpers.GetInstanceMethod(expectedType, "GetEnumerator", null);
- Type itemType = Tail.ExpectedType;
- Type getReturnType = null;
- if (getEnumerator != null)
- {
- getReturnType = getEnumerator.ReturnType;
- iteratorType = getReturnType
- #if WINRT || COREFX || COREFX
- .GetTypeInfo()
- #endif
- ;
- moveNext = Helpers.GetInstanceMethod(iteratorType, "MoveNext", null);
- PropertyInfo prop = Helpers.GetProperty(iteratorType, "Current", false);
- current = prop == null ? null : Helpers.GetGetMethod(prop, false, false);
- if (moveNext == null && (model.MapType(ienumeratorType).IsAssignableFrom(iteratorType)))
- {
- moveNext = Helpers.GetInstanceMethod(model.MapType(ienumeratorType), "MoveNext", null);
- }
- // fully typed
- if (moveNext != null && moveNext.ReturnType == model.MapType(typeof(bool))
- && current != null && current.ReturnType == itemType)
- {
- return getEnumerator;
- }
- moveNext = current = getEnumerator = null;
- }
-
- #if !NO_GENERICS
- // try IEnumerable<T>
- Type tmp = model.MapType(typeof(System.Collections.Generic.IEnumerable<>), false);
-
- if (tmp != null)
- {
- tmp = tmp.MakeGenericType(itemType);
- #if WINRT || COREFX
- enumeratorType = tmp.GetTypeInfo();
- #else
- enumeratorType = tmp;
- #endif
- }
- ;
- if (enumeratorType != null && enumeratorType.IsAssignableFrom(expectedType))
- {
- getEnumerator = Helpers.GetInstanceMethod(enumeratorType, "GetEnumerator");
- getReturnType = getEnumerator.ReturnType;
- #if WINRT || COREFX
- iteratorType = getReturnType.GetTypeInfo();
- #else
- iteratorType = getReturnType;
- #endif
- moveNext = Helpers.GetInstanceMethod(model.MapType(ienumeratorType), "MoveNext");
- current = Helpers.GetGetMethod(Helpers.GetProperty(iteratorType, "Current", false), false, false);
- return getEnumerator;
- }
- #endif
- // give up and fall-back to non-generic IEnumerable
- enumeratorType = model.MapType(ienumerableType);
- getEnumerator = Helpers.GetInstanceMethod(enumeratorType, "GetEnumerator");
- getReturnType = getEnumerator.ReturnType;
- iteratorType = getReturnType
- #if WINRT || COREFX
- .GetTypeInfo()
- #endif
- ;
- moveNext = Helpers.GetInstanceMethod(iteratorType, "MoveNext");
- current = Helpers.GetGetMethod(Helpers.GetProperty(iteratorType,"Current", false), false, false);
- return getEnumerator;
- }
- #if FEAT_COMPILER
- protected override void EmitWrite(ProtoBuf.Compiler.CompilerContext ctx, ProtoBuf.Compiler.Local valueFrom)
- {
- using (Compiler.Local list = ctx.GetLocalWithValue(ExpectedType, valueFrom))
- {
- MethodInfo moveNext, current, getEnumerator = GetEnumeratorInfo(ctx.Model, out moveNext, out current);
- Helpers.DebugAssert(moveNext != null);
- Helpers.DebugAssert(current != null);
- Helpers.DebugAssert(getEnumerator != null);
- Type enumeratorType = getEnumerator.ReturnType;
- bool writePacked = WritePacked;
- using (Compiler.Local iter = new Compiler.Local(ctx, enumeratorType))
- using (Compiler.Local token = writePacked ? new Compiler.Local(ctx, ctx.MapType(typeof(SubItemToken))) : null)
- {
- if (writePacked)
- {
- ctx.LoadValue(fieldNumber);
- ctx.LoadValue((int)WireType.String);
- ctx.LoadReaderWriter();
- ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("WriteFieldHeader"));
- ctx.LoadValue(list);
- ctx.LoadReaderWriter();
- ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("StartSubItem"));
- ctx.StoreValue(token);
- ctx.LoadValue(fieldNumber);
- ctx.LoadReaderWriter();
- ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("SetPackedField"));
- }
- ctx.LoadAddress(list, ExpectedType);
- ctx.EmitCall(getEnumerator, ExpectedType);
- ctx.StoreValue(iter);
- using (ctx.Using(iter))
- {
- Compiler.CodeLabel body = ctx.DefineLabel(), next = ctx.DefineLabel();
- ctx.Branch(next, false);
-
- ctx.MarkLabel(body);
- ctx.LoadAddress(iter, enumeratorType);
- ctx.EmitCall(current, enumeratorType);
- Type itemType = Tail.ExpectedType;
- if (itemType != ctx.MapType(typeof(object)) && current.ReturnType == ctx.MapType(typeof(object)))
- {
- ctx.CastFromObject(itemType);
- }
- Tail.EmitWrite(ctx, null);
- ctx.MarkLabel(@next);
- ctx.LoadAddress(iter, enumeratorType);
- ctx.EmitCall(moveNext, enumeratorType);
- ctx.BranchIfTrue(body, false);
- }
- if (writePacked)
- {
- ctx.LoadValue(token);
- ctx.LoadReaderWriter();
- ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("EndSubItem"));
- }
- }
- }
- }
- #endif
- #if !FEAT_IKVM
- public override void Write(object value, ProtoWriter dest)
- {
- SubItemToken token;
- bool writePacked = WritePacked;
- if (writePacked)
- {
- ProtoWriter.WriteFieldHeader(fieldNumber, WireType.String, dest);
- token = ProtoWriter.StartSubItem(value, dest);
- ProtoWriter.SetPackedField(fieldNumber, dest);
- }
- else
- {
- token = new SubItemToken(); // default
- }
- bool checkForNull = !SupportNull;
- foreach (object subItem in (IEnumerable)value)
- {
- if (checkForNull && subItem == null) { throw new NullReferenceException(); }
- Tail.Write(subItem, dest);
- }
- if (writePacked)
- {
- ProtoWriter.EndSubItem(token, dest);
- }
- }
- public override object Read(object value, ProtoReader source)
- {
- try
- {
- int field = source.FieldNumber;
- object origValue = value;
- if (value == null) value = Activator.CreateInstance(concreteType);
- bool isList = IsList && !SuppressIList;
- if (packedWireType != WireType.None && source.WireType == WireType.String)
- {
- SubItemToken token = ProtoReader.StartSubItem(source);
- if (isList)
- {
- IList list = (IList)value;
- while (ProtoReader.HasSubValue(packedWireType, source))
- {
- list.Add(Tail.Read(null, source));
- }
- }
- else
- {
- object[] args = new object[1];
- while (ProtoReader.HasSubValue(packedWireType, source))
- {
- args[0] = Tail.Read(null, source);
- add.Invoke(value, args);
- }
- }
- ProtoReader.EndSubItem(token, source);
- }
- else
- {
- if (isList)
- {
- IList list = (IList)value;
- do
- {
- list.Add(Tail.Read(null, source));
- } while (source.TryReadFieldHeader(field));
- }
- else
- {
- object[] args = new object[1];
- do
- {
- args[0] = Tail.Read(null, source);
- add.Invoke(value, args);
- } while (source.TryReadFieldHeader(field));
- }
- }
- return origValue == value ? null : value;
- } catch(TargetInvocationException tie)
- {
- if (tie.InnerException != null) throw tie.InnerException;
- throw;
- }
- }
- #endif
- }
- }
- #endif
|