#pragma warning disable CA1863 // Cache a 'CompositeFormat' for repeated use in this formatting operation #nullable disable // based on https://github.com/jehugaleahsa/truncon.collections.OrderedDictionary // https://github.com/jehugaleahsa/truncon.collections.OrderedDictionary/blob/master/UNLICENSE.txt using System.Collections; using System.ComponentModel; using System.Diagnostics; using System.Globalization; namespace Jint.Runtime; /// /// 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}")] internal sealed class OrderedDictionary : IDictionary, IList> where TKey : class where TValue : class { private readonly Dictionary dictionary; private readonly List keys; private const string ArrayTooSmall = "The given array was too small to hold the items."; private const string EditReadOnlyList = "An attempt was made to edit a read-only list."; private const string IndexOutOfRange = "The index is negative or outside the bounds of the collection."; private const string TooSmall = "The given value cannot be less than {0}."; /// /// Initializes a new instance of an OrderedDictionary. /// public OrderedDictionary() { dictionary = new Dictionary(); keys = new List(); } /// /// Initializes a new instance of an OrderedDictionary. /// /// The initial capacity of the dictionary. /// The capacity is less than zero. public OrderedDictionary(int capacity) { dictionary = new Dictionary(capacity); keys = new List(capacity); } /// /// Initializes a new instance of an OrderedDictionary. /// /// The equality comparer to use to compare keys. public OrderedDictionary(IEqualityComparer comparer) { dictionary = new Dictionary(comparer); keys = new List(); } /// /// 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); keys = new List(capacity); } /// /// 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, value); keys.Add(key); } /// /// 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 > dictionary.Count) { Throw.ArgumentOutOfRangeException(nameof(index), IndexOutOfRange); } dictionary.Add(key, value); keys.Insert(index, key); } /// /// 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) { if (!dictionary.ContainsKey(key)) { return -1; } var keysCount = keys.Count; for (int i = 0; i < keysCount; ++i) { if (dictionary.Comparer.Equals(keys[i], key)) { return i; } } return -1; } /// /// Gets the keys in the dictionary in the order they were added. /// public KeyCollection Keys => new KeyCollection(this); /// /// 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. /// This operation runs in O(n). public bool Remove(TKey key) { if (dictionary.Remove(key)) { var keysCount = keys.Count; for (int i = 0; i < keysCount; ++i) { if (dictionary.Comparer.Equals(keys[i], key)) { keys.RemoveAt(i); break; } } 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. /// This operation runs in O(n). public void RemoveAt(int index) { TKey key = keys[index]; dictionary.Remove(key); keys.RemoveAt(index); } /// /// 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) { return dictionary.TryGetValue(key, out value); } /// /// Gets the values in the dictionary. /// public ValueCollection Values => new ValueCollection(this); /// /// 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 => dictionary[keys[index]]; set => dictionary[keys[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 => dictionary[key]; set { if (!dictionary.ContainsKey(key)) { keys.Add(key); } dictionary[key] = value; } } /// /// Removes all key/value pairs from the dictionary. /// public void Clear() { dictionary.Clear(); keys.Clear(); } /// /// Gets the number of key/value pairs in the dictionary. /// public int Count => 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() { foreach (TKey key in keys) { yield return new KeyValuePair(key, dictionary[key]); } } int IList>.IndexOf(KeyValuePair item) { if (!dictionary.TryGetValue(item.Key, out var value)) { return -1; } if (!Equals(item.Value, value)) { return -1; } var keysCount = keys.Count; for (int i = 0; i < keysCount; ++i) { if (dictionary.Comparer.Equals(keys[i], item.Key)) { return i; } } return -1; } void IList>.Insert(int index, KeyValuePair item) { if (index < 0 || index > dictionary.Count) { Throw.ArgumentOutOfRangeException(nameof(index), IndexOutOfRange); } dictionary.Add(item.Key, item.Value); keys.Insert(index, item.Key); } KeyValuePair IList>.this[int index] { get { TKey key = keys[index]; TValue value = dictionary[key]; return new KeyValuePair(key, value); } set { TKey key = keys[index]; if (dictionary.Comparer.Equals(key, value.Key)) { dictionary[value.Key] = value.Value; } else { dictionary.Add(value.Key, value.Value); dictionary.Remove(key); keys[index] = value.Key; } } } ICollection IDictionary.Keys => Keys; ICollection IDictionary.Values => Values; void ICollection>.Add(KeyValuePair item) { dictionary.Add(item.Key, item.Value); keys.Add(item.Key); } bool ICollection>.Contains(KeyValuePair item) { if (!dictionary.TryGetValue(item.Key, out var value)) { return false; } return Equals(value, item.Value); } void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { if (array == null) { Throw.ArgumentNullException(nameof(array)); return; } if (arrayIndex < 0) { Throw.ArgumentOutOfRangeException(nameof(arrayIndex), string.Format(CultureInfo.CurrentCulture, TooSmall, 0)); } if (dictionary.Count > array.Length - arrayIndex) { Throw.ArgumentException(ArrayTooSmall, nameof(array)); } foreach (TKey key in keys) { TValue value = dictionary[key]; array[arrayIndex] = new KeyValuePair(key, value); ++arrayIndex; } } bool ICollection>.IsReadOnly => false; bool ICollection>.Remove(KeyValuePair item) { if (!dictionary.TryGetValue(item.Key, out var value)) { return false; } if (!Equals(item.Value, value)) { return false; } // O(n) dictionary.Remove(item.Key); var keysCount = keys.Count; for (int i = 0; i < keysCount; ++i) { if (dictionary.Comparer.Equals(keys[i], item.Key)) { keys.RemoveAt(i); } } return true; } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Wraps the keys in an OrderDictionary. /// public sealed class KeyCollection : ICollection { private readonly OrderedDictionary parent; /// /// Initializes a new instance of a KeyCollection. /// /// The OrderedDictionary whose keys to wrap. /// The dictionary is null. public KeyCollection(OrderedDictionary dictionary) { parent = 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) { parent.keys.CopyTo(array, arrayIndex); } /// /// Gets the number of keys in the OrderedDictionary. /// public int Count { get { return parent.dictionary.Count; } } /// /// Gets an enumerator over the keys in the OrderedDictionary. /// /// The enumerator. public IEnumerator GetEnumerator() { return parent.keys.GetEnumerator(); } [EditorBrowsable(EditorBrowsableState.Never)] bool ICollection.Contains(TKey item) { return parent.dictionary.ContainsKey(item); } [EditorBrowsable(EditorBrowsableState.Never)] void ICollection.Add(TKey item) { Throw.NotSupportedException(EditReadOnlyList); } [EditorBrowsable(EditorBrowsableState.Never)] void ICollection.Clear() { Throw.NotSupportedException(EditReadOnlyList); } [EditorBrowsable(EditorBrowsableState.Never)] bool ICollection.IsReadOnly { get { return true; } } [EditorBrowsable(EditorBrowsableState.Never)] bool ICollection.Remove(TKey item) { Throw.NotSupportedException(EditReadOnlyList); return false; } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } /// /// Wraps the keys in an OrderDictionary. /// public sealed class ValueCollection : ICollection { private readonly OrderedDictionary parent; /// /// Initializes a new instance of a ValueCollection. /// /// The OrderedDictionary whose keys to wrap. /// The dictionary is null. public ValueCollection(OrderedDictionary dictionary) { parent = dictionary; } /// /// 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) { if (arrayIndex < 0) { Throw.ArgumentOutOfRangeException(nameof(arrayIndex), string.Format(CultureInfo.InvariantCulture, TooSmall, 0)); } if (parent.dictionary.Count > array.Length - arrayIndex) { Throw.ArgumentException(ArrayTooSmall, nameof(array)); } foreach (TKey key in parent.keys) { TValue value = parent.dictionary[key]; array[arrayIndex] = value; ++arrayIndex; } } /// /// Gets the number of values in the OrderedDictionary. /// public int Count => parent.dictionary.Count; /// /// Gets an enumerator over the values in the OrderedDictionary. /// /// The enumerator. public IEnumerator GetEnumerator() { foreach (TKey key in parent.keys) { TValue value = parent.dictionary[key]; yield return value; } } [EditorBrowsable(EditorBrowsableState.Never)] bool ICollection.Contains(TValue item) { return parent.dictionary.ContainsValue(item); } [EditorBrowsable(EditorBrowsableState.Never)] void ICollection.Add(TValue item) { Throw.NotSupportedException(EditReadOnlyList); } [EditorBrowsable(EditorBrowsableState.Never)] void ICollection.Clear() { Throw.NotSupportedException(EditReadOnlyList); } [EditorBrowsable(EditorBrowsableState.Never)] bool ICollection.IsReadOnly => true; [EditorBrowsable(EditorBrowsableState.Never)] bool ICollection.Remove(TValue item) { Throw.NotSupportedException(EditReadOnlyList); return false; } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } }