//----------------------------------------------------------------------- // // Copyright (c) Andrew Arnott. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOAuth.Messaging.Reflection { using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; /// /// Wraps an instance in a dictionary that /// provides access to both well-defined message properties and "extra" /// name/value pairs that have no properties associated with them. /// internal class MessageDictionary : IDictionary { private IProtocolMessage message; private MessageDescription description; internal MessageDictionary(IProtocolMessage message) { if (message == null) { throw new ArgumentNullException("message"); } this.message = message; this.description = MessageDescription.Get(message.GetType()); } #region IDictionary Members public void Add(string key, string value) { if (value == null) { throw new ArgumentNullException("value"); } MessagePart part; if (this.description.Mapping.TryGetValue(key, out part)) { if (part.IsNondefaultValueSet(this.message)) { throw new ArgumentException(MessagingStrings.KeyAlreadyExists); } part.SetValue(this.message, value); } else { this.message.ExtraData.Add(key, value); } } public bool ContainsKey(string key) { return this.message.ExtraData.ContainsKey(key) || (this.description.Mapping.ContainsKey(key) && this.description.Mapping[key].GetValue(this.message) != null); } public ICollection Keys { get { List keys = new List(this.message.ExtraData.Count + this.description.Mapping.Count); foreach (var pair in this.description.Mapping) { // Don't include keys with null values, but default values for structs is ok if (pair.Value.GetValue(this.message) != null) { keys.Add(pair.Key); } } foreach (string key in this.message.ExtraData.Keys) { keys.Add(key); } return keys.AsReadOnly(); } } public bool Remove(string key) { if (this.message.ExtraData.Remove(key)) { return true; } else { MessagePart part; if (this.description.Mapping.TryGetValue(key, out part)) { if (part.GetValue(this.message) != null) { part.SetValue(this.message, null); return true; } } return false; } } public bool TryGetValue(string key, out string value) { MessagePart part; if (this.description.Mapping.TryGetValue(key, out part)) { value = part.GetValue(this.message); return true; } return this.message.ExtraData.TryGetValue(key, out value); } public ICollection Values { get { List values = new List(this.message.ExtraData.Count + this.description.Mapping.Count); foreach (MessagePart part in this.description.Mapping.Values) { if (part.GetValue(this.message) != null) { values.Add(part.GetValue(this.message)); } } foreach (string value in this.message.ExtraData.Values) { Debug.Assert(value != null, "Null values should never be allowed in the extra data dictionary."); values.Add(value); } return values.AsReadOnly(); } } public string this[string key] { get { MessagePart part; if (this.description.Mapping.TryGetValue(key, out part)) { // Never throw KeyNotFoundException for declared properties. return part.GetValue(this.message); } else { return this.message.ExtraData[key]; } } set { MessagePart part; if (this.description.Mapping.TryGetValue(key, out part)) { part.SetValue(this.message, value); } else { if (value == null) { this.message.ExtraData.Remove(key); } else { this.message.ExtraData[key] = value; } } } } #endregion #region ICollection> Members public void Add(KeyValuePair item) { this.Add(item.Key, item.Value); } public void Clear() { foreach (string key in this.Keys) { this.Remove(key); } } public bool Contains(KeyValuePair item) { MessagePart part; if (this.description.Mapping.TryGetValue(item.Key, out part)) { return string.Equals(part.GetValue(this.message), item.Value, StringComparison.Ordinal); } else { return this.message.ExtraData.Contains(item); } } void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { foreach (var pair in (IDictionary)this) { array[arrayIndex++] = pair; } } public int Count { get { return this.Keys.Count; } } bool ICollection>.IsReadOnly { get { return false; } } public bool Remove(KeyValuePair item) { // We use contains because that checks that the value is equal as well. if (((ICollection>)this).Contains(item)) { ((IDictionary)this).Remove(item.Key); return true; } return false; } #endregion #region IEnumerable> Members public IEnumerator> GetEnumerator() { foreach (string key in Keys) { yield return new KeyValuePair(key, this[key]); } } #endregion #region IEnumerable Members IEnumerator System.Collections.IEnumerable.GetEnumerator() { return ((IEnumerable>)this).GetEnumerator(); } #endregion } }