#region Apache License 2.0 /* Nuclex .NET Framework Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #endregion // Apache License 2.0 using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; namespace Nuclex.Support.Collections { /// Dictionary that can contain multiple values under the same key /// Type of keys used within the dictionary /// Type of values used within the dictionary public partial class MultiDictionary : IMultiDictionary { #region class Enumerator /// Enumerates the values stored in a multi dictionary private class Enumerator : IDictionaryEnumerator, IEnumerator> { /// Initializes a new multi dictionary enumerator /// Dictionary that will be enumerated public Enumerator(MultiDictionary dictionary) { this.dictionary = dictionary; Reset(); } /// The current entry the enumerator is pointing at public KeyValuePair Current { get { if(this.currentValue == null) { throw new InvalidOperationException("Enumerator is not on a valid position"); } return new KeyValuePair( this.currentCollection.Current.Key, this.currentValue.Current ); } } /// Immediately releases all resources owned by the instance public void Dispose() { if(this.currentValue != null) { this.currentValue.Dispose(); this.currentValue = null; } if(this.currentCollection != null) { this.currentCollection.Dispose(); this.currentCollection = null; } } /// Advances the enumerator to the entry /// /// True if there was a next entry, false if the end of the set has been reached /// public bool MoveNext() { if(this.currentCollection == null) { return false; } for(; ; ) { // Try to move the enumerator in the current key's list to the next item if(this.currentValue != null) { if(this.currentValue.MoveNext()) { return true; // We found the next item } else { this.currentValue.Dispose(); } } // Enumerator for the current key's list reached the end, go to the next key if(this.currentCollection.MoveNext()) { this.currentValue = this.currentCollection.Current.Value.GetEnumerator(); } else { this.currentValue = null; // Guaranteed to be disposed already this.currentCollection.Dispose(); this.currentCollection = null; return false; } } } /// Resets the enumerator to its initial position public void Reset() { if(this.currentValue != null) { this.currentValue.Dispose(); this.currentValue = null; } if(this.currentCollection != null) { this.currentCollection.Dispose(); } this.currentCollection = this.dictionary.GetEnumerator(); } #region IEnumerator implementation /// The item the enumerator is currently pointing at object IEnumerator.Current { get { return Current; } } #endregion // IEnumerator implementation #region IDictionaryEnumerator implementation /// The current entry the enumerator is pointing to DictionaryEntry IDictionaryEnumerator.Entry { get { enforceEnumeratorOnValidPosition(); return new DictionaryEntry( this.currentCollection.Current.Key, this.currentValue.Current ); } } /// The current dictionary key object IDictionaryEnumerator.Key { get { enforceEnumeratorOnValidPosition(); return this.currentCollection.Current.Key; } } /// The current dictionary value object IDictionaryEnumerator.Value { get { enforceEnumeratorOnValidPosition(); return this.currentValue.Current; } } #endregion // IDictionaryEnumerator implementation /// /// Throws an exception if the enumerator is not on a valid position /// private void enforceEnumeratorOnValidPosition() { if(this.currentValue == null) { throw new InvalidOperationException("Enumerator is not on a valid position"); } } /// Dictionary over whose entries the enumerator is enumerating private IDictionary> dictionary; /// Current key the enumerator is at private IEnumerator>> currentCollection; /// Current value in the current key the enumerator is at private IEnumerator currentValue; } #endregion // class Enumerator #region class ValueList /// Stores the list of values for a dictionary key private class ValueList : Collection { /// Initializes a new value list /// Dictionary the value list belongs to public ValueList(MultiDictionary dictionary) { this.dictionary = dictionary; } /// Called when the value list is being cleared protected override void ClearItems() { this.dictionary.count -= Count; base.ClearItems(); } /// Called when an item is inserted into the value list /// Index at which the item is being inserted /// Item that is being inserted protected override void InsertItem(int index, TValue item) { base.InsertItem(index, item); ++this.dictionary.count; } /// Called when an item is removed from the value list /// Index at which the item is being removed protected override void RemoveItem(int index) { base.RemoveItem(index); --this.dictionary.count; } /// The dictionary the value list belongs to private MultiDictionary dictionary; } #endregion // class ValueList /// Initializes a new multi dictionary public MultiDictionary() : this(new Dictionary>()) { } /// Initializes a new multi dictionary /// Dictionary the multi dictionary will be based on internal MultiDictionary(IDictionary> dictionary) { this.typedDictionary = dictionary; this.objectDictionary = (this.typedDictionary as IDictionary); foreach(ICollection values in dictionary.Values) { this.count += values.Count; } } /// Whether the dictionary is write-protected public bool IsReadOnly { get { return this.typedDictionary.IsReadOnly; } } /// Determines the number of values stored under the specified key /// Key whose values will be counted /// The number of values stored under the specified key public int CountValues(TKey key) { ICollection values; if(this.typedDictionary.TryGetValue(key, out values)) { return values.Count; } else { return 0; } } /// /// Determines whether the specified KeyValuePair is contained in the dictionary /// /// KeyValuePair that will be checked for /// True if the provided KeyValuePair was contained in the dictionary public bool Contains(KeyValuePair item) { ICollection values; if(this.typedDictionary.TryGetValue(item.Key, out values)) { return values.Contains(item.Value); } else { return false; } } /// Determines whether the Dictionary contains the specified key /// Key that will be checked for /// /// True if an entry with the specified key was contained in the Dictionary /// public bool ContainsKey(TKey key) { return this.typedDictionary.ContainsKey(key); } /// Copies the contents of the Dictionary into an array /// Array the Dictionary will be copied into /// /// Starting index at which to begin filling the destination array /// public void CopyTo(KeyValuePair[] array, int arrayIndex) { foreach(KeyValuePair> item in this.typedDictionary) { foreach(TValue value in item.Value) { array[arrayIndex] = new KeyValuePair(item.Key, value); ++arrayIndex; } } } /// Number of elements contained in the multi dictionary public int Count { get { return this.count; } } /// Creates a new enumerator for the dictionary /// The new dictionary enumerator public IEnumerator> GetEnumerator() { return new Enumerator(this); } /// Collection of all keys contained in the dictionary public ICollection Keys { get { return this.typedDictionary.Keys; } } /// Collection of all values contained in the dictionary public ICollection Values { get { if(this.valueCollection == null) { this.valueCollection = new ValueCollection(this); } return this.valueCollection; } } /// /// Attempts to retrieve the item with the specified key from the dictionary /// /// Key of the item to attempt to retrieve /// /// Output parameter that will receive the values upon successful completion /// /// /// True if the item was found and has been placed in the output parameter /// public bool TryGetValue(TKey key, out ICollection values) { return this.typedDictionary.TryGetValue(key, out values); } /// Accesses an item in the dictionary by its key /// Key of the item that will be accessed public ICollection this[TKey key] { get { return this.typedDictionary[key]; } set { if(value == null) { RemoveKey(key); } else { ICollection currentValues; if(this.typedDictionary.TryGetValue(key, out currentValues)) { currentValues.Clear(); } else { currentValues = new ValueList(this); this.typedDictionary.Add(key, currentValues); } foreach(TValue addedValue in value) { currentValues.Add(addedValue); } } } } /// Inserts an item into the dictionary /// Key under which to add the new item /// Item that will be added to the dictionary public void Add(TKey key, TValue value) { ICollection values; if(!this.typedDictionary.TryGetValue(key, out values)) { values = new ValueList(this); this.typedDictionary.Add(key, values); } values.Add(value); } /// /// Removes the item with the specified key and value from the dictionary /// /// Key of the item that will be removed /// Value of the item that will be removed /// /// True if the specified item was contained in the dictionary and was removed /// /// If the dictionary is read-only public bool Remove(TKey key, TValue value) { ICollection values; if(this.typedDictionary.TryGetValue(key, out values)) { values.Remove(value); if(values.Count == 0) { this.typedDictionary.Remove(key); } return true; } else { return false; } } /// Removes all items with the specified key from the dictionary /// Key of the item that will be removed /// The number of items that have been removed from the dictionary /// If the dictionary is read-only public int RemoveKey(TKey key) { ICollection values; if(this.typedDictionary.TryGetValue(key, out values)) { this.count -= values.Count; this.typedDictionary.Remove(key); return values.Count; } else { return 0; } } /// Removes all items from the Dictionary public void Clear() { this.typedDictionary.Clear(); this.count = 0; } /// The wrapped Dictionary under its type-safe interface private IDictionary> typedDictionary; /// The wrapped Dictionary under its object interface private IDictionary objectDictionary; /// The number of items currently in the multi dictionary private int count; /// Provides the values stores in the dictionary in sequence private ValueCollection valueCollection; } } // namespace Nuclex.Support.Collections