/****************************************************************************** * Spine Runtimes License Agreement * Last updated January 1, 2020. Replaces all prior versions. * * Copyright (c) 2013-2020, Esoteric Software LLC * * Integration of the Spine Runtimes into software or otherwise creating * derivative works of the Spine Runtimes is permitted under the terms and * conditions of Section 2 of the Spine Editor License Agreement: * http://esotericsoftware.com/spine-editor-license * * Otherwise, it is permitted to integrate the Spine Runtimes into software * or otherwise create derivative works of the Spine Runtimes (collectively, * "Products"), provided that each user of the Products must obtain their own * Spine Editor license and redistribution of the Products in any form must * include this license and copyright notice. * * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ /****************************************************************************** * Thanks to Travis Parks * https://github.com/jehugaleahsa/truncon.collections.OrderedDictionary *****************************************************************************/ using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; namespace Spine.Collections { /// /// Represents a dictionary that tracks the order that items were added. /// /// The type of the dictionary keys. /// The type of the dictionary values. /// /// This dictionary makes it possible to get the index of a key and a key based on an index. /// It can be costly to find the index of a key because it must be searched for linearly. /// It can be costly to insert a key/value pair because other key's indexes must be adjusted. /// It can be costly to remove a key/value pair because other keys' indexes must be adjusted. /// [DebuggerDisplay("Count = {Count}")] [DebuggerTypeProxy(typeof(OrderedDictionaryDebugView<,>))] public sealed class OrderedDictionary : IDictionary, IList> { private readonly Dictionary dictionary; private readonly List keys; private readonly List values; private int version; private const string CollectionModifiedMessage = "Collection was modified; enumeration operation may not execute."; private const string EditReadOnlyListMessage = "An attempt was made to edit a read-only list."; private const string IndexOutOfRangeMessage = "The index is negative or outside the bounds of the collection."; /// /// Initializes a new instance of an OrderedDictionary. /// public OrderedDictionary () : this(0, null) { } /// /// Initializes a new instance of an OrderedDictionary. /// /// The initial capacity of the dictionary. /// The capacity is less than zero. public OrderedDictionary (int capacity) : this(capacity, null) { } /// /// Initializes a new instance of an OrderedDictionary. /// /// The equality comparer to use to compare keys. public OrderedDictionary (IEqualityComparer comparer) : this(0, comparer) { } /// /// Initializes a new instance of an OrderedDictionary. /// /// The initial capacity of the dictionary. /// The equality comparer to use to compare keys. public OrderedDictionary (int capacity, IEqualityComparer comparer) { dictionary = new Dictionary(capacity, comparer ?? EqualityComparer.Default); keys = new List(capacity); values = new List(capacity); } /// /// Gets the equality comparer used to compare keys. /// public IEqualityComparer Comparer { get { return dictionary.Comparer; } } /// /// Adds the given key/value pair to the dictionary. /// /// The key to add to the dictionary. /// The value to associated with the key. /// The given key already exists in the dictionary. /// The key is null. public void Add (TKey key, TValue value) { dictionary.Add(key, values.Count); keys.Add(key); values.Add(value); ++version; } /// /// Inserts the given key/value pair at the specified index. /// /// The index to insert the key/value pair. /// The key to insert. /// The value to insert. /// The given key already exists in the dictionary. /// The key is null. /// The index is negative -or- larger than the size of the dictionary. public void Insert (int index, TKey key, TValue value) { if (index < 0 || index > values.Count) { throw new ArgumentOutOfRangeException("index", index, IndexOutOfRangeMessage); } dictionary.Add(key, index); for (int keyIndex = index; keyIndex != keys.Count; ++keyIndex) { var otherKey = keys[keyIndex]; dictionary[otherKey] += 1; } keys.Insert(index, key); values.Insert(index, value); ++version; } /// /// Determines whether the given key exists in the dictionary. /// /// The key to look for. /// True if the key exists in the dictionary; otherwise, false. /// The key is null. public bool ContainsKey (TKey key) { return dictionary.ContainsKey(key); } /// /// Gets the key at the given index. /// /// The index of the key to get. /// The key at the given index. /// The index is negative -or- larger than the number of keys. public TKey GetKey (int index) { return keys[index]; } /// /// Gets the index of the given key. /// /// The key to get the index of. /// The index of the key in the dictionary -or- -1 if the key is not found. /// The operation runs in O(n). public int IndexOf (TKey key) { int index; if (dictionary.TryGetValue(key, out index)) { return index; } return -1; } /// /// Gets the keys in the dictionary in the order they were added. /// public KeyCollection Keys { get { return new KeyCollection(this.dictionary); } } /// /// Removes the key/value pair with the given key from the dictionary. /// /// The key of the pair to remove. /// True if the key was found and the pair removed; otherwise, false. /// The key is null. public bool Remove (TKey key) { int index; if (dictionary.TryGetValue(key, out index)) { RemoveAt(index); return true; } return false; } /// /// Removes the key/value pair at the given index. /// /// The index of the key/value pair to remove. /// The index is negative -or- larger than the size of the dictionary. public void RemoveAt (int index) { var key = keys[index]; for (int keyIndex = index + 1; keyIndex < keys.Count; ++keyIndex) { var otherKey = keys[keyIndex]; dictionary[otherKey] -= 1; } dictionary.Remove(key); keys.RemoveAt(index); values.RemoveAt(index); ++version; } /// /// Tries to get the value associated with the given key. If the key is not found, /// default(TValue) value is stored in the value. /// /// The key to get the value for. /// The value used to hold the results. /// True if the key was found; otherwise, false. /// The key is null. public bool TryGetValue (TKey key, out TValue value) { int index; if (dictionary.TryGetValue(key, out index)) { value = values[index]; return true; } value = default(TValue); return false; } /// /// Gets the values in the dictionary. /// public ValueCollection Values { get { return new ValueCollection(values); } } /// /// Gets or sets the value at the given index. /// /// The index of the value to get. /// The value at the given index. /// The index is negative -or- beyond the length of the dictionary. public TValue this[int index] { get { return values[index]; } set { values[index] = value; } } /// /// Gets or sets the value associated with the given key. /// /// The key to get the associated value by or to associate with the value. /// The value associated with the given key. /// The key is null. /// The key is not in the dictionary. public TValue this[TKey key] { get { return values[dictionary[key]]; } set { int index; if (dictionary.TryGetValue(key, out index)) { keys[index] = key; values[index] = value; } else { Add(key, value); } } } /// /// Removes all key/value pairs from the dictionary. /// public void Clear () { dictionary.Clear(); keys.Clear(); values.Clear(); ++version; } /// /// Gets the number of key/value pairs in the dictionary. /// public int Count { get { return dictionary.Count; } } /// /// Gets the key/value pairs in the dictionary in the order they were added. /// /// An enumerator over the key/value pairs in the dictionary. public IEnumerator> GetEnumerator () { int startVersion = version; for (int index = 0; index != keys.Count; ++index) { var key = keys[index]; var value = values[index]; yield return new KeyValuePair(key, value); if (version != startVersion) { throw new InvalidOperationException(CollectionModifiedMessage); } } } int IList>.IndexOf (KeyValuePair item) { int index; if (dictionary.TryGetValue(item.Key, out index) && Equals(values[index], item.Value)) { return index; } return -1; } void IList>.Insert (int index, KeyValuePair item) { Insert(index, item.Key, item.Value); } KeyValuePair IList>.this[int index] { get { TKey key = keys[index]; TValue value = values[index]; return new KeyValuePair(key, value); } set { TKey key = keys[index]; if (dictionary.Comparer.Equals(key, value.Key)) { dictionary[value.Key] = index; } else { dictionary.Add(value.Key, index); // will throw if key already exists dictionary.Remove(key); } keys[index] = value.Key; values[index] = value.Value; } } ICollection IDictionary.Keys { get { return Keys; } } ICollection IDictionary.Values { get { return Values; } } void ICollection>.Add (KeyValuePair item) { Add(item.Key, item.Value); } bool ICollection>.Contains (KeyValuePair item) { int index; if (dictionary.TryGetValue(item.Key, out index) && Equals(values[index], item.Value)) { return true; } return false; } void ICollection>.CopyTo (KeyValuePair[] array, int arrayIndex) { if (array == null) { throw new ArgumentNullException("array"); } if (arrayIndex < 0) { throw new ArgumentOutOfRangeException("arrayIndex", arrayIndex, IndexOutOfRangeMessage); } for (int index = 0; index != keys.Count && arrayIndex < array.Length; ++index, ++arrayIndex) { var key = keys[index]; var value = values[index]; array[arrayIndex] = new KeyValuePair(key, value); } } bool ICollection>.IsReadOnly { get { return false; } } bool ICollection>.Remove (KeyValuePair item) { ICollection> self = this; if (self.Contains(item)) { return Remove(item.Key); } return false; } IEnumerator IEnumerable.GetEnumerator () { return GetEnumerator(); } /// /// Wraps the keys in an OrderDictionary. /// public sealed class KeyCollection : ICollection { private readonly Dictionary dictionary; /// /// Initializes a new instance of a KeyCollection. /// /// The OrderedDictionary whose keys to wrap. /// The dictionary is null. internal KeyCollection (Dictionary dictionary) { this.dictionary = dictionary; } /// /// Copies the keys from the OrderedDictionary to the given array, starting at the given index. /// /// The array to copy the keys to. /// The index into the array to start copying the keys. /// The array is null. /// The arrayIndex is negative. /// The array, starting at the given index, is not large enough to contain all the keys. public void CopyTo (TKey[] array, int arrayIndex) { dictionary.Keys.CopyTo(array, arrayIndex); } /// /// Gets the number of keys in the OrderedDictionary. /// public int Count { get { return dictionary.Count; } } /// /// Gets an enumerator over the keys in the OrderedDictionary. /// /// The enumerator. public IEnumerator GetEnumerator () { return dictionary.Keys.GetEnumerator(); } [EditorBrowsable(EditorBrowsableState.Never)] bool ICollection.Contains (TKey item) { return dictionary.ContainsKey(item); } [EditorBrowsable(EditorBrowsableState.Never)] void ICollection.Add (TKey item) { throw new NotSupportedException(EditReadOnlyListMessage); } [EditorBrowsable(EditorBrowsableState.Never)] void ICollection.Clear () { throw new NotSupportedException(EditReadOnlyListMessage); } [EditorBrowsable(EditorBrowsableState.Never)] bool ICollection.IsReadOnly { get { return true; } } [EditorBrowsable(EditorBrowsableState.Never)] bool ICollection.Remove (TKey item) { throw new NotSupportedException(EditReadOnlyListMessage); } IEnumerator IEnumerable.GetEnumerator () { return GetEnumerator(); } } /// /// Wraps the keys in an OrderDictionary. /// public sealed class ValueCollection : ICollection { private readonly List values; /// /// Initializes a new instance of a ValueCollection. /// /// The OrderedDictionary whose keys to wrap. /// The dictionary is null. internal ValueCollection (List values) { this.values = values; } /// /// Copies the values from the OrderedDictionary to the given array, starting at the given index. /// /// The array to copy the values to. /// The index into the array to start copying the values. /// The array is null. /// The arrayIndex is negative. /// The array, starting at the given index, is not large enough to contain all the values. public void CopyTo (TValue[] array, int arrayIndex) { values.CopyTo(array, arrayIndex); } /// /// Gets the number of values in the OrderedDictionary. /// public int Count { get { return values.Count; } } /// /// Gets an enumerator over the values in the OrderedDictionary. /// /// The enumerator. public IEnumerator GetEnumerator () { return values.GetEnumerator(); } [EditorBrowsable(EditorBrowsableState.Never)] bool ICollection.Contains (TValue item) { return values.Contains(item); } [EditorBrowsable(EditorBrowsableState.Never)] void ICollection.Add (TValue item) { throw new NotSupportedException(EditReadOnlyListMessage); } [EditorBrowsable(EditorBrowsableState.Never)] void ICollection.Clear () { throw new NotSupportedException(EditReadOnlyListMessage); } [EditorBrowsable(EditorBrowsableState.Never)] bool ICollection.IsReadOnly { get { return true; } } [EditorBrowsable(EditorBrowsableState.Never)] bool ICollection.Remove (TValue item) { throw new NotSupportedException(EditReadOnlyListMessage); } IEnumerator IEnumerable.GetEnumerator () { return GetEnumerator(); } } } internal class OrderedDictionaryDebugView { private readonly OrderedDictionary dictionary; public OrderedDictionaryDebugView (OrderedDictionary dictionary) { this.dictionary = dictionary; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public KeyValuePair[] Items { get { return dictionary.ToArray(); } } } /// /// Provides extensions methods for constructing instances of . /// public static class CollectionExtensions { #region ToOrderedDictionary /// /// Creates a new OrderedDictionary from the given collection, using the key selector to extract the key. /// /// The type of the items in the collection. /// The type of the key. /// The items to created the OrderedDictionary from. /// A delegate that can extract a key from an item in the collection. /// An OrderedDictionary mapping the extracted keys to their values. public static OrderedDictionary ToOrderedDictionary (this IEnumerable source, Func keySelector) { return ToOrderedDictionary(source, keySelector, null); } /// /// Creates a new OrderedDictionary from the given collection, using the key selector to extract the key. /// The key comparer is passed to the OrderedDictionary for comparing the extracted keys. /// /// The type of the items in the collection. /// The type of the key. /// The items to created the OrderedDictionary from. /// A delegate that can extract a key from an item in the collection. /// The key equality comparer to use to compare keys in the dictionary. /// An OrderedDictionary mapping the extracted keys to their values. public static OrderedDictionary ToOrderedDictionary ( this IEnumerable source, Func keySelector, IEqualityComparer comparer) { if (source == null) { throw new ArgumentNullException("source"); } if (keySelector == null) { throw new ArgumentNullException("keySelector"); } var dictionary = new OrderedDictionary(comparer); foreach (TSource item in source) { TKey key = keySelector(item); dictionary.Add(key, item); } return dictionary; } #endregion } }