File: System\Configuration\ConfigurationLockCollection.cs
Web Access
Project: src\src\libraries\System.Configuration.ConfigurationManager\src\System.Configuration.ConfigurationManager.csproj (System.Configuration.ConfigurationManager)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections;
using System.Collections.Specialized;
using System.Text;
 
namespace System.Configuration
{
    public sealed class ConfigurationLockCollection : ICollection
    {
        private const string LockAll = "*";
        private readonly string _ignoreName;
        private readonly ConfigurationElement _thisElement;
        private readonly ArrayList _internalArraylist;
        private readonly HybridDictionary _internalDictionary;
        private string _seedList = string.Empty;
 
        internal ConfigurationLockCollection(ConfigurationElement thisElement)
            : this(thisElement, ConfigurationLockCollectionType.LockedAttributes)
        { }
 
        internal ConfigurationLockCollection(ConfigurationElement thisElement, ConfigurationLockCollectionType lockType)
            : this(thisElement, lockType, string.Empty)
        { }
 
        internal ConfigurationLockCollection(ConfigurationElement thisElement, ConfigurationLockCollectionType lockType,
            string ignoreName)
            : this(thisElement, lockType, ignoreName, null)
        { }
 
        internal ConfigurationLockCollection(ConfigurationElement thisElement, ConfigurationLockCollectionType lockType,
            string ignoreName, ConfigurationLockCollection parentCollection)
        {
            _thisElement = thisElement;
            LockType = lockType;
            _internalDictionary = new HybridDictionary();
            _internalArraylist = new ArrayList();
            IsModified = false;
 
            ExceptionList = (LockType == ConfigurationLockCollectionType.LockedExceptionList) ||
                (LockType == ConfigurationLockCollectionType.LockedElementsExceptionList);
            _ignoreName = ignoreName;
 
            if (parentCollection == null) return;
 
            foreach (string key in parentCollection) // seed the new collection
            {
                Add(key, ConfigurationValueFlags.Inherited); // add the local copy
                if (!ExceptionList) continue;
 
                if (_seedList.Length != 0)
                    _seedList += ",";
                _seedList += key;
            }
        }
 
        internal ConfigurationLockCollectionType LockType { get; }
 
        public bool IsModified { get; private set; }
 
        internal bool ExceptionList { get; }
 
        public string AttributeList
        {
            get
            {
                StringBuilder sb = new StringBuilder();
 
                foreach (DictionaryEntry de in _internalDictionary)
                {
                    if (sb.Length != 0) sb.Append(',');
                    sb.Append(de.Key);
                }
                return sb.ToString();
            }
        }
 
        public bool HasParentElements
        {
            get
            {
                // return true if there is at least one element that was defined in the parent
                bool result = false;
 
                // Check to see if the exception list is empty as a result of a merge from config
                // If so the there were some parent elements because empty string is invalid in config.
                // and the only way to get an empty list is for the merged config to have no elements
                // in common.
                if (ExceptionList && (_internalDictionary.Count == 0) && !string.IsNullOrEmpty(_seedList))
                    return true;
 
                foreach (DictionaryEntry de in _internalDictionary)
                    if (((ConfigurationValueFlags)de.Value & ConfigurationValueFlags.Inherited) != 0)
                    {
                        result = true;
                        break;
                    }
 
                return result;
            }
        }
 
        public int Count => _internalDictionary.Count;
 
        public bool IsSynchronized => false;
 
        public object SyncRoot => this;
 
        void ICollection.CopyTo(Array array, int index)
        {
            _internalArraylist.CopyTo(array, index);
        }
 
        public IEnumerator GetEnumerator()
        {
            return _internalArraylist.GetEnumerator();
        }
 
        internal void ClearSeedList()
        {
            _seedList = string.Empty;
        }
 
