summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Core/ListProxy.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Xamarin.Forms.Core/ListProxy.cs')
-rw-r--r--Xamarin.Forms.Core/ListProxy.cs488
1 files changed, 488 insertions, 0 deletions
diff --git a/Xamarin.Forms.Core/ListProxy.cs b/Xamarin.Forms.Core/ListProxy.cs
new file mode 100644
index 00000000..229dcb6b
--- /dev/null
+++ b/Xamarin.Forms.Core/ListProxy.cs
@@ -0,0 +1,488 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+
+namespace Xamarin.Forms
+{
+ internal sealed class ListProxy : IReadOnlyList<object>, IList, INotifyCollectionChanged
+ {
+ readonly ICollection _collection;
+ readonly IList _list;
+ readonly int _windowSize;
+
+ IEnumerator _enumerator;
+ int _enumeratorIndex;
+
+ bool _finished;
+ HashSet<int> _indexesCounted;
+
+ Dictionary<int, object> _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<object>)
+ _collection = new ReadOnlyListAdapter((IReadOnlyCollection<object>)enumerable);
+
+ _list = enumerable as IList;
+ if (_list == null && enumerable is IReadOnlyList<object>)
+ _list = new ReadOnlyListAdapter((IReadOnlyList<object>)enumerable);
+
+ var changed = enumerable as INotifyCollectionChanged;
+ if (changed != null)
+ new WeakNotifyProxy(this, changed);
+ }
+
+ public IEnumerable ProxiedEnumerable { get; }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public IEnumerator<object> GetEnumerator()
+ {
+ return new ProxyEnumerator(this);
+ }
+
+ /// <summary>
+ /// Gets whether or not the current window contains the <paramref name="item" />.
+ /// </summary>
+ /// <param name="item">The item to search for.</param>
+ /// <returns><c>true</c> if the item was found in a list or the current window, <c>false</c> otherwise.</returns>
+ public bool Contains(object item)
+ {
+ if (_list != null)
+ return _list.Contains(item);
+
+ EnsureWindowCreated();
+
+ if (_items != null)
+ return _items.Values.Contains(item);
+
+ return false;
+ }
+
+ /// <summary>
+ /// Gets the index for the <paramref name="item" /> if in a list or the current window.
+ /// </summary>
+ /// <param name="item">The item to search for.</param>
+ /// <returns>The index of the item if in a list or the current window, -1 otherwise.</returns>
+ public int IndexOf(object item)
+ {
+ if (_list != null)
+ return _list.IndexOf(item);
+
+ EnsureWindowCreated();
+
+ if (_items != null)
+ {
+ foreach (KeyValuePair<int, object> 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<int>();
+
+ 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<int, object>();
+
+ 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<INotifyCollectionChanged> _weakCollection;
+ readonly WeakReference<ListProxy> _weakProxy;
+
+ public WeakNotifyProxy(ListProxy proxy, INotifyCollectionChanged incc)
+ {
+ incc.CollectionChanged += OnCollectionChanged;
+
+ _weakProxy = new WeakReference<ListProxy>(proxy);
+ _weakCollection = new WeakReference<INotifyCollectionChanged>(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<object>
+ {
+ 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
+ }
+} \ No newline at end of file