You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

386 lines
13 KiB

  1. #region License
  2. // Copyright (c) 2007 James Newton-King
  3. //
  4. // Permission is hereby granted, free of charge, to any person
  5. // obtaining a copy of this software and associated documentation
  6. // files (the "Software"), to deal in the Software without
  7. // restriction, including without limitation the rights to use,
  8. // copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. // copies of the Software, and to permit persons to whom the
  10. // Software is furnished to do so, subject to the following
  11. // conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be
  14. // included in all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  17. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  18. // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  19. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  20. // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  21. // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  22. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  23. // OTHER DEALINGS IN THE SOFTWARE.
  24. #endregion
  25. using System;
  26. using System.Collections.Generic;
  27. using System.Collections.ObjectModel;
  28. using System.Reflection;
  29. using System.Text;
  30. using System.Collections;
  31. using System.Diagnostics;
  32. #if NET20
  33. using Newtonsoft.Json.Utilities.LinqBridge;
  34. #else
  35. using System.Linq;
  36. #endif
  37. using System.Globalization;
  38. #if NETSTD
  39. using System.Runtime.CompilerServices;
  40. #endif
  41. using Newtonsoft.Json.Serialization;
  42. namespace Newtonsoft.Json.Utilities
  43. {
  44. internal static class CollectionUtils
  45. {
  46. /// <summary>
  47. /// Determines whether the collection is <c>null</c> or empty.
  48. /// </summary>
  49. /// <param name="collection">The collection.</param>
  50. /// <returns>
  51. /// <c>true</c> if the collection is <c>null</c> or empty; otherwise, <c>false</c>.
  52. /// </returns>
  53. public static bool IsNullOrEmpty<T>(ICollection<T> collection)
  54. {
  55. if (collection != null)
  56. {
  57. return (collection.Count == 0);
  58. }
  59. return true;
  60. }
  61. /// <summary>
  62. /// Adds the elements of the specified collection to the specified generic <see cref="IList{T}"/>.
  63. /// </summary>
  64. /// <param name="initial">The list to add to.</param>
  65. /// <param name="collection">The collection of elements to add.</param>
  66. public static void AddRange<T>(this IList<T> initial, IEnumerable<T> collection)
  67. {
  68. if (initial == null)
  69. {
  70. throw new ArgumentNullException(nameof(initial));
  71. }
  72. if (collection == null)
  73. {
  74. return;
  75. }
  76. foreach (T value in collection)
  77. {
  78. initial.Add(value);
  79. }
  80. }
  81. #if NET20
  82. public static void AddRange<T>(this IList<T> initial, IEnumerable collection)
  83. {
  84. ValidationUtils.ArgumentNotNull(initial, nameof(initial));
  85. // because earlier versions of .NET didn't support covariant generics
  86. initial.AddRange(collection.Cast<T>());
  87. }
  88. #endif
  89. public static bool IsDictionaryType(Type type)
  90. {
  91. ValidationUtils.ArgumentNotNull(type, nameof(type));
  92. if (typeof(IDictionary).IsAssignableFrom(type))
  93. {
  94. return true;
  95. }
  96. if (ReflectionUtils.ImplementsGenericDefinition(type, typeof(IDictionary<,>)))
  97. {
  98. return true;
  99. }
  100. #if NETSTD
  101. if (ReflectionUtils.ImplementsGenericDefinition(type, typeof(IReadOnlyDictionary<,>)))
  102. {
  103. return true;
  104. }
  105. #endif
  106. return false;
  107. }
  108. public static ConstructorInfo ResolveEnumerableCollectionConstructor(Type collectionType, Type collectionItemType)
  109. {
  110. Type genericConstructorArgument = typeof(IList<>).MakeGenericType(collectionItemType);
  111. return ResolveEnumerableCollectionConstructor(collectionType, collectionItemType, genericConstructorArgument);
  112. }
  113. public static ConstructorInfo ResolveEnumerableCollectionConstructor(Type collectionType, Type collectionItemType, Type constructorArgumentType)
  114. {
  115. Type genericEnumerable = typeof(IEnumerable<>).MakeGenericType(collectionItemType);
  116. ConstructorInfo match = null;
  117. foreach (ConstructorInfo constructor in collectionType.GetConstructors(BindingFlags.Public | BindingFlags.Instance))
  118. {
  119. IList<ParameterInfo> parameters = constructor.GetParameters();
  120. if (parameters.Count == 1)
  121. {
  122. Type parameterType = parameters[0].ParameterType;
  123. if (genericEnumerable == parameterType)
  124. {
  125. // exact match
  126. match = constructor;
  127. break;
  128. }
  129. // in case we can't find an exact match, use first inexact
  130. if (match == null)
  131. {
  132. if (parameterType.IsAssignableFrom(constructorArgumentType))
  133. {
  134. match = constructor;
  135. }
  136. }
  137. }
  138. }
  139. return match;
  140. }
  141. public static bool AddDistinct<T>(this IList<T> list, T value)
  142. {
  143. return list.AddDistinct(value, EqualityComparer<T>.Default);
  144. }
  145. public static bool AddDistinct<T>(this IList<T> list, T value, IEqualityComparer<T> comparer)
  146. {
  147. if (list.ContainsValue(value, comparer))
  148. {
  149. return false;
  150. }
  151. list.Add(value);
  152. return true;
  153. }
  154. // this is here because LINQ Bridge doesn't support Contains with IEqualityComparer<T>
  155. public static bool ContainsValue<TSource>(this IEnumerable<TSource> source, TSource value, IEqualityComparer<TSource> comparer)
  156. {
  157. if (comparer == null)
  158. {
  159. comparer = EqualityComparer<TSource>.Default;
  160. }
  161. if (source == null)
  162. {
  163. throw new ArgumentNullException(nameof(source));
  164. }
  165. foreach (TSource local in source)
  166. {
  167. if (comparer.Equals(local, value))
  168. {
  169. return true;
  170. }
  171. }
  172. return false;
  173. }
  174. public static bool AddRangeDistinct<T>(this IList<T> list, IEnumerable<T> values, IEqualityComparer<T> comparer)
  175. {
  176. bool allAdded = true;
  177. foreach (T value in values)
  178. {
  179. if (!list.AddDistinct(value, comparer))
  180. {
  181. allAdded = false;
  182. }
  183. }
  184. return allAdded;
  185. }
  186. public static int IndexOf<T>(this IEnumerable<T> collection, Func<T, bool> predicate)
  187. {
  188. int index = 0;
  189. foreach (T value in collection)
  190. {
  191. if (predicate(value))
  192. {
  193. return index;
  194. }
  195. index++;
  196. }
  197. return -1;
  198. }
  199. public static bool Contains<T>(this List<T> list, T value, IEqualityComparer comparer)
  200. {
  201. for (int i = 0; i < list.Count; i++)
  202. {
  203. if (comparer.Equals(value, list[i]))
  204. {
  205. return true;
  206. }
  207. }
  208. return false;
  209. }
  210. public static int IndexOfReference<T>(this List<T> list, T item)
  211. {
  212. for (int i = 0; i < list.Count; i++)
  213. {
  214. if (ReferenceEquals(item, list[i]))
  215. {
  216. return i;
  217. }
  218. }
  219. return -1;
  220. }
  221. // faster reverse in .NET Framework with value types - https://github.com/JamesNK/Newtonsoft.Json/issues/1430
  222. public static void FastReverse<T>(this List<T> list)
  223. {
  224. int i = 0;
  225. int j = list.Count - 1;
  226. while (i < j)
  227. {
  228. T temp = list[i];
  229. list[i] = list[j];
  230. list[j] = temp;
  231. i++;
  232. j--;
  233. }
  234. }
  235. private static IList<int> GetDimensions(IList values, int dimensionsCount)
  236. {
  237. IList<int> dimensions = new List<int>();
  238. IList currentArray = values;
  239. while (true)
  240. {
  241. dimensions.Add(currentArray.Count);
  242. // don't keep calculating dimensions for arrays inside the value array
  243. if (dimensions.Count == dimensionsCount)
  244. {
  245. break;
  246. }
  247. if (currentArray.Count == 0)
  248. {
  249. break;
  250. }
  251. object v = currentArray[0];
  252. if (v is IList list)
  253. {
  254. currentArray = list;
  255. }
  256. else
  257. {
  258. break;
  259. }
  260. }
  261. return dimensions;
  262. }
  263. private static void CopyFromJaggedToMultidimensionalArray(IList values, Array multidimensionalArray, int[] indices)
  264. {
  265. int dimension = indices.Length;
  266. if (dimension == multidimensionalArray.Rank)
  267. {
  268. multidimensionalArray.SetValue(JaggedArrayGetValue(values, indices), indices);
  269. return;
  270. }
  271. int dimensionLength = multidimensionalArray.GetLength(dimension);
  272. IList list = (IList)JaggedArrayGetValue(values, indices);
  273. int currentValuesLength = list.Count;
  274. if (currentValuesLength != dimensionLength)
  275. {
  276. throw new Exception("Cannot deserialize non-cubical array as multidimensional array.");
  277. }
  278. int[] newIndices = new int[dimension + 1];
  279. for (int i = 0; i < dimension; i++)
  280. {
  281. newIndices[i] = indices[i];
  282. }
  283. for (int i = 0; i < multidimensionalArray.GetLength(dimension); i++)
  284. {
  285. newIndices[dimension] = i;
  286. CopyFromJaggedToMultidimensionalArray(values, multidimensionalArray, newIndices);
  287. }
  288. }
  289. private static object JaggedArrayGetValue(IList values, int[] indices)
  290. {
  291. IList currentList = values;
  292. for (int i = 0; i < indices.Length; i++)
  293. {
  294. int index = indices[i];
  295. if (i == indices.Length - 1)
  296. {
  297. return currentList[index];
  298. }
  299. else
  300. {
  301. currentList = (IList)currentList[index];
  302. }
  303. }
  304. return currentList;
  305. }
  306. public static Array ToMultidimensionalArray(IList values, Type type, int rank)
  307. {
  308. IList<int> dimensions = GetDimensions(values, rank);
  309. while (dimensions.Count < rank)
  310. {
  311. dimensions.Add(0);
  312. }
  313. Array multidimensionalArray = Array.CreateInstance(type, dimensions.ToArray());
  314. CopyFromJaggedToMultidimensionalArray(values, multidimensionalArray, ArrayEmpty<int>());
  315. return multidimensionalArray;
  316. }
  317. // 4.6 has Array.Empty<T> to return a cached empty array. Lacking that in other
  318. // frameworks, Enumerable.Empty<T> happens to be implemented as a cached empty
  319. // array in all versions (in .NET Core the same instance as Array.Empty<T>).
  320. // This includes the internal Linq bridge for 2.0.
  321. // Since this method is simple and only 11 bytes long in a release build it's
  322. // pretty much guaranteed to be inlined, giving us fast access of that cached
  323. // array. With 4.5 and up we use AggressiveInlining just to be sure, so it's
  324. // effectively the same as calling Array.Empty<T> even when not available.
  325. #if NETSTD
  326. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  327. #endif
  328. public static T[] ArrayEmpty<T>()
  329. {
  330. T[] array = Enumerable.Empty<T>() as T[];
  331. Debug.Assert(array != null);
  332. // Defensively guard against a version of Linq where Enumerable.Empty<T> doesn't
  333. // return an array, but throw in debug versions so a better strategy can be
  334. // used if that ever happens.
  335. return array ?? new T[0];
  336. }
  337. }
  338. }