        public void Add(string name)
        {
            if (((_thisElement.ItemLocked & ConfigurationValueFlags.Locked) != 0) &&
                ((_thisElement.ItemLocked & ConfigurationValueFlags.Inherited) != 0))
                throw new ConfigurationErrorsException(SR.Format(SR.Config_base_attribute_locked, name));
 
            ConfigurationValueFlags flags = ConfigurationValueFlags.Modified;
 
            string attribToLockTrim = name.Trim();
            ConfigurationProperty propToLock = _thisElement.Properties[attribToLockTrim];
            if ((propToLock == null) && (attribToLockTrim != LockAll))
            {
                ConfigurationElementCollection collection = _thisElement as ConfigurationElementCollection;
                if ((collection == null) && (_thisElement.Properties.DefaultCollectionProperty != null))
                {
                    // this is not a collection but it may contain a default collection
                    collection =
                        _thisElement[_thisElement.Properties.DefaultCollectionProperty] as
                            ConfigurationElementCollection;
                }
 
                if ((collection == null) ||
                    (LockType == ConfigurationLockCollectionType.LockedAttributes) ||
                    // If the collection type is not element then the lock is bogus
                    (LockType == ConfigurationLockCollectionType.LockedExceptionList))
                    _thisElement.ReportInvalidLock(attribToLockTrim, LockType, null, null);
                else
                {
                    if (!collection.IsLockableElement(attribToLockTrim))
                        _thisElement.ReportInvalidLock(attribToLockTrim, LockType, null, collection.LockableElements);
                }
            }
            else
            {
                // the lock is in the property bag but is it the correct type?
                if ((propToLock != null) && propToLock.IsRequired)
                {
                    throw new ConfigurationErrorsException(SR.Format(SR.Config_base_required_attribute_lock_attempt,
                        propToLock.Name));
                }
 
                if (attribToLockTrim != LockAll)
                {
                    if ((LockType == ConfigurationLockCollectionType.LockedElements) ||
                        (LockType == ConfigurationLockCollectionType.LockedElementsExceptionList))
                    {
                        // If it is an element then it must be derived from ConfigurationElement
                        if (!typeof(ConfigurationElement).IsAssignableFrom(propToLock?.Type))
                            _thisElement.ReportInvalidLock(attribToLockTrim, LockType, null, null);
                    }
                    else
                    {
                        // if it is a property then it cannot be derived from ConfigurationElement
                        if (typeof(ConfigurationElement).IsAssignableFrom(propToLock?.Type))
                            _thisElement.ReportInvalidLock(attribToLockTrim, LockType, null, null);
                    }
                }
            }
 
            if (_internalDictionary.Contains(name))
            {
                flags = ConfigurationValueFlags.Modified | (ConfigurationValueFlags)_internalDictionary[name];
                _internalDictionary.Remove(name); // not from parent
                _internalArraylist.Remove(name);
            }
            _internalDictionary.Add(name, flags); // not from parent
            _internalArraylist.Add(name);
            IsModified = true;
        }
 
        internal void Add(string name, ConfigurationValueFlags flags)
        {
            if ((flags != ConfigurationValueFlags.Inherited) && _internalDictionary.Contains(name))
            {
                // the user has an item declared as locked below a level where it is already locked
                // keep enough info so we can write out the lock if they save in modified mode
                flags = ConfigurationValueFlags.Modified | (ConfigurationValueFlags)_internalDictionary[name];
                _internalDictionary.Remove(name);
                _internalArraylist.Remove(name);
            }
 
            _internalDictionary.Add(name, flags); // not from parent
            _internalArraylist.Add(name);
        }
 
        internal bool DefinedInParent(string name)
        {
            if (name == null)
                return false;
 
            if (!ExceptionList)
            {
                return _internalDictionary.Contains(name) &&
                    (((ConfigurationValueFlags)_internalDictionary[name] & ConfigurationValueFlags.Inherited) != 0);
            }
 
            string parentListEnclosed = "," + _seedList + ",";
            if (name.Equals(_ignoreName) ||
#if NET
                parentListEnclosed.Contains("," + name + ",", StringComparison.Ordinal))
#else
                parentListEnclosed.IndexOf("," + name + ",", StringComparison.Ordinal) >= 0)
#endif
                return true;
            return _internalDictionary.Contains(name) &&
                (((ConfigurationValueFlags)_internalDictionary[name] & ConfigurationValueFlags.Inherited) != 0);
        }
 
