ArrayDecorator.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  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. sealed class ArrayDecorator : ProtoDecoratorBase
  14. {
  15. private readonly int fieldNumber;
  16. private const byte
  17. OPTIONS_WritePacked = 1,
  18. OPTIONS_OverwriteList = 2,
  19. OPTIONS_SupportNull = 4;
  20. private readonly byte options;
  21. private readonly WireType packedWireType;
  22. public ArrayDecorator(TypeModel model, IProtoSerializer tail, int fieldNumber, bool writePacked, WireType packedWireType, Type arrayType, bool overwriteList, bool supportNull)
  23. : base(tail)
  24. {
  25. Helpers.DebugAssert(arrayType != null, "arrayType should be non-null");
  26. Helpers.DebugAssert(arrayType.IsArray && arrayType.GetArrayRank() == 1, "should be single-dimension array; " + arrayType.FullName);
  27. this.itemType = arrayType.GetElementType();
  28. #if NO_GENERICS
  29. Type underlyingItemType = itemType;
  30. #else
  31. Type underlyingItemType = supportNull ? itemType : (Helpers.GetUnderlyingType(itemType) ?? itemType);
  32. #endif
  33. Helpers.DebugAssert(underlyingItemType == Tail.ExpectedType
  34. || (Tail.ExpectedType == typeof(object) && !Helpers.IsValueType(underlyingItemType)), "invalid tail");
  35. Helpers.DebugAssert(Tail.ExpectedType != model.MapType(typeof(byte)), "Should have used BlobSerializer");
  36. if ((writePacked || packedWireType != WireType.None) && fieldNumber <= 0) throw new ArgumentOutOfRangeException("fieldNumber");
  37. if (!ListDecorator.CanPack(packedWireType))
  38. {
  39. if (writePacked) throw new InvalidOperationException("Only simple data-types can use packed encoding");
  40. packedWireType = WireType.None;
  41. }
  42. this.fieldNumber = fieldNumber;
  43. this.packedWireType = packedWireType;
  44. if (writePacked) options |= OPTIONS_WritePacked;
  45. if (overwriteList) options |= OPTIONS_OverwriteList;
  46. if (supportNull) options |= OPTIONS_SupportNull;
  47. this.arrayType = arrayType;
  48. }
  49. readonly Type arrayType, itemType; // this is, for example, typeof(int[])
  50. public override Type ExpectedType { get { return arrayType; } }
  51. public override bool RequiresOldValue { get { return AppendToCollection; } }
  52. public override bool ReturnsValue { get { return true; } }
  53. #if FEAT_COMPILER
  54. protected override void EmitWrite(ProtoBuf.Compiler.CompilerContext ctx, ProtoBuf.Compiler.Local valueFrom)
  55. {
  56. // int i and T[] arr
  57. using (Compiler.Local arr = ctx.GetLocalWithValue(arrayType, valueFrom))
  58. using (Compiler.Local i = new ProtoBuf.Compiler.Local(ctx, ctx.MapType(typeof(int))))
  59. {
  60. bool writePacked = (options & OPTIONS_WritePacked) != 0;
  61. using (Compiler.Local token = writePacked ? new Compiler.Local(ctx, ctx.MapType(typeof(SubItemToken))) : null)
  62. {
  63. Type mappedWriter = ctx.MapType(typeof (ProtoWriter));
  64. if (writePacked)
  65. {
  66. ctx.LoadValue(fieldNumber);
  67. ctx.LoadValue((int)WireType.String);
  68. ctx.LoadReaderWriter();
  69. ctx.EmitCall(mappedWriter.GetMethod("WriteFieldHeader"));
  70. ctx.LoadValue(arr);
  71. ctx.LoadReaderWriter();
  72. ctx.EmitCall(mappedWriter.GetMethod("StartSubItem"));
  73. ctx.StoreValue(token);
  74. ctx.LoadValue(fieldNumber);
  75. ctx.LoadReaderWriter();
  76. ctx.EmitCall(mappedWriter.GetMethod("SetPackedField"));
  77. }
  78. EmitWriteArrayLoop(ctx, i, arr);
  79. if (writePacked)
  80. {
  81. ctx.LoadValue(token);
  82. ctx.LoadReaderWriter();
  83. ctx.EmitCall(mappedWriter.GetMethod("EndSubItem"));
  84. }
  85. }
  86. }
  87. }
  88. private void EmitWriteArrayLoop(Compiler.CompilerContext ctx, Compiler.Local i, Compiler.Local arr)
  89. {
  90. // i = 0
  91. ctx.LoadValue(0);
  92. ctx.StoreValue(i);
  93. // range test is last (to minimise branches)
  94. Compiler.CodeLabel loopTest = ctx.DefineLabel(), processItem = ctx.DefineLabel();
  95. ctx.Branch(loopTest, false);
  96. ctx.MarkLabel(processItem);
  97. // {...}
  98. ctx.LoadArrayValue(arr, i);
  99. if (SupportNull)
  100. {
  101. Tail.EmitWrite(ctx, null);
  102. }
  103. else
  104. {
  105. ctx.WriteNullCheckedTail(itemType, Tail, null);
  106. }
  107. // i++
  108. ctx.LoadValue(i);
  109. ctx.LoadValue(1);
  110. ctx.Add();
  111. ctx.StoreValue(i);
  112. // i < arr.Length
  113. ctx.MarkLabel(loopTest);
  114. ctx.LoadValue(i);
  115. ctx.LoadLength(arr, false);
  116. ctx.BranchIfLess(processItem, false);
  117. }
  118. #endif
  119. private bool AppendToCollection
  120. {
  121. get { return (options & OPTIONS_OverwriteList) == 0; }
  122. }
  123. private bool SupportNull { get { return (options & OPTIONS_SupportNull) != 0; } }
  124. #if !FEAT_IKVM
  125. public override void Write(object value, ProtoWriter dest)
  126. {
  127. IList arr = (IList)value;
  128. int len = arr.Count;
  129. SubItemToken token;
  130. bool writePacked = (options & OPTIONS_WritePacked) != 0;
  131. if (writePacked)
  132. {
  133. ProtoWriter.WriteFieldHeader(fieldNumber, WireType.String, dest);
  134. token = ProtoWriter.StartSubItem(value, dest);
  135. ProtoWriter.SetPackedField(fieldNumber, dest);
  136. }
  137. else
  138. {
  139. token = new SubItemToken(); // default
  140. }
  141. bool checkForNull = !SupportNull;
  142. for (int i = 0; i < len; i++)
  143. {
  144. object obj = arr[i];
  145. if (checkForNull && obj == null) { throw new NullReferenceException(); }
  146. Tail.Write(obj, dest);
  147. }
  148. if (writePacked)
  149. {
  150. ProtoWriter.EndSubItem(token, dest);
  151. }
  152. }
  153. public override object Read(object value, ProtoReader source)
  154. {
  155. int field = source.FieldNumber;
  156. BasicList list = new BasicList();
  157. if (packedWireType != WireType.None && source.WireType == WireType.String)
  158. {
  159. SubItemToken token = ProtoReader.StartSubItem(source);
  160. while (ProtoReader.HasSubValue(packedWireType, source))
  161. {
  162. list.Add(Tail.Read(null, source));
  163. }
  164. ProtoReader.EndSubItem(token, source);
  165. }
  166. else
  167. {
  168. do
  169. {
  170. list.Add(Tail.Read(null, source));
  171. } while (source.TryReadFieldHeader(field));
  172. }
  173. int oldLen = AppendToCollection ? ((value == null ? 0 : ((Array)value).Length)) : 0;
  174. Array result = Array.CreateInstance(itemType, oldLen + list.Count);
  175. if (oldLen != 0) ((Array)value).CopyTo(result, 0);
  176. list.CopyTo(result, oldLen);
  177. return result;
  178. }
  179. #endif
  180. #if FEAT_COMPILER
  181. protected override void EmitRead(ProtoBuf.Compiler.CompilerContext ctx, ProtoBuf.Compiler.Local valueFrom)
  182. {
  183. Type listType;
  184. #if NO_GENERICS
  185. listType = typeof(BasicList);
  186. #else
  187. listType = ctx.MapType(typeof(System.Collections.Generic.List<>)).MakeGenericType(itemType);
  188. #endif
  189. Type expected = ExpectedType;
  190. using (Compiler.Local oldArr = AppendToCollection ? ctx.GetLocalWithValue(expected, valueFrom) : null)
  191. using (Compiler.Local newArr = new Compiler.Local(ctx, expected))
  192. using (Compiler.Local list = new Compiler.Local(ctx, listType))
  193. {
  194. ctx.EmitCtor(listType);
  195. ctx.StoreValue(list);
  196. ListDecorator.EmitReadList(ctx, list, Tail, listType.GetMethod("Add"), packedWireType, false);
  197. // leave this "using" here, as it can share the "FieldNumber" local with EmitReadList
  198. using(Compiler.Local oldLen = AppendToCollection ? new ProtoBuf.Compiler.Local(ctx, ctx.MapType(typeof(int))) : null) {
  199. Type[] copyToArrayInt32Args = new Type[] { ctx.MapType(typeof(Array)), ctx.MapType(typeof(int)) };
  200. if (AppendToCollection)
  201. {
  202. ctx.LoadLength(oldArr, true);
  203. ctx.CopyValue();
  204. ctx.StoreValue(oldLen);
  205. ctx.LoadAddress(list, listType);
  206. ctx.LoadValue(listType.GetProperty("Count"));
  207. ctx.Add();
  208. ctx.CreateArray(itemType, null); // length is on the stack
  209. ctx.StoreValue(newArr);
  210. ctx.LoadValue(oldLen);
  211. Compiler.CodeLabel nothingToCopy = ctx.DefineLabel();
  212. ctx.BranchIfFalse(nothingToCopy, true);
  213. ctx.LoadValue(oldArr);
  214. ctx.LoadValue(newArr);
  215. ctx.LoadValue(0); // index in target
  216. ctx.EmitCall(expected.GetMethod("CopyTo", copyToArrayInt32Args));
  217. ctx.MarkLabel(nothingToCopy);
  218. ctx.LoadValue(list);
  219. ctx.LoadValue(newArr);
  220. ctx.LoadValue(oldLen);
  221. }
  222. else
  223. {
  224. ctx.LoadAddress(list, listType);
  225. ctx.LoadValue(listType.GetProperty("Count"));
  226. ctx.CreateArray(itemType, null);
  227. ctx.StoreValue(newArr);
  228. ctx.LoadAddress(list, listType);
  229. ctx.LoadValue(newArr);
  230. ctx.LoadValue(0);
  231. }
  232. copyToArrayInt32Args[0] = expected; // // prefer: CopyTo(T[], int)
  233. MethodInfo copyTo = listType.GetMethod("CopyTo", copyToArrayInt32Args);
  234. if (copyTo == null)
  235. { // fallback: CopyTo(Array, int)
  236. copyToArrayInt32Args[1] = ctx.MapType(typeof(Array));
  237. copyTo = listType.GetMethod("CopyTo", copyToArrayInt32Args);
  238. }
  239. ctx.EmitCall(copyTo);
  240. }
  241. ctx.LoadValue(newArr);
  242. }
  243. }
  244. #endif
  245. }
  246. }
  247. #endif