using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using Xamarin.Forms.Internals; namespace Xamarin.Forms { internal sealed class ListProxy : IReadOnlyList, IListProxy, INotifyCollectionChanged { readonly ICollection _collection; readonly IList _list; readonly int _windowSize; IEnumerator _enumerator; int _enumeratorIndex; bool _finished; HashSet _indexesCounted; Dictionary _items; int _version; int _windowIndex; internal ListProxy(IEnumerable enumerable, int windowSize = int.MaxValue) { _windowSize = windowSize; ProxiedEnumerable = enumerable; _collection = enumerable as ICollection; if (_collection == null && enumerable is IReadOnlyCollection) _collection = new ReadOnlyListAdapter((IReadOnlyCollection)enumerable); _list = enumerable as IList; if (_list == null && enumerable is IReadOnlyList) _list = new ReadOnlyListAdapter((IReadOnlyList)enumerable); var changed = enumerable as INotifyCollectionChanged; if (changed != null) new WeakNotifyProxy(this, changed); } public IEnumerable ProxiedEnumerable { get; } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public IEnumerator GetEnumerator() { return new ProxyEnumerator(this); } /// /// Gets whether or not the current window contains the . /// /// The item to search for. /// true if the item was found in a list or the current window, false otherwise. public bool Contains(object item) { if (_list != null) return _list.Contains(item); EnsureWindowCreated(); if (_items != null) return _items.Values.Contains(item); return false; } /// /// Gets the index for the if in a list or the current window. /// /// The item to search for. /// The index of the item if in a list or the current window, -1 otherwise. public int IndexOf(object item) { if (_list != null) return _list.IndexOf(item); EnsureWindowCreated(); if (_items != null) { foreach (KeyValuePair kvp in _items) { if (Equals(kvp.Value, item)) return kvp.Key; } } return -1; } public event NotifyCollectionChangedEventHandler CollectionChanged; public int Count { get { if (_collection != null) return _collection.Count; EnsureWindowCreated(); if (_indexesCounted != null) return _indexesCounted.Count; return 0; } } public object this[int index] { get { object value; if (!TryGetValue(index, out value)) throw new ArgumentOutOfRangeException("index"); return value; } } public void Clear() { _version++; _finished = false; _windowIndex = 0; _enumeratorIndex = 0; if (_enumerator != null) { var dispose = _enumerator as IDisposable; if (dispose != null) dispose.Dispose(); _enumerator = null; } if (_items != null) _items.Clear(); if (_indexesCounted != null) _indexesCounted.Clear(); OnCountChanged(); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } public event EventHandler CountChanged; void ClearRange(int index, int clearCount) { if (_items == null) return; for (int i = index; i < index + clearCount; i++) _items.Remove(i); } bool CountIndex(int index) { if (_collection != null) return false; // A collection is used in case TryGetValue is called out of order. if (_indexesCounted == null) _indexesCounted = new HashSet(); if (_indexesCounted.Contains(index)) return false; _indexesCounted.Add(index); return true; } void EnsureWindowCreated() { if (_items != null && _items.Count > 0) return; object value; TryGetValue(0, out value); } void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { Action action; if (_list == null) { action = Clear; } else { action = () => { _version++; OnCollectionChanged(e); }; } CollectionSynchronizationContext sync; if (BindingBase.TryGetSynchronizedCollection(ProxiedEnumerable, out sync)) { sync.Callback(ProxiedEnumerable, sync.Context, () => { e = e.WithCount(Count); Device.BeginInvokeOnMainThread(action); }, false); } else { e = e.WithCount(Count); if (Device.IsInvokeRequired) Device.BeginInvokeOnMainThread(action); else action(); } } void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { NotifyCollectionChangedEventHandler changed = CollectionChanged; if (changed != null) changed(this, e); } void OnCountChanged() { EventHandler changed = CountChanged; if (changed != null) changed(this, EventArgs.Empty); } bool TryGetValue(int index, out object value) { value = null; CollectionSynchronizationContext syncContext; BindingBase.TryGetSynchronizedCollection(ProxiedEnumerable, out syncContext); if (_list != null) { object indexedValue = null; var inRange = false; Action getFromList = () => { if (index >= _list.Count) return; indexedValue = _list[index]; inRange = true; }; if (syncContext != null) syncContext.Callback(ProxiedEnumerable, syncContext.Context, getFromList, false); else getFromList(); value = indexedValue; return inRange; } if (_collection != null && index >= _collection.Count) return false; if (_items != null) { bool found = _items.TryGetValue(index, out value); if (found || _finished) return found; } if (index >= _windowIndex + _windowSize) { int newIndex = index - _windowSize / 2; ClearRange(_windowIndex, newIndex - _windowIndex); _windowIndex = newIndex; } else if (index < _windowIndex) { int clearIndex = _windowIndex; int clearSize = _windowSize; if (clearIndex <= index + clearSize) { int diff = index + clearSize - clearIndex; clearIndex += diff + 1; clearSize -= diff; } ClearRange(clearIndex, clearSize); _windowIndex = 0; var dispose = _enumerator as IDisposable; if (dispose != null) dispose.Dispose(); _enumerator = null; _enumeratorIndex = 0; } if (_enumerator == null) _enumerator = ProxiedEnumerable.GetEnumerator(); if (_items == null) _items = new Dictionary(); var countChanged = false; int end = _windowIndex + _windowSize; for (; _enumeratorIndex < end; _enumeratorIndex++) { var moved = false; Action move = () => { try { moved = _enumerator.MoveNext(); } catch (InvalidOperationException ioex) { throw new InvalidOperationException("You must call UpdateNonNotifyingList() after updating a list that does not implement INotifyCollectionChanged", ioex); } if (!moved) { var dispose = _enumerator as IDisposable; if (dispose != null) dispose.Dispose(); _enumerator = null; _enumeratorIndex = 0; _finished = true; } }; if (syncContext == null) move(); else syncContext.Callback(ProxiedEnumerable, syncContext.Context, move, false); if (!moved) break; if (CountIndex(_enumeratorIndex)) countChanged = true; if (_enumeratorIndex >= _windowIndex) _items.Add(_enumeratorIndex, _enumerator.Current); } if (countChanged) OnCountChanged(); return _items.TryGetValue(index, out value); } class WeakNotifyProxy { readonly WeakReference _weakCollection; readonly WeakReference _weakProxy; public WeakNotifyProxy(ListProxy proxy, INotifyCollectionChanged incc) { incc.CollectionChanged += OnCollectionChanged; _weakProxy = new WeakReference(proxy); _weakCollection = new WeakReference(incc); } void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { ListProxy proxy; if (!_weakProxy.TryGetTarget(out proxy)) { INotifyCollectionChanged collection; if (_weakCollection.TryGetTarget(out collection)) collection.CollectionChanged -= OnCollectionChanged; return; } proxy.OnCollectionChanged(sender, e); } } class ProxyEnumerator : IEnumerator { readonly ListProxy _proxy; readonly int _version; int _index; public ProxyEnumerator(ListProxy proxy) { _proxy = proxy; _version = proxy._version; } public void Dispose() { } public bool MoveNext() { if (_proxy._version != _version) throw new InvalidOperationException(); object value; bool next = _proxy.TryGetValue(_index++, out value); if (next) Current = value; return next; } public void Reset() { _index = 0; Current = null; } public object Current { get; private set; } } #region IList object IList.this[int index] { get { return this[index]; } set { throw new NotSupportedException(); } } bool IList.IsReadOnly { get { return true; } } bool IList.IsFixedSize { get { return false; } } bool ICollection.IsSynchronized { get { return false; } } object ICollection.SyncRoot { get { throw new NotSupportedException(); } } void ICollection.CopyTo(Array array, int index) { throw new NotSupportedException(); } int IList.Add(object item) { throw new NotSupportedException(); } void IList.Remove(object item) { throw new NotSupportedException(); } void IList.Insert(int index, object item) { throw new NotSupportedException(); } void IList.RemoveAt(int index) { throw new NotSupportedException(); } void IList.Clear() { throw new NotSupportedException(); } #endregion } }