        internal bool IsValueModified(string name)
        {
            return _internalDictionary.Contains(name) &&
                (((ConfigurationValueFlags)_internalDictionary[name] & ConfigurationValueFlags.Modified) != 0);
        }
 
        internal void RemoveInheritedLocks()
        {
            StringCollection removeList = new StringCollection();
            foreach (string key in this) if (DefinedInParent(key)) removeList.Add(key);
            foreach (string key in removeList)
            {
                _internalDictionary.Remove(key);
                _internalArraylist.Remove(key);
            }
        }
 
        public void Remove(string name)
        {
            if (!_internalDictionary.Contains(name))
                throw new ConfigurationErrorsException(SR.Format(SR.Config_base_collection_entry_not_found, name));
 
            // in a locked list you cannot remove items that were locked in the parent
            // in an exception list this is legal because it makes the list more restrictive
            if ((ExceptionList == false) &&
                (((ConfigurationValueFlags)_internalDictionary[name] & ConfigurationValueFlags.Inherited) != 0))
            {
                if (((ConfigurationValueFlags)_internalDictionary[name] & ConfigurationValueFlags.Modified) == 0)
                    throw new ConfigurationErrorsException(SR.Format(SR.Config_base_attribute_locked, name));
 
                // allow the local one to be "removed" so it won't write out but throw if they try and remove
                // one that is only inherited
                ConfigurationValueFlags flags = (ConfigurationValueFlags)_internalDictionary[name];
                flags &= ~ConfigurationValueFlags.Modified;
                _internalDictionary[name] = flags;
                IsModified = true;
                return;
            }
 
            _internalDictionary.Remove(name);
            _internalArraylist.Remove(name);
            IsModified = true;
        }
 
        internal void ClearInternal(bool useSeedIfAvailable)
        {
            ArrayList removeList = new ArrayList();
            foreach (DictionaryEntry de in _internalDictionary)
                if ((((ConfigurationValueFlags)de.Value & ConfigurationValueFlags.Inherited) == 0)
                    || ExceptionList)
                    removeList.Add(de.Key);
 
            foreach (object removeKey in removeList)
            {
                _internalDictionary.Remove(removeKey);
                _internalArraylist.Remove(removeKey);
            }
 
            // Clearing an Exception list really means revert to parent
            if (useSeedIfAvailable && !string.IsNullOrEmpty(_seedList))
            {
                string[] keys = _seedList.Split(',');
                foreach (string key in keys) Add(key, ConfigurationValueFlags.Inherited);
            }
            IsModified = true;
        }
 
        public void Clear()
        {
            ClearInternal(true);
        }
 
        public bool Contains(string name)
        {
            if (ExceptionList && name.Equals(_ignoreName)) return true;
            return _internalDictionary.Contains(name);
        }
 
        public void CopyTo(string[] array, int index)
        {
            ((ICollection)this).CopyTo(array, index);
        }
 
        internal void ResetModified()
        {
            IsModified = false;
        }
 
        public bool IsReadOnly(string name)
        {
            if (!_internalDictionary.Contains(name))
                throw new ConfigurationErrorsException(SR.Format(SR.Config_base_collection_entry_not_found, name));
            return
                ((ConfigurationValueFlags)_internalDictionary[name] & ConfigurationValueFlags.Inherited) != 0;
        }
 
        public void SetFromList(string attributeList)
        {
            string[] splits = attributeList.Split(',', ';', ':');
            Clear();
            foreach (string name in splits)
            {
                string attribTrim = name.Trim();
                if (!Contains(attribTrim)) Add(attribTrim);
            }
        }
    }
}