PageRenderTime 53ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/mcs/class/Npgsql/NpgsqlTypes/ArrayHandling.cs

https://bitbucket.org/danipen/mono
C# | 457 lines | 364 code | 17 blank | 76 comment | 17 complexity | b0554c01d9cead9f2b04384e9f7cadaa MD5 | raw file
Possible License(s): Unlicense, Apache-2.0, LGPL-2.0, MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0
  1. // NpgsqlTypes\ArrayHandling.cs
  2. //
  3. // Author:
  4. // Jon Hanna. (jon@hackcraft.net)
  5. //
  6. // Copyright (C) 2007-2008 The Npgsql Development Team
  7. // npgsql-general@gborg.postgresql.org
  8. // http://gborg.postgresql.org/project/npgsql/projdisplay.php
  9. //
  10. // Permission to use, copy, modify, and distribute this software and its
  11. // documentation for any purpose, without fee, and without a written
  12. // agreement is hereby granted, provided that the above copyright notice
  13. // and this paragraph and the following two paragraphs appear in all copies.
  14. //
  15. // IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY
  16. // FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
  17. // INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
  18. // DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF
  19. // THE POSSIBILITY OF SUCH DAMAGE.
  20. //
  21. // THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES,
  22. // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
  23. // AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
  24. // ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS
  25. // TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
  26. using System;
  27. using System.Collections;
  28. using System.Collections.Generic;
  29. using System.Text;
  30. namespace NpgsqlTypes
  31. {
  32. /// <summary>
  33. /// Handles serialisation of .NET array or IEnumeration to pg format.
  34. /// Arrays of arrays, enumerations of enumerations, arrays of enumerations etc.
  35. /// are treated as multi-dimensional arrays (in much the same manner as an array of arrays
  36. /// is used to emulate multi-dimensional arrays in languages that lack native support for them).
  37. /// If such an enumeration of enumerations is "jagged" (as opposed to rectangular, cuboid,
  38. /// hypercuboid, hyperhypercuboid, etc) then this class will "correctly" serialise it, but pg
  39. /// will raise an error as it doesn't allow jagged arrays.
  40. /// </summary>
  41. internal class ArrayNativeToBackendTypeConverter
  42. {
  43. private readonly NpgsqlNativeTypeInfo _elementConverter;
  44. /// <summary>
  45. /// Create an ArrayNativeToBackendTypeConverter with the element converter passed
  46. /// </summary>
  47. /// <param name="elementConverter">The <see cref="NpgsqlNativeTypeInfo"/> that would be used to serialise the element type.</param>
  48. public ArrayNativeToBackendTypeConverter(NpgsqlNativeTypeInfo elementConverter)
  49. {
  50. _elementConverter = elementConverter;
  51. }
  52. /// <summary>
  53. /// Serialise the enumeration or array.
  54. /// </summary>
  55. public string FromArray(NpgsqlNativeTypeInfo TypeInfo, object NativeData)
  56. {
  57. //just prepend "array" and then pass to WriteItem.
  58. StringBuilder sb = new StringBuilder("array");
  59. if (WriteItem(TypeInfo, NativeData, sb))
  60. {
  61. return sb.ToString();
  62. }
  63. else
  64. {
  65. return "'{}'";
  66. }
  67. }
  68. private bool WriteItem(NpgsqlNativeTypeInfo TypeInfo, object item, StringBuilder sb)
  69. {
  70. //item could be:
  71. //an Ienumerable - in which case we call WriteEnumeration
  72. //an element - in which case we call the NpgsqlNativeTypeInfo for that type to serialise it.
  73. //an array - in which case we call WriteArray,
  74. if (item is IEnumerable)
  75. {
  76. return WriteEnumeration(TypeInfo, item as IEnumerable, sb);
  77. }
  78. else if (item is Array)
  79. {
  80. return WriteArray(TypeInfo, item as Array, sb);
  81. }
  82. else
  83. {
  84. sb.Append(_elementConverter.ConvertToBackend(item, false));
  85. return true;
  86. }
  87. }
  88. private bool WriteArray(NpgsqlNativeTypeInfo TypeInfo, Array ar, StringBuilder sb)
  89. {
  90. bool writtenSomething = false;
  91. //we need to know the size of each dimension.
  92. int c = ar.Rank;
  93. List<int> lengths = new List<int>(c);
  94. do
  95. {
  96. lengths.Add(ar.GetLength(--c));
  97. }
  98. while (c != 0);
  99. //c is now zero. Might as well reuse it!
  100. foreach (object item in ar)
  101. {
  102. //to work out how many [ characters we need we need to work where we are compared to the dimensions.
  103. //Say we are at position 24 in a 3 * 4 * 5 array.
  104. //We're at the end of a row as 24 % 3 == 0 so write one [ for that.
  105. //We're at the end of a square as 24 % (3 * 4) == 24 % (12) == 0 so write one [ for that.
  106. //We're not at the end of a cube as 24 % (3 * 4 * 5) == 24 % (30) != 0, so we're finished for that pass.
  107. int curlength = 1;
  108. foreach (int lengthTest in lengths)
  109. {
  110. if (c%(curlength *= lengthTest) == 0)
  111. {
  112. sb.Append('[');
  113. }
  114. else
  115. {
  116. break;
  117. }
  118. }
  119. //Write whatever the element is.
  120. writtenSomething |= WriteItem(TypeInfo, item, sb);
  121. ++c; //up our counter for knowing when to write [ and ]
  122. //same logic as above for writing [ this time writing ]
  123. curlength = 1;
  124. foreach (int lengthTest in lengths)
  125. {
  126. if (c%(curlength *= lengthTest) == 0)
  127. {
  128. sb.Append(']');
  129. }
  130. else
  131. {
  132. break;
  133. }
  134. }
  135. //comma between each item.
  136. sb.Append(',');
  137. }
  138. if (writtenSomething)
  139. {
  140. //last comma was one too many.
  141. sb.Remove(sb.Length - 1, 1);
  142. }
  143. return writtenSomething;
  144. }
  145. private bool WriteEnumeration(NpgsqlNativeTypeInfo TypeInfo, IEnumerable col, StringBuilder sb)
  146. {
  147. bool writtenSomething = false;
  148. sb.Append('[');
  149. //write each item with a comma between them.
  150. foreach (object item in col)
  151. {
  152. writtenSomething |= WriteItem(TypeInfo, item, sb);
  153. sb.Append(',');
  154. }
  155. if (writtenSomething)
  156. {
  157. //last comma was one too many. Replace it with the final }
  158. sb[sb.Length - 1] = ']';
  159. }
  160. return writtenSomething;
  161. }
  162. }
  163. /// <summary>
  164. /// Handles parsing of pg arrays into .NET arrays.
  165. /// </summary>
  166. internal class ArrayBackendToNativeTypeConverter
  167. {
  168. #region Helper Classes
  169. /// <summary>
  170. /// Takes a string representation of a pg 1-dimensional array
  171. /// (or a 1-dimensional row within an n-dimensional array)
  172. /// and allows enumeration of the string represenations of each items.
  173. /// </summary>
  174. private static IEnumerable<string> TokenEnumeration(string source)
  175. {
  176. bool inQuoted = false;
  177. StringBuilder sb = new StringBuilder(source.Length);
  178. //We start of not in a quoted section, with an empty StringBuilder.
  179. //We iterate through each character. Generally we add that character
  180. //to the string builder, but in the case of special characters we
  181. //have different handling. When we reach a comma that isn't in a quoted
  182. //section we yield return the string we have built and then clear it
  183. //to continue with the next.
  184. for (int idx = 0; idx < source.Length; ++idx)
  185. {
  186. char c = source[idx];
  187. switch (c)
  188. {
  189. case '"': //entering of leaving a quoted string
  190. inQuoted = !inQuoted;
  191. break;
  192. case ',': //ending this item, unless we're in a quoted string.
  193. if (inQuoted)
  194. {
  195. sb.Append(',');
  196. }
  197. else
  198. {
  199. yield return sb.ToString();
  200. sb = new StringBuilder(source.Length - idx);
  201. }
  202. break;
  203. case '\\': //next char is an escaped character, grab it, ignore the \ we are on now.
  204. sb.Append(source[++idx]);
  205. break;
  206. default:
  207. sb.Append(c);
  208. break;
  209. }
  210. }
  211. yield return sb.ToString();
  212. }
  213. /// <summary>
  214. /// Takes a string representation of a pg n-dimensional array
  215. /// and allows enumeration of the string represenations of the next
  216. /// lower level of rows (which in turn can be taken as (n-1)-dimensional arrays.
  217. /// </summary>
  218. private static IEnumerable<string> ArrayChunkEnumeration(string source)
  219. {
  220. //Iterate through the string counting { and } characters, mindful of the effects of
  221. //" and \ on how the string is to be interpretted.
  222. //Increment a count of braces at each { and decrement at each }
  223. //If we hit a comma and the brace-count is zero then we have found an item. yield it
  224. //and then we move on to the next.
  225. bool inQuoted = false;
  226. int braceCount = 0;
  227. int startIdx = 0;
  228. int len = source.Length;
  229. for (int idx = 0; idx != len; ++idx)
  230. {
  231. switch (source[idx])
  232. {
  233. case '"': //beginning or ending a quoted chunk.
  234. inQuoted = !inQuoted;
  235. break;
  236. case ',':
  237. if (braceCount == 0) //if bracecount is zero we've done our chunk
  238. {
  239. yield return source.Substring(startIdx, idx - startIdx);
  240. startIdx = idx + 1;
  241. }
  242. break;
  243. case '\\': //next character is escaped. Skip it.
  244. ++idx;
  245. break;
  246. case '{': //up the brace count if this isn't in a quoted string
  247. if (!inQuoted)
  248. {
  249. ++braceCount;
  250. }
  251. break;
  252. case '}': //lower the brace count if this isn't in a quoted string
  253. if (!inQuoted)
  254. {
  255. --braceCount;
  256. }
  257. break;
  258. }
  259. }
  260. yield return source.Substring(startIdx);
  261. }
  262. /// <summary>
  263. /// Takes an array of ints and treats them like the limits of a set of counters.
  264. /// Retains a matching set of ints that is set to all zeros on the first ++
  265. /// On a ++ it increments the "right-most" int. If that int reaches it's
  266. /// limit it is set to zero and the one before it is incremented, and so on.
  267. ///
  268. /// Making this a more general purpose class is pretty straight-forward, but we'll just put what we need here.
  269. /// </summary>
  270. private class IntSetIterator
  271. {
  272. private readonly int[] _bounds;
  273. private readonly int[] _current;
  274. public IntSetIterator(int[] bounds)
  275. {
  276. _current = new int[(_bounds = bounds).Length];
  277. _current[_current.Length - 1] = -1; //zero after first ++
  278. }
  279. public IntSetIterator(List<int> bounds)
  280. : this(bounds.ToArray())
  281. {
  282. }
  283. public int[] Bounds
  284. {
  285. get { return _bounds; }
  286. }
  287. public static implicit operator int[](IntSetIterator isi)
  288. {
  289. return isi._current;
  290. }
  291. public static IntSetIterator operator ++(IntSetIterator isi)
  292. {
  293. for (int idx = isi._current.Length - 1; ++isi._current[idx] == isi._bounds[idx]; --idx)
  294. {
  295. isi._current[idx] = 0;
  296. }
  297. return isi;
  298. }
  299. }
  300. /// <summary>
  301. /// Takes an ArrayList which may be an ArrayList of ArrayLists, an ArrayList of ArrayLists of ArrayLists
  302. /// and so on and enumerates the items that aren't ArrayLists (the leaf nodes if we think of the ArrayList
  303. /// passed as a tree). Simply uses the ArrayLists' own IEnumerators to get that of the next,
  304. /// pushing them onto a stack until we hit something that isn't an ArrayList.
  305. /// <param name="list"><see cref="System.Collections.ArrayList">ArrayList</see> to enumerate</param>
  306. /// <returns><see cref="System.Collections.IEnumerable">IEnumerable</see></returns>
  307. /// </summary>
  308. private static IEnumerable RecursiveArrayListEnumeration(ArrayList list)
  309. {
  310. //This sort of recursive enumeration used to be really fiddly. .NET2.0's yield makes it trivial. Hurray!
  311. Stack<IEnumerator> stk = new Stack<IEnumerator>();
  312. stk.Push(list.GetEnumerator());
  313. while (stk.Count != 0)
  314. {
  315. IEnumerator ienum = stk.Peek();
  316. while (ienum.MoveNext())
  317. {
  318. object obj = ienum.Current;
  319. if (obj is ArrayList)
  320. {
  321. stk.Push(ienum = (obj as ArrayList).GetEnumerator());
  322. }
  323. else
  324. {
  325. yield return obj;
  326. }
  327. }
  328. stk.Pop();
  329. }
  330. }
  331. #endregion
  332. private readonly NpgsqlBackendTypeInfo _elementConverter;
  333. /// <summary>
  334. /// Create a new ArrayBackendToNativeTypeConverter
  335. /// </summary>
  336. /// <param name="elementConverter"><see cref="NpgsqlBackendTypeInfo"/> for the element type.</param>
  337. public ArrayBackendToNativeTypeConverter(NpgsqlBackendTypeInfo elementConverter)
  338. {
  339. _elementConverter = elementConverter;
  340. }
  341. /// <summary>
  342. /// Creates an array from pg representation.
  343. /// </summary>
  344. public object ToArray(NpgsqlBackendTypeInfo TypeInfo, String BackendData, Int16 TypeSize, Int32 TypeModifier)
  345. {
  346. //first create an arraylist, then convert it to an array.
  347. return ToArray(ToArrayList(TypeInfo, BackendData, TypeSize, TypeModifier), _elementConverter.Type);
  348. }
  349. /// <summary>
  350. /// Creates an array list from pg represenation of an array.
  351. /// Multidimensional arrays are treated as ArrayLists of ArrayLists
  352. /// </summary>
  353. private ArrayList ToArrayList(NpgsqlBackendTypeInfo TypeInfo, String BackendData, Int16 elementTypeSize,
  354. Int32 elementTypeModifier)
  355. {
  356. ArrayList list = new ArrayList();
  357. //remove the braces on either side and work on what they contain.
  358. string stripBraces = BackendData.Trim().Substring(1, BackendData.Length - 2).Trim();
  359. if (stripBraces.Length == 0)
  360. {
  361. return list;
  362. }
  363. if (stripBraces[0] == '{')
  364. //there are still braces so we have an n-dimension array. Recursively build an ArrayList of ArrayLists
  365. {
  366. foreach (string arrayChunk in ArrayChunkEnumeration(stripBraces))
  367. {
  368. list.Add(ToArrayList(TypeInfo, arrayChunk, elementTypeSize, elementTypeModifier));
  369. }
  370. }
  371. else
  372. //We're either dealing with a 1-dimension array or treating a row of an n-dimension array. In either case parse the elements and put them in our ArrayList
  373. {
  374. foreach (string token in TokenEnumeration(stripBraces))
  375. {
  376. //Use the NpgsqlBackendTypeInfo for the element type to obtain each element.
  377. list.Add(_elementConverter.ConvertToNative(token, elementTypeSize, elementTypeModifier));
  378. }
  379. }
  380. return list;
  381. }
  382. /// <summary>
  383. /// Creates an n-dimensional array from an ArrayList of ArrayLists or
  384. /// a 1-dimensional array from something else.
  385. /// </summary>
  386. /// <param name="list"><see cref="ArrayList"/> to convert</param>
  387. /// <returns><see cref="Array"/> produced.</returns>
  388. private static Array ToArray(ArrayList list, Type elementType)
  389. {
  390. //First we need to work out how many dimensions we're dealing with and what size each one is.
  391. //We examine the arraylist we were passed for it's count. Then we recursively do that to
  392. //it's first item until we hit an item that isn't an ArrayList.
  393. //We then find out the type of that item.
  394. List<int> dimensions = new List<int>();
  395. object item = list;
  396. for (ArrayList itemAsList = item as ArrayList; item is ArrayList; itemAsList = (item = itemAsList[0]) as ArrayList)
  397. {
  398. if (itemAsList != null)
  399. {
  400. int dimension = itemAsList.Count;
  401. if (dimension == 0)
  402. {
  403. return Array.CreateInstance(elementType, 0);
  404. }
  405. dimensions.Add(dimension);
  406. }
  407. }
  408. if (dimensions.Count == 1) //1-dimension array so we can just use ArrayList.ToArray()
  409. {
  410. return list.ToArray(elementType);
  411. }
  412. //Get an IntSetIterator to hold the position we're setting.
  413. IntSetIterator isi = new IntSetIterator(dimensions);
  414. //Create our array.
  415. Array ret = Array.CreateInstance(elementType, isi.Bounds);
  416. //Get each item and put it in the array at the appropriate place.
  417. foreach (object val in RecursiveArrayListEnumeration(list))
  418. {
  419. ret.SetValue(val, ++isi);
  420. }
  421. return ret;
  422. }
  423. }
  424. }