#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