#region CPL License /* Nuclex Framework Copyright (C) 2002-2012 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #endregion 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 : IEnumerator>, IDictionaryEnumerator { /// 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 ); } } /// /// 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"); } } /// 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 /// 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 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) { this.typedDictionary.Remove(key); } ICollection currentValues; if(this.typedDictionary.TryGetValue(key, out currentValues)) { ValueList currentValueList = (ValueList)currentValues; int index = 0; foreach(TValue addedValue in value) { if(index < currentValueList.Count) { TValue original = currentValueList[index]; currentValueList[index] = addedValue; OnReplaced( new KeyValuePair(key, original), new KeyValuePair(key, addedValue) ); } else { currentValueList.Add(addedValue); OnAdded(new KeyValuePair(key, addedValue)); } ++index; } int count = currentValueList.Count; while(count > index) { --count; TValue removedValue = currentValueList[count]; currentValueList.RemoveAt(count); OnRemoved(new KeyValuePair(key, removedValue)); } } else { currentValues = new ValueList(this); this.typedDictionary.Add(key, currentValues); foreach(TValue addedValue in value) { currentValues.Add(addedValue); OnAdded(new KeyValuePair(key, 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); OnAdded(new KeyValuePair(key, 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); } OnRemoved(new KeyValuePair(key, value)); 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); foreach(TValue value in values) { OnRemoved(new KeyValuePair(key, value)); } return values.Count; } else { return 0; } } /// Removes all items from the Dictionary public void Clear() { OnClearing(); this.typedDictionary.Clear(); this.count = 0; OnCleared(); } /// Fires the 'ItemAdded' event /// Item that has been added to the collection protected virtual void OnAdded(KeyValuePair item) { } /// Fires the 'ItemRemoved' event /// Item that has been removed from the collection protected virtual void OnRemoved(KeyValuePair item) { } /// Fires the 'ItemReplaced' event /// Item that was replaced in the collection /// Item that the original was replaced by protected virtual void OnReplaced( KeyValuePair oldItem, KeyValuePair newItem ) { } /// Fires the 'Clearing' event protected virtual void OnClearing() { } /// Fires the 'Cleared' event protected virtual void OnCleared() { } /// 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