// // ImmutableList.cs // // Author: // Mike Krüger // // Copyright (c) 2013 Xamarin Inc. (http://xamarin.com) // // 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. using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; namespace Quobject.Collections.Immutable { public class ImmutableList : IImmutableList, IList, IList { public static readonly ImmutableList Empty = new ImmutableList(); readonly AvlNode root = AvlNode.Empty; readonly IEqualityComparer valueComparer; internal ImmutableList() { this.valueComparer = EqualityComparer.Default; } internal ImmutableList(AvlNode root, IEqualityComparer equalityComparer) { this.root = root; this.valueComparer = equalityComparer; } public void CopyTo(int index, T[] array, int arrayIndex, int count) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException("index"); if (array == null) throw new ArgumentNullException("array"); if (arrayIndex < 0 || arrayIndex + count > array.Length) throw new ArgumentOutOfRangeException("arrayIndex"); if (count < 0 || index + count > Count) throw new ArgumentOutOfRangeException("count"); foreach (var item in root.Enumerate (index, count, false)) { array[arrayIndex++] = item; } } public void CopyTo(T[] array, int arrayIndex) { if (array == null) throw new ArgumentNullException("array"); if (arrayIndex < 0 || arrayIndex + Count > array.Length) throw new ArgumentOutOfRangeException("arrayIndex"); CopyTo(0, array, 0, Count); } public void CopyTo(T[] array) { if (array == null) throw new ArgumentNullException("array"); CopyTo(array, 0); } public bool Exists(Predicate match) { if (match == null) throw new ArgumentNullException("match"); return this.Any(i => match(i)); } public T Find(Predicate match) { if (match == null) throw new ArgumentNullException("match"); return this.FirstOrDefault(i => match(i)); } public ImmutableList FindAll(Predicate match) { if (match == null) throw new ArgumentNullException("match"); var builder = Clear().ToBuilder(); foreach (var item in this) { if (match(item)) builder.Add(item); } return builder.ToImmutable(); } public int FindIndex(int startIndex, int count, Predicate match) { if (startIndex < 0 || startIndex >= Count) throw new ArgumentOutOfRangeException("startIndex"); if (count < 0 || startIndex + count > Count) throw new ArgumentOutOfRangeException("count"); if (match == null) throw new ArgumentNullException("match"); int i = startIndex; foreach (var item in root.Enumerate (startIndex, count, false)) { if (match(item)) return i; i++; } return -1; } public int FindIndex(Predicate match) { if (match == null) throw new ArgumentNullException("match"); return FindIndex(0, Count, match); } public int FindIndex(int startIndex, Predicate match) { if (startIndex < 0 || startIndex >= Count) throw new ArgumentOutOfRangeException("startIndex"); if (match == null) throw new ArgumentNullException("match"); return FindIndex(startIndex, Count - startIndex, match); } public T FindLast(Predicate match) { if (match == null) throw new ArgumentNullException("match"); return this.LastOrDefault(i => match(i)); } public int FindLastIndex(Predicate match) { if (match == null) throw new ArgumentNullException("match"); return FindLastIndex(Count - 1, Count, match); } public int FindLastIndex(int startIndex, Predicate match) { if (startIndex < 0 || startIndex >= Count) throw new ArgumentOutOfRangeException("startIndex"); if (match == null) throw new ArgumentNullException("match"); return FindLastIndex(startIndex, startIndex + 1, match); } public int FindLastIndex(int startIndex, int count, Predicate match) { if (startIndex < 0 || startIndex >= Count) throw new ArgumentOutOfRangeException("startIndex"); if (count > Count || startIndex - count + 1 < 0) throw new ArgumentOutOfRangeException("count"); if (match == null) throw new ArgumentNullException("match"); int index = startIndex; foreach (var item in root.Enumerate (startIndex, count, true)) { if (match(item)) return index; index--; } return -1; } public void ForEach(Action action) { if (action == null) throw new ArgumentNullException("action"); foreach (var item in this) { action(item); } } public ImmutableList GetRange(int index, int count) { return ImmutableList.Create(valueComparer, root.Enumerate(index, count, false)); } public int IndexOf(T value) { return IndexOf(value, 0, Count); } public int IndexOf(T value, int index) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException("index"); return IndexOf(value, 0, Count - index); } public int IndexOf(T value, int index, int count) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException("index"); if (count < 0 || index + count > Count) throw new ArgumentOutOfRangeException("count"); return FindIndex(index, count, i => valueComparer.Equals(value, i)); } public int LastIndexOf(T item, int index) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException("index"); return LastIndexOf(item, index, index + 1); } public int LastIndexOf(T item) { return LastIndexOf(item, Count - 1, Count); } public int LastIndexOf(T item, int index, int count) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException("index"); if (count > Count || index - count + 1 < 0) throw new ArgumentOutOfRangeException("count"); return FindLastIndex(index, count, i => valueComparer.Equals(item, i)); } #region IList implementation int IList.Add(object value) { throw new NotSupportedException(); } void IList.Clear() { throw new NotSupportedException(); } bool IList.Contains(object value) { return Contains((T)value); } int IList.IndexOf(object value) { return IndexOf((T)value); } void IList.Insert(int index, object value) { throw new NotSupportedException(); } void IList.Remove(object value) { throw new NotSupportedException(); } void IList.RemoveAt(int index) { throw new NotSupportedException(); } bool IList.IsFixedSize { get { return true; } } object IList.this [int index] { get { return this[index]; } set { throw new NotSupportedException(); } } bool IList.IsReadOnly { get { return true; } } #endregion #region ICollection implementation void ICollection.CopyTo(Array array, int index) { foreach (var item in this) array.SetValue(item, index++); } bool ICollection.IsSynchronized { get { return true; } } object ICollection.SyncRoot { get { return this; } } #endregion #region IList implementation T IList.this [int index] { get { return this[index]; } set { throw new NotSupportedException(); } } void IList.Insert(int index, T item) { throw new NotSupportedException(); } void IList.RemoveAt(int index) { throw new NotSupportedException(); } #endregion #region ICollection implementation void ICollection.Add(T item) { throw new NotSupportedException(); } void ICollection.Clear() { throw new NotSupportedException(); } void ICollection.CopyTo(T[] array, int arrayIndex) { CopyTo(array, arrayIndex); } bool ICollection.Remove(T item) { throw new NotSupportedException(); } bool ICollection.IsReadOnly { get { return true; } } #endregion #region IEnumerable implementation IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } #endregion #region IImmutableList implementation public ImmutableList Add(T value) { return Insert(Count, value); } IImmutableList IImmutableList.Add(T value) { return Add(value); } public ImmutableList AddRange(IEnumerable items) { return InsertRange(Count, items); } IImmutableList IImmutableList.AddRange(IEnumerable items) { return AddRange(items); } public ImmutableList Clear() { return Empty.WithComparer(valueComparer); } IImmutableList IImmutableList.Clear() { return Clear(); } public bool Contains(T value) { return IndexOf(value) != -1; } public ImmutableList Insert(int index, T element) { if (index > Count) throw new ArgumentOutOfRangeException("index"); return new ImmutableList(root.InsertIntoNew(index, element), valueComparer); } IImmutableList IImmutableList.Insert(int index, T element) { return Insert(index, element); } public ImmutableList InsertRange(int index, IEnumerable items) { var result = this; foreach (var item in items) { result = result.Insert(index++, item); } return result; } IImmutableList IImmutableList.InsertRange(int index, IEnumerable items) { return InsertRange(index, items); } public ImmutableList Remove(T value) { int loc = IndexOf(value); if (loc != -1) return RemoveAt(loc); return this; } IImmutableList IImmutableList.Remove(T value) { return Remove(value); } public ImmutableList RemoveAll(Predicate match) { if (match == null) throw new ArgumentNullException("match"); var result = this; for (int i = 0; i < result.Count; i++) { if (match(result[i])) { result = result.RemoveAt(i); i--; continue; } } return result; } IImmutableList IImmutableList.RemoveAll(Predicate match) { return RemoveAll(match); } public ImmutableList RemoveAt(int index) { bool found; return new ImmutableList(root.RemoveFromNew(index, out found), valueComparer); } IImmutableList IImmutableList.RemoveAt(int index) { return RemoveAt(index); } void CheckRange(int idx, int count) { if (idx < 0) throw new ArgumentOutOfRangeException("index"); if (count < 0) throw new ArgumentOutOfRangeException("count"); if ((uint)idx + (uint)count > (uint)this.Count) throw new ArgumentException("index and count exceed length of list"); } public ImmutableList RemoveRange(int index, int count) { CheckRange(index, count); var result = this; while (count-- > 0) { result = result.RemoveAt(index); } return result; } IImmutableList IImmutableList.RemoveRange(int index, int count) { return RemoveRange(index, count); } public ImmutableList RemoveRange(IEnumerable items) { var result = this; foreach (var item in items) { result = result.Remove(item); } return result; } IImmutableList IImmutableList.RemoveRange(IEnumerable items) { return RemoveRange(items); } public ImmutableList Replace(T oldValue, T newValue) { var idx = IndexOf(oldValue); if (idx < 0) return this; return SetItem(idx, newValue); } IImmutableList IImmutableList.Replace(T oldValue, T newValue) { return Replace(oldValue, newValue); } public ImmutableList SetItem(int index, T value) { if (index > Count) throw new ArgumentOutOfRangeException("index"); return new ImmutableList(root.SetItem(index, value), valueComparer); } IImmutableList IImmutableList.SetItem(int index, T value) { return SetItem(index, value); } public ImmutableList WithComparer(IEqualityComparer equalityComparer) { return new ImmutableList(root, equalityComparer); } IImmutableList IImmutableList.WithComparer(IEqualityComparer equalityComparer) { return WithComparer(equalityComparer); } public IEqualityComparer ValueComparer { get { return valueComparer; } } #endregion #region IEnumerable implementation public IEnumerator GetEnumerator() { return root.GetEnumerator(false); } #endregion #region IReadOnlyList implementation public T this [int index] { get { if (index >= Count) throw new ArgumentOutOfRangeException("index"); return root.GetNodeAt(index).Value; } } #endregion #region IReadOnlyCollection implementation public int Count { get { return root.Count; } } #endregion #region Builder public Builder ToBuilder() { return new Builder(root, valueComparer); } public class Builder { AvlNode root; readonly IEqualityComparer valueComparer; internal Builder(AvlNode immutableRoot, IEqualityComparer comparer) { root = immutableRoot.ToMutable(); valueComparer = comparer; } public ImmutableList ToImmutable() { return new ImmutableList(root.ToImmutable(), valueComparer); } public void Add(T value) { Insert(Count, value); } public void Insert(int index, T element) { if (index > Count) throw new ArgumentOutOfRangeException("index"); root = root.InsertIntoNew(index, element); Debug.Assert(root.IsMutable); } public int Count { get { return root.Count; } } } #endregion } public static class ImmutableList { public static ImmutableList Create() { return ImmutableList.Empty; } public static ImmutableList Create(IEqualityComparer equalityComparer, params T[] items) { return ImmutableList.Empty.WithComparer(equalityComparer).AddRange(items); } public static ImmutableList Create(params T[] items) { return Create(EqualityComparer.Default, items); } public static ImmutableList Create(IEqualityComparer equalityComparer, IEnumerable items) { return Create(equalityComparer, items.ToArray()); } public static ImmutableList Create(IEnumerable items) { return Create(items.ToArray()); } public static ImmutableList Create(IEqualityComparer equalityComparer, T item) { return ImmutableList.Empty.WithComparer(equalityComparer).Add(item); } public static ImmutableList Create(T item) { return Create(EqualityComparer.Default, item); } public static ImmutableList Create(IEqualityComparer equalityComparer) { return Create().WithComparer(equalityComparer); } public static ImmutableList ToImmutableList(this IEnumerable source) { if (source == null) throw new ArgumentNullException("source"); return Create().AddRange(source); } public static ImmutableList ToImmutableList(this IEnumerable source, IEqualityComparer equalityComparer) { if (source == null) throw new ArgumentNullException("source"); return Create().WithComparer(equalityComparer).AddRange(source); } } }