// // MutableLookup.cs // // Author: // Eric Maupin // // Copyright (c) 2011 Eric Maupin (http://www.ermau.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.Generic; using System.Collections.Specialized; using System.Linq; using Cadenza.Collections; namespace Xamarin.PropertyEditing { /// /// A mutable lookup implementing /// /// The lookup key. /// The elements under each . internal class ObservableLookup : IMutableLookup, INotifyCollectionChanged, IReadOnlyList> { public ObservableLookup () : this (EqualityComparer.Default) { } public ObservableLookup (IEqualityComparer comparer) { if (comparer == null) throw new ArgumentNullException ("comparer"); this.groupings = new OrderedDictionary> (comparer); if (!typeof (TKey).IsValueType) this.nullGrouping = new ObservableGrouping (default (TKey)); } public event NotifyCollectionChangedEventHandler CollectionChanged; public bool ReuseGroups { get { return this.reuseGroups; } set { if (this.reuseGroups == value) return; this.reuseGroups = value; if (value) this.oldGroups = new Dictionary> (); else this.oldGroups = null; } } /// /// Adds under the specified . does not need to exist. /// /// The key to add under. /// The element to add. public void Add (TKey key, TElement element) { ObservableGrouping grouping; if (key == null) { grouping = nullGrouping; if (grouping.Count == 0) OnCollectionChanged (new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Add, (object)grouping, this.groupings.Count)); } else if (!this.groupings.TryGetValue (key, out grouping)) { if (!ReuseGroups || !this.oldGroups.TryRemove (key, out grouping)) grouping = new ObservableGrouping (key); this.groupings.Add (key, grouping); OnCollectionChanged (new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Add, (object)grouping, this.groupings.Count - 1)); } grouping.Add (element); } public void Add (TKey key, IEnumerable elements) { if (elements == null) throw new ArgumentNullException (nameof(elements)); ObservableGrouping grouping; if (key == null) { bool wasEmpty = this.nullGrouping.Count == 0; grouping = nullGrouping; grouping.AddRange (elements); if (wasEmpty && this.nullGrouping.Count > 0) OnCollectionChanged (new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Add, (object)grouping, this.groupings.Count)); } else if (!this.groupings.TryGetValue (key, out grouping)) { if (!ReuseGroups || !this.oldGroups.TryRemove (key, out grouping)) grouping = new ObservableGrouping (key); grouping.AddRange (elements); if (grouping.Count == 0) { if (ReuseGroups) this.oldGroups.Add (grouping.Key, grouping); return; } this.groupings.Add (key, grouping); OnCollectionChanged (new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Add, (object)grouping, this.groupings.Count - 1)); } } public void Add (IGrouping grouping) { if (grouping == null) throw new ArgumentNullException (nameof(grouping)); if (grouping.Key == null) { bool wasEmpty = this.nullGrouping.Count == 0; this.nullGrouping.AddRange (grouping); if (wasEmpty && this.nullGrouping.Count > 0) OnCollectionChanged (new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Add, (object)grouping, this.groupings.Count)); return; } ObservableGrouping og; if (!ReuseGroups || !this.oldGroups.TryRemove (grouping.Key, out og)) { og = new ObservableGrouping (grouping.Key); } og.AddRange (grouping); if (og.Count == 0) { if (ReuseGroups) this.oldGroups.Add (grouping.Key, og); return; } this.groupings.Add (grouping.Key, og); OnCollectionChanged (new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Add, (object)og, this.groupings.Count - 1)); } public void Insert (int index, IGrouping grouping) { ObservableGrouping og; if (!ReuseGroups || !this.oldGroups.TryRemove (grouping.Key, out og)) { og = new ObservableGrouping (grouping.Key); } og.AddRange (grouping); if (og.Count == 0) { if (ReuseGroups) this.oldGroups.Add (grouping.Key, og); return; } this.groupings.Insert (index, og.Key, og); OnCollectionChanged (new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Add, (object)og, index)); } /// /// Removes from the . /// /// The key that is located under. /// The element to remove from . /// true if and existed, false if not. public bool Remove (TKey key, TElement element) { ObservableGrouping group; if (key == null) { group = this.nullGrouping; } else { if (!this.groupings.TryGetValue (key, out group)) return false; } if (group.Remove (element)) { if (group.Count == 0) { if (!Remove (key) && key == null) OnCollectionChanged (new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Remove, (object)this.nullGrouping, this.groupings.Count)); } return true; } return false; } /// /// Removes from the lookup. /// /// They to remove. /// true if existed. /// is null. public bool Remove (TKey key) { if (key == null) { bool removed = (this.nullGrouping.Count > 0); if (removed) { this.nullGrouping.Clear(); OnCollectionChanged (new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Remove, (object) this.nullGrouping, this.groupings.Count)); } return removed; } int index = this.groupings.IndexOf (key); if (index >= 0) { var g = this.groupings[index]; this.groupings.Remove (key); if (ReuseGroups) { g.Clear (); this.oldGroups.Add (key, g); } OnCollectionChanged (new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Remove, (object)g, index)); return true; } return false; } public void Clear () { this.nullGrouping?.Clear(); if (ReuseGroups) { foreach (var g in this.groupings.Values) { this.oldGroups.Add (g.Key, g); g.Clear (); } } this.groupings.Clear (); OnCollectionChanged (new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Reset)); } public bool TryGetValues (TKey key, out IEnumerable values) { values = null; if (key == null) { if (this.nullGrouping.Count != 0) { values = this.nullGrouping; return true; } else return false; } ObservableGrouping grouping; if (!this.groupings.TryGetValue (key, out grouping)) return false; values = grouping; return true; } #region ILookup Members /// /// Gets the number of groupings. /// public int Count { get { return this.groupings.Count + ((this.nullGrouping != null && this.nullGrouping.Count > 0) ? 1 : 0); } } IGroupingList IReadOnlyList>.this [int index] { get { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException (nameof(index)); return (IGroupingList)this[index]; } } /// /// Gets the elements for . /// /// The key to get the elements for. /// The elements under . public IEnumerable this[TKey key] { get { if (key == null) return this.nullGrouping; ObservableGrouping grouping; if (this.groupings.TryGetValue (key, out grouping)) return grouping; return Enumerable.Empty(); } } public IEnumerable this[int index] { get { if (index == this.groupings.Count) { if (this.nullGrouping.Count > 0) return this.nullGrouping; else return Enumerable.Empty(); } return this.groupings[index]; } } /// /// Gets whether or not there's a grouping for . /// /// The key to check for. /// true if is present. public bool Contains (TKey key) { if (key == null) return (this.nullGrouping.Count > 0); return this.groupings.ContainsKey (key); } public IEnumerator> GetEnumerator () { foreach (var g in this.groupings.Values) yield return g; if (this.nullGrouping != null && this.nullGrouping.Count > 0) yield return this.nullGrouping; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () { return this.GetEnumerator (); } #endregion private readonly OrderedDictionary> groupings; private readonly ObservableGrouping nullGrouping; private bool reuseGroups; private Dictionary> oldGroups; private void OnCollectionChanged (NotifyCollectionChangedEventArgs e) { CollectionChanged?.Invoke (this, e); } IEnumerator> IEnumerable>.GetEnumerator () { foreach (var g in this.groupings.Values) yield return g; if (this.nullGrouping != null && this.nullGrouping.Count > 0) yield return this.nullGrouping; } } }