ListDecorator.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. #if !NO_RUNTIME
  2. using System;
  3. using System.Collections;
  4. using ProtoBuf.Meta;
  5. #if FEAT_IKVM
  6. using Type = IKVM.Reflection.Type;
  7. using IKVM.Reflection;
  8. #else
  9. using System.Reflection;
  10. #endif
  11. namespace ProtoBuf.Serializers
  12. {
  13. class ListDecorator : ProtoDecoratorBase
  14. {
  15. internal static bool CanPack(WireType wireType)
  16. {
  17. switch (wireType)
  18. {
  19. case WireType.Fixed32:
  20. case WireType.Fixed64:
  21. case WireType.SignedVariant:
  22. case WireType.Variant:
  23. return true;
  24. default:
  25. return false;
  26. }
  27. }
  28. private readonly byte options;
  29. private const byte OPTIONS_IsList = 1,
  30. OPTIONS_SuppressIList = 2,
  31. OPTIONS_WritePacked = 4,
  32. OPTIONS_ReturnList = 8,
  33. OPTIONS_OverwriteList = 16,
  34. OPTIONS_SupportNull = 32;
  35. private readonly Type declaredType, concreteType;
  36. private readonly MethodInfo add;
  37. private readonly int fieldNumber;
  38. private bool IsList { get { return (options & OPTIONS_IsList) != 0; } }
  39. private bool SuppressIList { get { return (options & OPTIONS_SuppressIList) != 0; } }
  40. private bool WritePacked { get { return (options & OPTIONS_WritePacked) != 0; } }
  41. private bool SupportNull { get { return (options & OPTIONS_SupportNull) != 0; } }
  42. private bool ReturnList { get { return (options & OPTIONS_ReturnList) != 0; } }
  43. protected readonly WireType packedWireType;
  44. internal static ListDecorator Create(TypeModel model, Type declaredType, Type concreteType, IProtoSerializer tail, int fieldNumber, bool writePacked, WireType packedWireType, bool returnList, bool overwriteList, bool supportNull)
  45. {
  46. #if !NO_GENERICS
  47. MethodInfo builderFactory, add, addRange, finish;
  48. if (returnList && ImmutableCollectionDecorator.IdentifyImmutable(model, declaredType, out builderFactory, out add, out addRange, out finish))
  49. {
  50. return new ImmutableCollectionDecorator(
  51. model, declaredType, concreteType, tail, fieldNumber, writePacked, packedWireType, returnList, overwriteList, supportNull,
  52. builderFactory, add, addRange, finish);
  53. }
  54. #endif
  55. return new ListDecorator(model, declaredType, concreteType, tail, fieldNumber, writePacked, packedWireType, returnList, overwriteList, supportNull);
  56. }
  57. protected ListDecorator(TypeModel model, Type declaredType, Type concreteType, IProtoSerializer tail, int fieldNumber, bool writePacked, WireType packedWireType, bool returnList, bool overwriteList, bool supportNull)
  58. : base(tail)
  59. {
  60. if (returnList) options |= OPTIONS_ReturnList;
  61. if (overwriteList) options |= OPTIONS_OverwriteList;
  62. if (supportNull) options |= OPTIONS_SupportNull;
  63. if ((writePacked || packedWireType != WireType.None) && fieldNumber <= 0) throw new ArgumentOutOfRangeException("fieldNumber");
  64. if (!CanPack(packedWireType))
  65. {
  66. if (writePacked) throw new InvalidOperationException("Only simple data-types can use packed encoding");
  67. packedWireType = WireType.None;
  68. }
  69. this.fieldNumber = fieldNumber;
  70. if (writePacked) options |= OPTIONS_WritePacked;
  71. this.packedWireType = packedWireType;
  72. if (declaredType == null) throw new ArgumentNullException("declaredType");
  73. if (declaredType.IsArray) throw new ArgumentException("Cannot treat arrays as lists", "declaredType");
  74. this.declaredType = declaredType;
  75. this.concreteType = concreteType;
  76. // look for a public list.Add(typedObject) method
  77. if (RequireAdd)
  78. {
  79. bool isList;
  80. add = TypeModel.ResolveListAdd(model, declaredType, tail.ExpectedType, out isList);
  81. if (isList)
  82. {
  83. options |= OPTIONS_IsList;
  84. string fullName = declaredType.FullName;
  85. if (fullName != null && fullName.StartsWith("System.Data.Linq.EntitySet`1[["))
  86. { // see http://stackoverflow.com/questions/6194639/entityset-is-there-a-sane-reason-that-ilist-add-doesnt-set-assigned
  87. options |= OPTIONS_SuppressIList;
  88. }
  89. }
  90. if (add == null) throw new InvalidOperationException("Unable to resolve a suitable Add method for " + declaredType.FullName);
  91. }
  92. }
  93. protected virtual bool RequireAdd { get { return true; } }
  94. public override Type ExpectedType { get { return declaredType; } }
  95. public override bool RequiresOldValue { get { return AppendToCollection; } }
  96. public override bool ReturnsValue { get { return ReturnList; } }
  97. protected bool AppendToCollection
  98. {
  99. get { return (options & OPTIONS_OverwriteList) == 0; }
  100. }
  101. #if FEAT_COMPILER
  102. protected override void EmitRead(ProtoBuf.Compiler.CompilerContext ctx, ProtoBuf.Compiler.Local valueFrom)
  103. {
  104. /* This looks more complex than it is. Look at the non-compiled Read to
  105. * see what it is trying to do, but note that it needs to cope with a
  106. * few more scenarios. Note that it picks the **most specific** Add,
  107. * unlike the runtime version that uses IList when possible. The core
  108. * is just a "do {list.Add(readValue())} while {thereIsMore}"
  109. *
  110. * The complexity is due to:
  111. * - value types vs reference types (boxing etc)
  112. * - initialization if we need to pass in a value to the tail
  113. * - handling whether or not the tail *returns* the value vs updates the input
  114. */
  115. bool returnList = ReturnList;
  116. using (Compiler.Local list = AppendToCollection ? ctx.GetLocalWithValue(ExpectedType, valueFrom) : new Compiler.Local(ctx, declaredType))
  117. using (Compiler.Local origlist = (returnList && AppendToCollection && !Helpers.IsValueType(ExpectedType)) ? new Compiler.Local(ctx, ExpectedType) : null)
  118. {
  119. if (!AppendToCollection)
  120. { // always new
  121. ctx.LoadNullRef();
  122. ctx.StoreValue(list);
  123. }
  124. else if (returnList && origlist != null)
  125. { // need a copy
  126. ctx.LoadValue(list);
  127. ctx.StoreValue(origlist);
  128. }
  129. if (concreteType != null)
  130. {
  131. ctx.LoadValue(list);
  132. Compiler.CodeLabel notNull = ctx.DefineLabel();
  133. ctx.BranchIfTrue(notNull, true);
  134. ctx.EmitCtor(concreteType);
  135. ctx.StoreValue(list);
  136. ctx.MarkLabel(notNull);
  137. }
  138. bool castListForAdd = !add.DeclaringType.IsAssignableFrom(declaredType);
  139. EmitReadList(ctx, list, Tail, add, packedWireType, castListForAdd);
  140. if (returnList)
  141. {
  142. if (AppendToCollection && origlist != null)
  143. {
  144. // remember ^^^^ we had a spare copy of the list on the stack; now we'll compare
  145. ctx.LoadValue(origlist);
  146. ctx.LoadValue(list); // [orig] [new-value]
  147. Compiler.CodeLabel sameList = ctx.DefineLabel(), allDone = ctx.DefineLabel();
  148. ctx.BranchIfEqual(sameList, true);
  149. ctx.LoadValue(list);
  150. ctx.Branch(allDone, true);
  151. ctx.MarkLabel(sameList);
  152. ctx.LoadNullRef();
  153. ctx.MarkLabel(allDone);
  154. }
  155. else
  156. {
  157. ctx.LoadValue(list);
  158. }
  159. }
  160. }
  161. }
  162. internal static void EmitReadList(ProtoBuf.Compiler.CompilerContext ctx, Compiler.Local list, IProtoSerializer tail, MethodInfo add, WireType packedWireType, bool castListForAdd)
  163. {
  164. using (Compiler.Local fieldNumber = new Compiler.Local(ctx, ctx.MapType(typeof(int))))
  165. {
  166. Compiler.CodeLabel readPacked = packedWireType == WireType.None ? new Compiler.CodeLabel() : ctx.DefineLabel();
  167. if (packedWireType != WireType.None)
  168. {
  169. ctx.LoadReaderWriter();
  170. ctx.LoadValue(typeof(ProtoReader).GetProperty("WireType"));
  171. ctx.LoadValue((int)WireType.String);
  172. ctx.BranchIfEqual(readPacked, false);
  173. }
  174. ctx.LoadReaderWriter();
  175. ctx.LoadValue(typeof(ProtoReader).GetProperty("FieldNumber"));
  176. ctx.StoreValue(fieldNumber);
  177. Compiler.CodeLabel @continue = ctx.DefineLabel();
  178. ctx.MarkLabel(@continue);
  179. EmitReadAndAddItem(ctx, list, tail, add, castListForAdd);
  180. ctx.LoadReaderWriter();
  181. ctx.LoadValue(fieldNumber);
  182. ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("TryReadFieldHeader"));
  183. ctx.BranchIfTrue(@continue, false);
  184. if (packedWireType != WireType.None)
  185. {
  186. Compiler.CodeLabel allDone = ctx.DefineLabel();
  187. ctx.Branch(allDone, false);
  188. ctx.MarkLabel(readPacked);
  189. ctx.LoadReaderWriter();
  190. ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("StartSubItem"));
  191. Compiler.CodeLabel testForData = ctx.DefineLabel(), noMoreData = ctx.DefineLabel();
  192. ctx.MarkLabel(testForData);
  193. ctx.LoadValue((int)packedWireType);
  194. ctx.LoadReaderWriter();
  195. ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("HasSubValue"));
  196. ctx.BranchIfFalse(noMoreData, false);
  197. EmitReadAndAddItem(ctx, list, tail, add, castListForAdd);
  198. ctx.Branch(testForData, false);
  199. ctx.MarkLabel(noMoreData);
  200. ctx.LoadReaderWriter();
  201. ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("EndSubItem"));
  202. ctx.MarkLabel(allDone);
  203. }
  204. }
  205. }
  206. private static void EmitReadAndAddItem(Compiler.CompilerContext ctx, Compiler.Local list, IProtoSerializer tail, MethodInfo add, bool castListForAdd)
  207. {
  208. ctx.LoadAddress(list, list.Type); // needs to be the reference in case the list is value-type (static-call)
  209. if (castListForAdd) ctx.Cast(add.DeclaringType);
  210. Type itemType = tail.ExpectedType;
  211. bool tailReturnsValue = tail.ReturnsValue;
  212. if (tail.RequiresOldValue)
  213. {
  214. if (Helpers.IsValueType(itemType) || !tailReturnsValue)
  215. {
  216. // going to need a variable
  217. using (Compiler.Local item = new Compiler.Local(ctx, itemType))
  218. {
  219. if (Helpers.IsValueType(itemType))
  220. { // initialise the struct
  221. ctx.LoadAddress(item, itemType);
  222. ctx.EmitCtor(itemType);
  223. }
  224. else
  225. { // assign null
  226. ctx.LoadNullRef();
  227. ctx.StoreValue(item);
  228. }
  229. tail.EmitRead(ctx, item);
  230. if (!tailReturnsValue) { ctx.LoadValue(item); }
  231. }
  232. }
  233. else
  234. { // no variable; pass the null on the stack and take the value *off* the stack
  235. ctx.LoadNullRef();
  236. tail.EmitRead(ctx, null);
  237. }
  238. }
  239. else
  240. {
  241. if (tailReturnsValue)
  242. { // out only (on the stack); just emit it
  243. tail.EmitRead(ctx, null);
  244. }
  245. else
  246. { // doesn't take anything in nor return anything! WTF?
  247. throw new InvalidOperationException();
  248. }
  249. }
  250. // our "Add" is chosen either to take the correct type, or to take "object";
  251. // we may need to box the value
  252. Type addParamType = add.GetParameters()[0].ParameterType;
  253. if(addParamType != itemType) {
  254. if (addParamType == ctx.MapType(typeof(object)))
  255. {
  256. ctx.CastToObject(itemType);
  257. }
  258. #if !NO_GENERICS
  259. else if(Helpers.GetUnderlyingType(addParamType) == itemType)
  260. { // list is nullable
  261. ConstructorInfo ctor = Helpers.GetConstructor(addParamType, new Type[] {itemType}, false);
  262. ctx.EmitCtor(ctor); // the itemType on the stack is now a Nullable<ItemType>
  263. }
  264. #endif
  265. else
  266. {
  267. throw new InvalidOperationException("Conflicting item/add type");
  268. }
  269. }
  270. ctx.EmitCall(add, list.Type);
  271. if (add.ReturnType != ctx.MapType(typeof(void)))
  272. {
  273. ctx.DiscardValue();
  274. }
  275. }
  276. #endif
  277. #if WINRT || COREFX
  278. private static readonly TypeInfo ienumeratorType = typeof(IEnumerator).GetTypeInfo(), ienumerableType = typeof (IEnumerable).GetTypeInfo();
  279. #else
  280. private static readonly System.Type ienumeratorType = typeof (IEnumerator), ienumerableType = typeof (IEnumerable);
  281. #endif
  282. protected MethodInfo GetEnumeratorInfo(TypeModel model, out MethodInfo moveNext, out MethodInfo current)
  283. {
  284. #if WINRT || COREFX
  285. TypeInfo enumeratorType = null, iteratorType, expectedType = ExpectedType.GetTypeInfo();
  286. #else
  287. Type enumeratorType = null, iteratorType, expectedType = ExpectedType;
  288. #endif
  289. // try a custom enumerator
  290. MethodInfo getEnumerator = Helpers.GetInstanceMethod(expectedType, "GetEnumerator", null);
  291. Type itemType = Tail.ExpectedType;
  292. Type getReturnType = null;
  293. if (getEnumerator != null)
  294. {
  295. getReturnType = getEnumerator.ReturnType;
  296. iteratorType = getReturnType
  297. #if WINRT || COREFX || COREFX
  298. .GetTypeInfo()
  299. #endif
  300. ;
  301. moveNext = Helpers.GetInstanceMethod(iteratorType, "MoveNext", null);
  302. PropertyInfo prop = Helpers.GetProperty(iteratorType, "Current", false);
  303. current = prop == null ? null : Helpers.GetGetMethod(prop, false, false);
  304. if (moveNext == null && (model.MapType(ienumeratorType).IsAssignableFrom(iteratorType)))
  305. {
  306. moveNext = Helpers.GetInstanceMethod(model.MapType(ienumeratorType), "MoveNext", null);
  307. }
  308. // fully typed
  309. if (moveNext != null && moveNext.ReturnType == model.MapType(typeof(bool))
  310. && current != null && current.ReturnType == itemType)
  311. {
  312. return getEnumerator;
  313. }
  314. moveNext = current = getEnumerator = null;
  315. }
  316. #if !NO_GENERICS
  317. // try IEnumerable<T>
  318. Type tmp = model.MapType(typeof(System.Collections.Generic.IEnumerable<>), false);
  319. if (tmp != null)
  320. {
  321. tmp = tmp.MakeGenericType(itemType);
  322. #if WINRT || COREFX
  323. enumeratorType = tmp.GetTypeInfo();
  324. #else
  325. enumeratorType = tmp;
  326. #endif
  327. }
  328. ;
  329. if (enumeratorType != null && enumeratorType.IsAssignableFrom(expectedType))
  330. {
  331. getEnumerator = Helpers.GetInstanceMethod(enumeratorType, "GetEnumerator");
  332. getReturnType = getEnumerator.ReturnType;
  333. #if WINRT || COREFX
  334. iteratorType = getReturnType.GetTypeInfo();
  335. #else
  336. iteratorType = getReturnType;
  337. #endif
  338. moveNext = Helpers.GetInstanceMethod(model.MapType(ienumeratorType), "MoveNext");
  339. current = Helpers.GetGetMethod(Helpers.GetProperty(iteratorType, "Current", false), false, false);
  340. return getEnumerator;
  341. }
  342. #endif
  343. // give up and fall-back to non-generic IEnumerable
  344. enumeratorType = model.MapType(ienumerableType);
  345. getEnumerator = Helpers.GetInstanceMethod(enumeratorType, "GetEnumerator");
  346. getReturnType = getEnumerator.ReturnType;
  347. iteratorType = getReturnType
  348. #if WINRT || COREFX
  349. .GetTypeInfo()
  350. #endif
  351. ;
  352. moveNext = Helpers.GetInstanceMethod(iteratorType, "MoveNext");
  353. current = Helpers.GetGetMethod(Helpers.GetProperty(iteratorType,"Current", false), false, false);
  354. return getEnumerator;
  355. }
  356. #if FEAT_COMPILER
  357. protected override void EmitWrite(ProtoBuf.Compiler.CompilerContext ctx, ProtoBuf.Compiler.Local valueFrom)
  358. {
  359. using (Compiler.Local list = ctx.GetLocalWithValue(ExpectedType, valueFrom))
  360. {
  361. MethodInfo moveNext, current, getEnumerator = GetEnumeratorInfo(ctx.Model, out moveNext, out current);
  362. Helpers.DebugAssert(moveNext != null);
  363. Helpers.DebugAssert(current != null);
  364. Helpers.DebugAssert(getEnumerator != null);
  365. Type enumeratorType = getEnumerator.ReturnType;
  366. bool writePacked = WritePacked;
  367. using (Compiler.Local iter = new Compiler.Local(ctx, enumeratorType))
  368. using (Compiler.Local token = writePacked ? new Compiler.Local(ctx, ctx.MapType(typeof(SubItemToken))) : null)
  369. {
  370. if (writePacked)
  371. {
  372. ctx.LoadValue(fieldNumber);
  373. ctx.LoadValue((int)WireType.String);
  374. ctx.LoadReaderWriter();
  375. ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("WriteFieldHeader"));
  376. ctx.LoadValue(list);
  377. ctx.LoadReaderWriter();
  378. ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("StartSubItem"));
  379. ctx.StoreValue(token);
  380. ctx.LoadValue(fieldNumber);
  381. ctx.LoadReaderWriter();
  382. ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("SetPackedField"));
  383. }
  384. ctx.LoadAddress(list, ExpectedType);
  385. ctx.EmitCall(getEnumerator, ExpectedType);
  386. ctx.StoreValue(iter);
  387. using (ctx.Using(iter))
  388. {
  389. Compiler.CodeLabel body = ctx.DefineLabel(), next = ctx.DefineLabel();
  390. ctx.Branch(next, false);
  391. ctx.MarkLabel(body);
  392. ctx.LoadAddress(iter, enumeratorType);
  393. ctx.EmitCall(current, enumeratorType);
  394. Type itemType = Tail.ExpectedType;
  395. if (itemType != ctx.MapType(typeof(object)) && current.ReturnType == ctx.MapType(typeof(object)))
  396. {
  397. ctx.CastFromObject(itemType);
  398. }
  399. Tail.EmitWrite(ctx, null);
  400. ctx.MarkLabel(@next);
  401. ctx.LoadAddress(iter, enumeratorType);
  402. ctx.EmitCall(moveNext, enumeratorType);
  403. ctx.BranchIfTrue(body, false);
  404. }
  405. if (writePacked)
  406. {
  407. ctx.LoadValue(token);
  408. ctx.LoadReaderWriter();
  409. ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("EndSubItem"));
  410. }
  411. }
  412. }
  413. }
  414. #endif
  415. #if !FEAT_IKVM
  416. public override void Write(object value, ProtoWriter dest)
  417. {
  418. SubItemToken token;
  419. bool writePacked = WritePacked;
  420. if (writePacked)
  421. {
  422. ProtoWriter.WriteFieldHeader(fieldNumber, WireType.String, dest);
  423. token = ProtoWriter.StartSubItem(value, dest);
  424. ProtoWriter.SetPackedField(fieldNumber, dest);
  425. }
  426. else
  427. {
  428. token = new SubItemToken(); // default
  429. }
  430. bool checkForNull = !SupportNull;
  431. foreach (object subItem in (IEnumerable)value)
  432. {
  433. if (checkForNull && subItem == null) { throw new NullReferenceException(); }
  434. Tail.Write(subItem, dest);
  435. }
  436. if (writePacked)
  437. {
  438. ProtoWriter.EndSubItem(token, dest);
  439. }
  440. }
  441. public override object Read(object value, ProtoReader source)
  442. {
  443. try
  444. {
  445. int field = source.FieldNumber;
  446. object origValue = value;
  447. if (value == null) value = Activator.CreateInstance(concreteType);
  448. bool isList = IsList && !SuppressIList;
  449. if (packedWireType != WireType.None && source.WireType == WireType.String)
  450. {
  451. SubItemToken token = ProtoReader.StartSubItem(source);
  452. if (isList)
  453. {
  454. IList list = (IList)value;
  455. while (ProtoReader.HasSubValue(packedWireType, source))
  456. {
  457. list.Add(Tail.Read(null, source));
  458. }
  459. }
  460. else
  461. {
  462. object[] args = new object[1];
  463. while (ProtoReader.HasSubValue(packedWireType, source))
  464. {
  465. args[0] = Tail.Read(null, source);
  466. add.Invoke(value, args);
  467. }
  468. }
  469. ProtoReader.EndSubItem(token, source);
  470. }
  471. else
  472. {
  473. if (isList)
  474. {
  475. IList list = (IList)value;
  476. do
  477. {
  478. list.Add(Tail.Read(null, source));
  479. } while (source.TryReadFieldHeader(field));
  480. }
  481. else
  482. {
  483. object[] args = new object[1];
  484. do
  485. {
  486. args[0] = Tail.Read(null, source);
  487. add.Invoke(value, args);
  488. } while (source.TryReadFieldHeader(field));
  489. }
  490. }
  491. return origValue == value ? null : value;
  492. } catch(TargetInvocationException tie)
  493. {
  494. if (tie.InnerException != null) throw tie.InnerException;
  495. throw;
  496. }
  497. }
  498. #endif
  499. }
  500. }
  501. #endif