#region License // Copyright (c) 2007 James Newton-King // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. #endregion using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Reflection; using System.Text; using System.Collections; using System.Linq; using System.Globalization; namespace Newtonsoft.Json.Utilities { internal static class CollectionUtils { public static IEnumerable CastValid(this IEnumerable enumerable) { ValidationUtils.ArgumentNotNull(enumerable, "enumerable"); return enumerable.Cast().Where(o => o is T).Cast(); } public static List CreateList(params T[] values) { return new List(values); } /// /// Determines whether the collection is null or empty. /// /// The collection. /// /// true if the collection is null or empty; otherwise, false. /// public static bool IsNullOrEmpty(ICollection collection) { if (collection != null) { return (collection.Count == 0); } return true; } /// /// Determines whether the collection is null or empty. /// /// The collection. /// /// true if the collection is null or empty; otherwise, false. /// public static bool IsNullOrEmpty(ICollection collection) { if (collection != null) { return (collection.Count == 0); } return true; } /// /// Determines whether the collection is null, empty or its contents are uninitialized values. /// /// The list. /// /// true if the collection is null or empty or its contents are uninitialized values; otherwise, false. /// public static bool IsNullOrEmptyOrDefault(IList list) { if (IsNullOrEmpty(list)) return true; return ReflectionUtils.ItemsUnitializedValue(list); } /// /// Makes a slice of the specified list in between the start and end indexes. /// /// The list. /// The start index. /// The end index. /// A slice of the list. public static IList Slice(IList list, int? start, int? end) { return Slice(list, start, end, null); } /// /// Makes a slice of the specified list in between the start and end indexes, /// getting every so many items based upon the step. /// /// The list. /// The start index. /// The end index. /// The step. /// A slice of the list. public static IList Slice(IList list, int? start, int? end, int? step) { if (list == null) throw new ArgumentNullException("list"); if (step == 0) throw new ArgumentException("Step cannot be zero.", "step"); List slicedList = new List(); // nothing to slice if (list.Count == 0) return slicedList; // set defaults for null arguments int s = step ?? 1; int startIndex = start ?? 0; int endIndex = end ?? list.Count; // start from the end of the list if start is negitive startIndex = (startIndex < 0) ? list.Count + startIndex : startIndex; // end from the start of the list if end is negitive endIndex = (endIndex < 0) ? list.Count + endIndex : endIndex; // ensure indexes keep within collection bounds startIndex = Math.Max(startIndex, 0); endIndex = Math.Min(endIndex, list.Count - 1); // loop between start and end indexes, incrementing by the step for (int i = startIndex; i < endIndex; i += s) { slicedList.Add(list[i]); } return slicedList; } /// /// Group the collection using a function which returns the key. /// /// The source collection to group. /// The key selector. /// A Dictionary with each key relating to a list of objects in a list grouped under it. public static Dictionary> GroupBy(ICollection source, Func keySelector) { if (keySelector == null) throw new ArgumentNullException("keySelector"); Dictionary> groupedValues = new Dictionary>(); foreach (V value in source) { // using delegate to get the value's key K key = keySelector(value); List groupedValueList; // add a list for grouped values if the key is not already in Dictionary if (!groupedValues.TryGetValue(key, out groupedValueList)) { groupedValueList = new List(); groupedValues.Add(key, groupedValueList); } groupedValueList.Add(value); } return groupedValues; } /// /// Adds the elements of the specified collection to the specified generic IList. /// /// The list to add to. /// The collection of elements to add. public static void AddRange(this IList initial, IEnumerable collection) { if (initial == null) throw new ArgumentNullException("initial"); if (collection == null) return; foreach (T value in collection) { initial.Add(value); } } public static void AddRange(this IList initial, IEnumerable collection) { ValidationUtils.ArgumentNotNull(initial, "initial"); ListWrapper wrapper = new ListWrapper(initial); wrapper.AddRange(collection.Cast()); } public static List Distinct(List collection) { List distinctList = new List(); foreach (T value in collection) { if (!distinctList.Contains(value)) distinctList.Add(value); } return distinctList; } public static List> Flatten(params IList[] lists) { List> flattened = new List>(); Dictionary currentList = new Dictionary(); Recurse(new List>(lists), 0, currentList, flattened); return flattened; } private static void Recurse(IList> global, int current, Dictionary currentSet, List> flattenedResult) { IList currentArray = global[current]; for (int i = 0; i < currentArray.Count; i++) { currentSet[current] = currentArray[i]; if (current == global.Count - 1) { List items = new List(); for (int k = 0; k < currentSet.Count; k++) { items.Add(currentSet[k]); } flattenedResult.Add(items); } else { Recurse(global, current + 1, currentSet, flattenedResult); } } } public static List CreateList(ICollection collection) { if (collection == null) throw new ArgumentNullException("collection"); T[] array = new T[collection.Count]; collection.CopyTo(array, 0); return new List(array); } public static bool ListEquals(IList a, IList b) { if (a == null || b == null) return (a == null && b == null); if (a.Count != b.Count) return false; EqualityComparer comparer = EqualityComparer.Default; for (int i = 0; i < a.Count; i++) { if (!comparer.Equals(a[i], b[i])) return false; } return true; } #region GetSingleItem public static bool TryGetSingleItem(IList list, out T value) { return TryGetSingleItem(list, false, out value); } public static bool TryGetSingleItem(IList list, bool returnDefaultIfEmpty, out T value) { return MiscellaneousUtils.TryAction(delegate { return GetSingleItem(list, returnDefaultIfEmpty); }, out value); } public static T GetSingleItem(IList list) { return GetSingleItem(list, false); } public static T GetSingleItem(IList list, bool returnDefaultIfEmpty) { if (list.Count == 1) return list[0]; else if (returnDefaultIfEmpty && list.Count == 0) return default(T); else throw new Exception("Expected single {0} in list but got {1}.".FormatWith(CultureInfo.InvariantCulture, typeof(T), list.Count)); } #endregion public static IList Minus(IList list, IList minus) { ValidationUtils.ArgumentNotNull(list, "list"); List result = new List(list.Count); foreach (T t in list) { if (minus == null || !minus.Contains(t)) result.Add(t); } return result; } public static IList CreateGenericList(Type listType) { ValidationUtils.ArgumentNotNull(listType, "listType"); return (IList)ReflectionUtils.CreateGeneric(typeof(List<>), listType); } public static IDictionary CreateGenericDictionary(Type keyType, Type valueType) { ValidationUtils.ArgumentNotNull(keyType, "keyType"); ValidationUtils.ArgumentNotNull(valueType, "valueType"); return (IDictionary)ReflectionUtils.CreateGeneric(typeof(Dictionary<,>), keyType, valueType); } public static bool IsListType(Type type) { ValidationUtils.ArgumentNotNull(type, "type"); if (type.IsArray) return true; if (typeof(IList).IsAssignableFrom(type)) return true; if (ReflectionUtils.ImplementsGenericDefinition(type, typeof(IList<>))) return true; return false; } public static bool IsCollectionType(Type type) { ValidationUtils.ArgumentNotNull(type, "type"); if (type.IsArray) return true; if (typeof(ICollection).IsAssignableFrom(type)) return true; if (ReflectionUtils.ImplementsGenericDefinition(type, typeof(ICollection<>))) return true; return false; } public static bool IsDictionaryType(Type type) { ValidationUtils.ArgumentNotNull(type, "type"); if (typeof(IDictionary).IsAssignableFrom(type)) return true; if (ReflectionUtils.ImplementsGenericDefinition(type, typeof (IDictionary<,>))) return true; return false; } public static IWrappedCollection CreateCollectionWrapper(object list) { ValidationUtils.ArgumentNotNull(list, "list"); Type collectionDefinition; if (ReflectionUtils.ImplementsGenericDefinition(list.GetType(), typeof(ICollection<>), out collectionDefinition)) { Type collectionItemType = ReflectionUtils.GetCollectionItemType(collectionDefinition); // Activator.CreateInstance throws AmbiguousMatchException. Manually invoke constructor Func, object> instanceCreator = (t, a) => { ConstructorInfo c = t.GetConstructor(new[] { collectionDefinition }); return c.Invoke(new[] { list }); }; return (IWrappedCollection)ReflectionUtils.CreateGeneric(typeof(CollectionWrapper<>), new[] { collectionItemType }, instanceCreator, list); } else if (list is IList) { return new CollectionWrapper((IList)list); } else { throw new Exception("Can not create ListWrapper for type {0}.".FormatWith(CultureInfo.InvariantCulture, list.GetType())); } } public static IWrappedList CreateListWrapper(object list) { ValidationUtils.ArgumentNotNull(list, "list"); Type listDefinition; if (ReflectionUtils.ImplementsGenericDefinition(list.GetType(), typeof(IList<>), out listDefinition)) { Type collectionItemType = ReflectionUtils.GetCollectionItemType(listDefinition); // Activator.CreateInstance throws AmbiguousMatchException. Manually invoke constructor Func, object> instanceCreator = (t, a) => { ConstructorInfo c = t.GetConstructor(new[] {listDefinition}); return c.Invoke(new[] { list }); }; return (IWrappedList)ReflectionUtils.CreateGeneric(typeof(ListWrapper<>), new[] { collectionItemType }, instanceCreator, list); } else if (list is IList) { return new ListWrapper((IList)list); } else { throw new Exception("Can not create ListWrapper for type {0}.".FormatWith(CultureInfo.InvariantCulture, list.GetType())); } } public static IWrappedDictionary CreateDictionaryWrapper(object dictionary) { ValidationUtils.ArgumentNotNull(dictionary, "dictionary"); Type dictionaryDefinition; if (ReflectionUtils.ImplementsGenericDefinition(dictionary.GetType(), typeof(IDictionary<,>), out dictionaryDefinition)) { Type dictionaryKeyType = ReflectionUtils.GetDictionaryKeyType(dictionaryDefinition); Type dictionaryValueType = ReflectionUtils.GetDictionaryValueType(dictionaryDefinition); // Activator.CreateInstance throws AmbiguousMatchException. Manually invoke constructor Func, object> instanceCreator = (t, a) => { ConstructorInfo c = t.GetConstructor(new[] { dictionaryDefinition }); return c.Invoke(new[] { dictionary }); }; return (IWrappedDictionary)ReflectionUtils.CreateGeneric(typeof(DictionaryWrapper<,>), new[] { dictionaryKeyType, dictionaryValueType }, instanceCreator, dictionary); } else if (dictionary is IDictionary) { return new DictionaryWrapper((IDictionary)dictionary); } else { throw new Exception("Can not create DictionaryWrapper for type {0}.".FormatWith(CultureInfo.InvariantCulture, dictionary.GetType())); } } public static object CreateAndPopulateList(Type listType, Action populateList) { ValidationUtils.ArgumentNotNull(listType, "listType"); ValidationUtils.ArgumentNotNull(populateList, "populateList"); IList list; Type collectionType; bool isReadOnlyOrFixedSize = false; if (listType.IsArray) { // have to use an arraylist when creating array // there is no way to know the size until it is finised list = new List(); isReadOnlyOrFixedSize = true; } else if (ReflectionUtils.InheritsGenericDefinition(listType, typeof(ReadOnlyCollection<>), out collectionType)) { Type readOnlyCollectionContentsType = collectionType.GetGenericArguments()[0]; Type genericEnumerable = ReflectionUtils.MakeGenericType(typeof(IEnumerable<>), readOnlyCollectionContentsType); bool suitableConstructor = false; foreach (ConstructorInfo constructor in listType.GetConstructors()) { IList parameters = constructor.GetParameters(); if (parameters.Count == 1) { if (genericEnumerable.IsAssignableFrom(parameters[0].ParameterType)) { suitableConstructor = true; break; } } } if (!suitableConstructor) throw new Exception("Read-only type {0} does not have a public constructor that takes a type that implements {1}.".FormatWith(CultureInfo.InvariantCulture, listType, genericEnumerable)); // can't add or modify a readonly list // use List and convert once populated list = CreateGenericList(readOnlyCollectionContentsType); isReadOnlyOrFixedSize = true; } else if (typeof(IList).IsAssignableFrom(listType)) { if (ReflectionUtils.IsInstantiatableType(listType)) list = (IList)Activator.CreateInstance(listType); else if (listType == typeof(IList)) list = new List(); else list = null; } else if (ReflectionUtils.ImplementsGenericDefinition(listType, typeof(ICollection<>))) { if (ReflectionUtils.IsInstantiatableType(listType)) list = CreateCollectionWrapper(Activator.CreateInstance(listType)); else list = null; } else { list = null; } if (list == null) throw new Exception("Cannot create and populate list type {0}.".FormatWith(CultureInfo.InvariantCulture, listType)); populateList(list, isReadOnlyOrFixedSize); // create readonly and fixed sized collections using the temporary list if (isReadOnlyOrFixedSize) { if (listType.IsArray) list = ToArray(((List)list).ToArray(), ReflectionUtils.GetCollectionItemType(listType)); else if (ReflectionUtils.InheritsGenericDefinition(listType, typeof(ReadOnlyCollection<>))) list = (IList)ReflectionUtils.CreateInstance(listType, list); } else if (list is IWrappedCollection) { return ((IWrappedCollection) list).UnderlyingCollection; } return list; } public static Array ToArray(Array initial, Type type) { if (type == null) throw new ArgumentNullException("type"); Array destinationArray = Array.CreateInstance(type, initial.Length); Array.Copy(initial, 0, destinationArray, 0, initial.Length); return destinationArray; } public static bool AddDistinct(this IList list, T value) { return list.AddDistinct(value, EqualityComparer.Default); } public static bool AddDistinct(this IList list, T value, IEqualityComparer comparer) { if (list.ContainsValue(value, comparer)) return false; list.Add(value); return true; } // this is here because LINQ Bridge doesn't support Contains with IEqualityComparer public static bool ContainsValue(this IEnumerable source, TSource value, IEqualityComparer comparer) { if (comparer == null) comparer = EqualityComparer.Default; if (source == null) throw new ArgumentNullException("source"); foreach (TSource local in source) { if (comparer.Equals(local, value)) return true; } return false; } public static bool AddRangeDistinct(this IList list, IEnumerable values) { return list.AddRangeDistinct(values, EqualityComparer.Default); } public static bool AddRangeDistinct(this IList list, IEnumerable values, IEqualityComparer comparer) { bool allAdded = true; foreach (T value in values) { if (!list.AddDistinct(value, comparer)) allAdded = false; } return allAdded; } public static int IndexOf(this IEnumerable collection, Func predicate) { int index = 0; foreach (T value in collection) { if (predicate(value)) return index; index++; } return -1; } } }