File: net\System\Net\mail\TrackingValidationObjectDictionary.cs
Project: ndp\fx\src\System.csproj (System)
//-----------------------------------------------------------------------------
// <copyright file="TrackingValidationObjectDicionary.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//-----------------------------------------------------------------------------
 
namespace System.Net
{
    using System;
    using System.Collections;
    using System.Collections.Specialized;
    using System.Collections.Generic;
    using System.Diagnostics;
 
    // TrackingValidationObjectDictionary uses an internal collection of objects to store
    // only those objects which are not strings.  It still places a copy of the string
    // value of these objects into the base StringDictionary so that the public methods
    // of StringDictionary still function correctly.  
    // NOTE:  all keys are converted to lowercase prior to adding to ensure consistency of 
    // values between  keys in the StringDictionary and internalObjects because StringDictionary
    // automatically does this internally
    internal class TrackingValidationObjectDictionary : StringDictionary
    {
        #region Private Fields
 
        // even though validators may exist, we should not initialize this initially since by default it is empty
        // and it may never be populated with values if the user does not set them
        private IDictionary<string, object> internalObjects = null;
        private readonly IDictionary<string, ValidateAndParseValue> validators;
 
        #endregion
 
        #region Constructors
 
        // it is valid for validators to be null.  this means that no validation should be performed
        internal TrackingValidationObjectDictionary(IDictionary<string, ValidateAndParseValue> validators)
        {
            IsChanged = false;
            this.validators = validators;
        }
 
        #endregion
 
        #region Private Methods
 
        // precondition:  key must not be null
        // addValue determines if we are doing a set (false) or an add (true)
        private void PersistValue(string key, string value, bool addValue)
        {
            Debug.Assert(key != null, "key was null");
 
            // StringDictionary will automatically store the key as lower case so
            // we must convert it so that the validators and internalObjects will
            // be consistent
            key = key.ToLowerInvariant();
           
            // StringDictionary allows keys with null values however null values for parameters in 
            // ContentDisposition have no meaning so they must be ignored on add. StringDictionary 
            // would not throw on null so this can't either since it would be a breaking change.
            // in addition, a key with an empty value is not valid so we do not persist those either
            if (!string.IsNullOrEmpty(value))
            {               
                if (validators != null && validators.ContainsKey(key))
                {
                    // run the validator for this key; it will throw if the value is invalid
                    object valueToAdd = validators[key](value);
 
                    // now that the value is valid, ensure that internalObjects exists since we have to 
                    // add to it
                    if (internalObjects == null)
                    {
                        internalObjects = new Dictionary<string, object>();
                    }
 
                    if (addValue)
                    {
                        // set will do an Add if the key does not exist but if the user
                        // specifically called Add then we must let it throw
                        internalObjects.Add(key, valueToAdd);
                        base.Add(key, valueToAdd.ToString());
                    }
                    else
                    {
                        internalObjects[key] = valueToAdd;
                        base[key] = valueToAdd.ToString();
                    }
                }
                else
                {
                    if (addValue)
                    {
                        base.Add(key, value);
                    }
                    else
                    {
                        base[key] = value;
                    }
                }
                IsChanged = true;
            }
        }
 
        #endregion
 
        #region Internal Fields
 
        // set to true if any values have been changed by any mutator method
        internal bool IsChanged
        {
            get;
            set;
        }
 
        // delegate to perform validation and conversion if necessary
        // these MUST throw on invalid values.  Additionally, each validator
        // may be passed a string OR another type of object and so should react
        // appropriately
        internal delegate object ValidateAndParseValue(object valueToValidate);
 
        #endregion
 
        #region Internal Methods
 
        // public interface only allows strings so this provides a means 
        // to get the objects when they are not strings
        internal object InternalGet(string key)
        {
            // internalObjects will throw if the key is not found so we must check it
            if (internalObjects != null && internalObjects.ContainsKey(key))
            {
                return internalObjects[key];
            }
            else
            {
                // this will return null if the key does not exist so no check needed
                return base[key];
            }
        }
 
        // this method bypasses validation
        // preconditions: value MUST have been validated and must not be null        
        internal void InternalSet(string key, object value)
        {
            // InternalSet is only used with objects that belong in internalObjects so we must always
            // initialize it here
            if (internalObjects == null)
            {
                internalObjects = new Dictionary<string, object>();
            }
 
            // always replace the existing value when we set internally
            internalObjects[key] = value;
            base[key] = value.ToString();
            IsChanged = true;
        }
 
        #endregion
 
        #region Public Fields
 
        public override string this[string key]
        {
            get
            {
                // no need to check internalObjects since the string equivalent in base will 
                // already have been set correctly when the value was originally passed in
                return base[key];
            }
            set
            {
                PersistValue(key, value, false);
            }
        }
 
        #endregion
 
        #region Public Methods
 
        public override void Add(string key, string value)
        {
            PersistValue(key, value, true);
        }
 
        public override void Clear()
        {
            if (internalObjects != null)
            {
                internalObjects.Clear();
            }
            base.Clear();
            IsChanged = true;
        }
 
        public override void Remove(string key)
        {
            if (internalObjects != null && internalObjects.ContainsKey(key))
            {
                internalObjects.Remove(key);
            }
            base.Remove(key);
            IsChanged = true;
        }
 
        #endregion
    }
}