// // OrderedDictionary.cs // // Author: // Eric Maupin // // Copyright (c) 2009 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; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using Xamarin.PropertyEditing; namespace Cadenza.Collections { internal class OrderedDictionary : IDictionary, IList>, IReadOnlyOrderedDictionary, INotifyCollectionChanged { public OrderedDictionary () : this (0) { } public OrderedDictionary (int capacity) : this (capacity, EqualityComparer.Default) { } public OrderedDictionary (IEqualityComparer equalityComparer) : this (0, equalityComparer) { } public OrderedDictionary (int capacity, IEqualityComparer equalityComparer) { this.dict = new Dictionary (capacity, equalityComparer); this.kvpCollection = this.dict; this.keyOrder = new List (capacity); this.roKeys = new ReadOnlyKeysCollection (this.keyOrder); this.roValues = new ReadOnlyValueCollection (this); } public OrderedDictionary (ICollection> dictionary) : this (dictionary, EqualityComparer.Default) { } public OrderedDictionary (ICollection> dictionary, IEqualityComparer equalityComparer) : this ((dictionary != null) ? dictionary.Count : 0, equalityComparer) { if (dictionary == null) throw new ArgumentNullException ("dictionary"); foreach (var kvp in dictionary) Add (kvp.Key, kvp.Value); } public event NotifyCollectionChangedEventHandler CollectionChanged; bool ICollection>.IsReadOnly { get { return false; } } /// /// Gets the equality comparer being used for . /// public IEqualityComparer Comparer { get { return this.dict.Comparer; } } /// /// Gets or sets the value associated with . /// /// The key to get or set the value for. /// The value associated with the key. /// was not found attempting to get. public TValue this[TKey key] { get { return this.dict[key]; } set { int index = -1; bool replace = false; if (!this.dict.TryGetValue (key, out TValue oldValue)) this.keyOrder.Add (key); else { replace = true; index = this.keyOrder.IndexOf (key); } this.dict[key] = value; if (replace) { this.roValues.OnCollectionChanged (new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Replace, oldValue, value, index)); OnCollectionChanged (new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Replace, new KeyValuePair (key, oldValue), new KeyValuePair (key, value))); } else { OnCollectionChanged (NotifyCollectionChangedAction.Add, this.keyOrder.Count - 1, key, value); } } } IEnumerable IReadOnlyDictionary.Keys { get { return Keys; } } IEnumerable IReadOnlyDictionary.Values { get { return Values; } } /// /// Gets the value at the specified index. /// /// The index to get the value at. /// The value at the specified index. /// is less than 0 or greater than . public TValue this[int index] { get { return this.dict[this.keyOrder[index]]; } } KeyValuePair IReadOnlyOrderedDictionary.this[int index] { get { return new KeyValuePair (this.keyOrder[index], this[index]); } } KeyValuePair IList>.this[int index] { get { return new KeyValuePair (this.keyOrder[index], this[index]); } set { TKey existingKey = this.keyOrder[index]; TValue existingValue = this.dict[existingKey]; if (!Equals (existingKey, value.Key)) { if (this.dict.ContainsKey (value.Key)) throw new ArgumentException ("Existing keys can't be moved by setting them into another index", $"{nameof(value)}.{nameof(value.Key)}"); this.keyOrder[index] = value.Key; this.dict.Remove (existingKey); } this.dict[value.Key] = value.Value; this.roKeys.OnCollectionChanged (new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Replace, existingKey, value.Key, index)); this.roValues.OnCollectionChanged (new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Replace, existingValue, value.Value, index)); OnCollectionChanged (new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Replace, new KeyValuePair (existingKey, existingValue), new KeyValuePair (value.Key, value.Value))); } } /// /// Gets the number of items in the dictionary. /// public int Count { get { return this.dict.Count; } } /// /// Gets a read only collection of keys in the dictionary. /// public ICollection Keys { get { return this.roKeys; } } /// /// Gets a read only collection of values in the dictionary. /// public ICollection Values { get { return this.roValues; } } bool ICollection>.Contains (KeyValuePair item) { return kvpCollection.Contains (item); } /// /// Gets whether or not is in the dictionary. /// /// The key to look for. /// true if the key was found, false if not. /// If is null. public bool ContainsKey (TKey key) { return this.dict.ContainsKey (key); } /// /// Gets whether or not is in the dictionary. /// /// The value to look for. /// true if the value was found, false if not. public bool ContainsValue (TValue value) { return this.dict.ContainsValue (value); } /// /// Gets the index of . /// /// The key to find the index of. /// -1 if the key was not found, the index otherwise. /// is null. public int IndexOf (TKey key) { if (key == null) throw new ArgumentNullException ("key"); return this.keyOrder.IndexOf (key); } /// /// Gets the index of starting with . /// /// The key to find the index of. /// The index to start the search at. /// -1 if the key was not found, the index otherwise. /// is null. /// is not within the valid range of indexes. public int IndexOf (TKey key, int startIndex) { if (key == null) throw new ArgumentNullException ("key"); return this.keyOrder.IndexOf (key, startIndex); } public TKey KeyAt (int index) { return this.keyOrder[index]; } public int BinarySearch (TKey key) { return this.keyOrder.BinarySearch (key); } public int BinarySearch (TKey key, int startIndex, int count) { return this.keyOrder.BinarySearch (startIndex, count, key, null); } /// /// Gets the index of between the range given by and . /// /// The key to find the index of. /// The index to start the search at. /// How many items to search, including the . /// -1 if the key was not found, the index otherwise. /// is null. /// is not within the valid range of indexes. /// and are not a valid range. /// is less than 0. public int IndexOf (TKey key, int startIndex, int count) { if (key == null) throw new ArgumentNullException ("key"); return this.keyOrder.IndexOf (key, startIndex, count); } int IList>.IndexOf (KeyValuePair item) { return this.keyOrder.IndexOf (item.Key); } /// /// Attempts to get the for the . /// /// The key to search for. /// The value, if found. /// true if the key was found, false otherwise. /// If is null. public bool TryGetValue (TKey key, out TValue value) { return this.dict.TryGetValue (key, out value); } /// /// Adds the and to the dictionary. /// /// The key to associate with the . /// The value to add. /// is null. /// already exists in the dictionary. public void Add (TKey key, TValue value) { int index = this.keyOrder.Count; this.dict.Add (key, value); this.keyOrder.Add (key); OnCollectionChanged (NotifyCollectionChangedAction.Add, index, key, value); } void ICollection>.Add (KeyValuePair item) { Add (item.Key, item.Value); } /// /// Inserts the and at the specified index. /// /// The index to insert the key and value at. /// The key to assicate with the . /// The value to insert. /// is null. /// is less than 0 or greater than public void Insert (int index, TKey key, TValue value) { if (index < 0 || index > this.keyOrder.Count) throw new ArgumentOutOfRangeException (nameof(index)); this.dict.Add (key, value); this.keyOrder.Insert (index, key); OnCollectionChanged (NotifyCollectionChangedAction.Add, index, key, value); } void IList>.Insert (int index, KeyValuePair item) { Insert (index, item.Key, item.Value); } /// /// Removes the key and associated value from the dictionary if found. /// /// The key to remove. /// true if the key was found, false if not. /// is null. public bool Remove (TKey key) { if (key == null) throw new ArgumentNullException (nameof(key)); int index = this.keyOrder.IndexOf (key); if (index > -1) { RemoveAt (index); return true; } return false; } bool ICollection>.Remove (KeyValuePair item) { int index = this.keyOrder.IndexOf (item.Key); if (index > -1) { RemoveAt (index); return true; } return false; } /// /// Removes they key and associated value from the dictionary located at . /// /// The index at which to remove an item. /// is less than 0 or greater than public void RemoveAt (int index) { TKey key = this.keyOrder[index]; this.keyOrder.RemoveAt (index); this.dict.TryRemove (key, out var value); OnCollectionChanged (NotifyCollectionChangedAction.Remove, index, key, value); } /// /// Clears the dictionary. /// public void Clear () { this.dict.Clear (); this.keyOrder.Clear (); var args = new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Reset); this.roKeys.OnCollectionChanged (args); this.roValues.OnCollectionChanged (args); OnCollectionChanged (args); } void ICollection>.CopyTo (KeyValuePair[] array, int arrayIndex) { if (array == null) throw new ArgumentNullException ("array"); if (this.Count > array.Length - arrayIndex) throw new ArgumentException ("Not enough space in array to copy"); if (arrayIndex < 0) throw new ArgumentOutOfRangeException ("arrayIndex"); for (int i = 0; i < this.keyOrder.Count; ++i) { TKey key = keyOrder[i]; array[arrayIndex++] = new KeyValuePair (key, this.dict[key]); } } public IEnumerator> GetEnumerator () { foreach (TKey key in this.keyOrder) yield return new KeyValuePair (key, this[key]); } IEnumerator IEnumerable.GetEnumerator () { return GetEnumerator (); } private readonly ReadOnlyValueCollection roValues; private readonly ReadOnlyKeysCollection roKeys; private readonly ICollection> kvpCollection; private readonly Dictionary dict; private readonly List keyOrder; private void OnCollectionChanged (NotifyCollectionChangedAction action, int index, TKey key, TValue value) { OnCollectionChanged (new NotifyCollectionChangedEventArgs (action, new KeyValuePair (key, value), index)); this.roKeys.OnCollectionChanged (new NotifyCollectionChangedEventArgs (action, key, index)); this.roValues.OnCollectionChanged (new NotifyCollectionChangedEventArgs (action, value, index)); } private void OnCollectionChanged (NotifyCollectionChangedEventArgs e) { CollectionChanged?.Invoke (this, e); } private class ReadOnlyKeysCollection : ReadOnlyCollection, INotifyCollectionChanged { public ReadOnlyKeysCollection (IList list) : base (list) { } public event NotifyCollectionChangedEventHandler CollectionChanged; internal void OnCollectionChanged (NotifyCollectionChangedEventArgs e) { CollectionChanged?.Invoke (this, e); } } private class ReadOnlyValueCollection : IList, IReadOnlyList, INotifyCollectionChanged { public ReadOnlyValueCollection (OrderedDictionary dict) { this.odict = dict; } public event NotifyCollectionChangedEventHandler CollectionChanged; public void Add (TValue item) { throw new NotSupportedException (); } public void Clear () { throw new NotSupportedException (); } public bool Contains (TValue item) { return this.odict.ContainsValue (item); } public void CopyTo (TValue[] array, int arrayIndex) { if (array == null) throw new ArgumentNullException ("array"); if (this.Count > array.Length - arrayIndex) throw new ArgumentException ("Not enough space in array to copy"); if (arrayIndex < 0 || arrayIndex > array.Length) throw new ArgumentOutOfRangeException ("arrayIndex"); for (int i = 0; i < this.odict.Count; ++i) array[arrayIndex++] = this.odict[i]; } public int Count { get { return odict.Count; } } public bool IsReadOnly { get { return true; } } public bool Remove (TValue item) { throw new NotSupportedException (); } public int IndexOf (TValue item) { throw new NotImplementedException (); //return this.odict.dict.Values.IndexOf (item); } public void Insert (int index, TValue item) { throw new NotSupportedException (); } public void RemoveAt (int index) { throw new NotSupportedException (); } public TValue this[int index] { get { return odict[index]; } set { throw new NotSupportedException (); } } public IEnumerator GetEnumerator () { for (int i = 0; i < odict.keyOrder.Count; ++i) yield return odict[i]; } IEnumerator IEnumerable.GetEnumerator () { return GetEnumerator (); } private readonly OrderedDictionary odict; internal void OnCollectionChanged (NotifyCollectionChangedEventArgs e) { CollectionChanged?.Invoke (this, e); } } } }