File: System\Configuration\ConfigurationElementCollection.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.Diagnostics;
using System.Xml;
 
namespace System.Configuration
{
    [DebuggerDisplay("Count = {Count}")]
    public abstract class ConfigurationElementCollection : ConfigurationElement, ICollection
    {
        internal const string DefaultAddItemName = "add";
        internal const string DefaultRemoveItemName = "remove";
        internal const string DefaultClearItemsName = "clear";
        private readonly IComparer _comparer;
        private string _addElement = DefaultAddItemName;
        private string _clearElement = DefaultClearItemsName;
        private bool _collectionCleared;
        private bool _emitClearTag;
        private int _inheritedCount; // Total number of inherited items
        private bool _modified;
        private bool _readOnly;
 
        private int _removedItemCount; // Number of items removed for this collection (not including parent)
        private string _removeElement = DefaultRemoveItemName;
        internal bool InternalAddToEnd;
        internal string InternalElementTagName = string.Empty;
 
        protected ConfigurationElementCollection() { }
 
        protected ConfigurationElementCollection(IComparer comparer)
        {
            if (comparer is null)
            {
                throw new ArgumentNullException(nameof(comparer));
            }
 
            _comparer = comparer;
        }
 
        private ArrayList Items { get; } = new ArrayList();
 
        protected internal string AddElementName
        {
            get { return _addElement; }
            set
            {
                _addElement = value;
                if (BaseConfigurationRecord.IsReservedAttributeName(value))
                    throw new ArgumentException(SR.Format(SR.Item_name_reserved, DefaultAddItemName, value));
            }
        }
 
        protected internal string RemoveElementName
        {
            get { return _removeElement; }
            set
            {
                if (BaseConfigurationRecord.IsReservedAttributeName(value))
                    throw new ArgumentException(SR.Format(SR.Item_name_reserved, DefaultRemoveItemName, value));
                _removeElement = value;
            }
        }
 
        protected internal string ClearElementName
        {
            get { return _clearElement; }
            set
            {
                if (BaseConfigurationRecord.IsReservedAttributeName(value))
                    throw new ArgumentException(SR.Format(SR.Item_name_reserved, DefaultClearItemsName, value));
                _clearElement = value;
            }
        }
 
        public bool EmitClear
        {
            get { return _emitClearTag; }
            set
            {
                if (IsReadOnly()) throw new ConfigurationErrorsException(SR.Config_base_read_only);
                if (value)
                {
                    CheckLockedElement(_clearElement, null); // has clear been locked?
                    CheckLockedElement(_removeElement, null); // has remove been locked? Clear implies remove
                }
                _modified = true;
                _emitClearTag = value;
            }
        }
 
        protected virtual string ElementName => "";
 
        internal string LockableElements
        {
            get
            {
                if ((CollectionType == ConfigurationElementCollectionType.AddRemoveClearMap) ||
                    (CollectionType == ConfigurationElementCollectionType.AddRemoveClearMapAlternate))
                {
                    string elementNames = "'" + AddElementName + "'"; // Must have an add
                    if (RemoveElementName.Length != 0)
                        elementNames += ", '" + RemoveElementName + "'";
                    if (ClearElementName.Length != 0)
                        elementNames += ", '" + ClearElementName + "'";
                    return elementNames;
                }
 
                if (!string.IsNullOrEmpty(ElementName)) return "'" + ElementName + "'";
                return string.Empty;
            }
        }
 
        protected virtual bool ThrowOnDuplicate
            => (CollectionType == ConfigurationElementCollectionType.AddRemoveClearMap) ||
            (CollectionType == ConfigurationElementCollectionType.AddRemoveClearMapAlternate);
 
        public virtual ConfigurationElementCollectionType CollectionType
            => ConfigurationElementCollectionType.AddRemoveClearMap;
 
        public int Count => Items.Count - _removedItemCount;
 
        public bool IsSynchronized => false;
 
        public object SyncRoot => null;
 
        void ICollection.CopyTo(Array arr, int index)
        {
            foreach (Entry entry in Items)
                if (entry.EntryType != EntryType.Removed) arr.SetValue(entry.Value, index++);
        }
 
        public IEnumerator GetEnumerator()
        {
            return GetEnumeratorImpl();
        }
 
        internal override void AssociateContext(BaseConfigurationRecord configRecord)
        {
            base.AssociateContext(configRecord);
 
            foreach (Entry entry in Items)
                entry.Value?.AssociateContext(configRecord);
        }
 
        protected internal override bool IsModified()
        {
            if (_modified) return true;
 
            if (base.IsModified()) return true;
 
            foreach (Entry entry in Items)
            {
                if (entry.EntryType == EntryType.Removed) continue;
 
                ConfigurationElement elem = entry.Value;
                if (elem.IsModified()) return true;
            }
 
            return false;
        }
 
        protected internal override void ResetModified()
        {
            _modified = false;
            base.ResetModified();
 
            foreach (Entry entry in Items)
            {
                if (entry.EntryType == EntryType.Removed) continue;
 
                ConfigurationElement elem = entry.Value;
                elem.ResetModified();
            }
        }
 
        public override bool IsReadOnly()
        {
            return _readOnly;
        }
 
        protected internal override void SetReadOnly()
        {
            _readOnly = true;
            foreach (Entry entry in Items)
            {
                if (entry.EntryType == EntryType.Removed) continue;
 
                ConfigurationElement elem = entry.Value;
                elem.SetReadOnly();
            }
        }
 
        internal virtual IEnumerator GetEnumeratorImpl()
        {
            return new Enumerator(Items, this);
        }
 
        internal IEnumerator GetElementsEnumerator()
        {
            // Return an enumerator over the collection's config elements.
            // This is different then the std GetEnumerator because the second one
            // can return different set of items if overridden in a derived class
 
            return new Enumerator(Items, this);
        }
 
        public override bool Equals(object compareTo)
        {
            if (compareTo == null || compareTo.GetType() != GetType())
                return false;
 
            ConfigurationElementCollection compareToElem = (ConfigurationElementCollection)compareTo;
            if (Count != compareToElem.Count)
                return false;
 
            foreach (Entry thisEntry in Items)
            {
                bool found = false;
                foreach (Entry compareEntry in compareToElem.Items)
                {
                    if (!Equals(thisEntry.Value, compareEntry.Value)) continue;
                    found = true;
                    break;
                }
 
                if (found == false)
                {
                    // not in the collection must be different
                    return false;
                }
            }
            return true;
        }
 
        public override int GetHashCode()
        {
            int hHashCode = 0;
            foreach (Entry thisEntry in Items)
            {
                ConfigurationElement elem = thisEntry.Value;
                hHashCode ^= elem.GetHashCode();
            }
            return hHashCode;
        }
 
 
        protected internal override void Unmerge(ConfigurationElement sourceElement,
            ConfigurationElement parentElement,
            ConfigurationSaveMode saveMode)
        {
            base.Unmerge(sourceElement, parentElement, saveMode);
            if (sourceElement == null) return;
 
            ConfigurationElementCollection parentCollection = parentElement as ConfigurationElementCollection;
            ConfigurationElementCollection sourceCollection = sourceElement as ConfigurationElementCollection;
            Hashtable inheritance = new Hashtable();
            _lockedAllExceptAttributesList = sourceElement._lockedAllExceptAttributesList;
            _lockedAllExceptElementsList = sourceElement._lockedAllExceptElementsList;
            _itemLockedFlag = sourceElement._itemLockedFlag;
            _lockedAttributesList = sourceElement._lockedAttributesList;
            _lockedElementsList = sourceElement._lockedElementsList;
 
            AssociateContext(sourceElement._configRecord);
 
            if (parentElement != null)
            {
                if (parentElement._lockedAttributesList != null)
                {
                    _lockedAttributesList = UnMergeLockList(sourceElement._lockedAttributesList,
                        parentElement._lockedAttributesList, saveMode);
                }
                if (parentElement._lockedElementsList != null)
                {
                    _lockedElementsList = UnMergeLockList(sourceElement._lockedElementsList,
                        parentElement._lockedElementsList, saveMode);
                }
                if (parentElement._lockedAllExceptAttributesList != null)
                {
                    _lockedAllExceptAttributesList = UnMergeLockList(sourceElement._lockedAllExceptAttributesList,
                        parentElement._lockedAllExceptAttributesList, saveMode);
                }
                if (parentElement._lockedAllExceptElementsList != null)
                {
                    _lockedAllExceptElementsList = UnMergeLockList(sourceElement._lockedAllExceptElementsList,
                        parentElement._lockedAllExceptElementsList, saveMode);
                }
            }
 
            if ((CollectionType == ConfigurationElementCollectionType.AddRemoveClearMap) ||
                (CollectionType == ConfigurationElementCollectionType.AddRemoveClearMapAlternate))
            {
                // When writing out portable configurations the <clear/> tag should be written
                _collectionCleared = sourceCollection._collectionCleared;
                EmitClear = ((saveMode == ConfigurationSaveMode.Full) && (_clearElement.Length != 0)) ||
                    ((saveMode == ConfigurationSaveMode.Modified) && _collectionCleared) || sourceCollection.EmitClear;
 
                if ((parentCollection != null) && (EmitClear != true))
                {
                    foreach (Entry entry in parentCollection.Items)
                        if (entry.EntryType != EntryType.Removed)
                            inheritance[entry.GetKey(this)] = InheritedType.InParent;
                }
 
                foreach (Entry entry in sourceCollection.Items)
                {
                    if (entry.EntryType == EntryType.Removed) continue;
 
                    if (inheritance.Contains(entry.GetKey(this)))
                    {
                        Entry parentEntry =
                            (Entry)parentCollection.Items[parentCollection.RealIndexOf(entry.Value)];
 
                        ConfigurationElement elem = entry.Value;
                        if (elem.Equals(parentEntry.Value))
                        {
                            // in modified mode we consider any change to be different than the parent
                            inheritance[entry.GetKey(this)] = InheritedType.InBothSame;
                            if (saveMode != ConfigurationSaveMode.Modified) continue;
 
                            if (elem.IsModified())
                                inheritance[entry.GetKey(this)] = InheritedType.InBothDiff;
                            else
                            {
                                if (elem.ElementPresent)
                                {
                                    // This is when the source file contained the entry but it was an
                                    // exact copy.  We don't want to emit a remove so we treat it as
                                    // a special case.
                                    inheritance[entry.GetKey(this)] = InheritedType.InBothCopyNoRemove;
                                }
                            }
                        }
                        else
                        {
                            inheritance[entry.GetKey(this)] = InheritedType.InBothDiff;
                            if ((CollectionType == ConfigurationElementCollectionType.AddRemoveClearMapAlternate)
                                && (entry.EntryType == EntryType.Added))
                            {
                                // this is a special case for deailing with defect number 529517
                                // this code allow the config to write out the same xml when no remove was
                                // present during deserialization.
                                inheritance[entry.GetKey(this)] = InheritedType.InBothCopyNoRemove;
                            }
                        }
                    }
                    else
                    {
                        // not in parent
                        inheritance[entry.GetKey(this)] = InheritedType.InSelf;
                    }
                }
 
                if ((parentCollection != null) && (EmitClear != true))
                {
                    foreach (Entry entry in parentCollection.Items)
                    {
                        if (entry.EntryType == EntryType.Removed) continue;
                        InheritedType tp = (InheritedType)inheritance[entry.GetKey(this)];
                        if ((tp != InheritedType.InParent) && (tp != InheritedType.InBothDiff)) continue;
                        ConfigurationElement elem = CallCreateNewElement(entry.GetKey(this).ToString());
 
                        elem.Reset(entry.Value); // copy this entry
                        BaseAdd(elem, ThrowOnDuplicate, true);
                        // Add it (so that is once existed in the temp
                        BaseRemove(entry.GetKey(this), false); // now remove it to for a remove instruction
                    }
                }
 
                foreach (Entry entry in sourceCollection.Items)
                {
                    if (entry.EntryType == EntryType.Removed) continue;
                    InheritedType tp = (InheritedType)inheritance[entry.GetKey(this)];
 
                    if ((tp != InheritedType.InSelf) && (tp != InheritedType.InBothDiff) &&
                        (tp != InheritedType.InBothCopyNoRemove))
                        continue;
                    ConfigurationElement elem = CallCreateNewElement(entry.GetKey(this).ToString());
 
                    elem.Unmerge(entry.Value, null, saveMode);
 
                    if (tp == InheritedType.InSelf)
                        elem.RemoveAllInheritedLocks(); // If the key changed only local locks are kept
 
                    BaseAdd(elem, ThrowOnDuplicate, true); // Add it
                }
            }
            else
            {
                if ((CollectionType != ConfigurationElementCollectionType.BasicMap) &&
                    (CollectionType != ConfigurationElementCollectionType.BasicMapAlternate))
                    return;
 
                foreach (Entry entry in sourceCollection.Items)
                {
                    bool foundKeyInParent = false;
                    Entry parentEntrySaved = null;
 
                    if ((entry.EntryType != EntryType.Added) && (entry.EntryType != EntryType.Replaced)) continue;
                    bool inParent = false;
 
                    if (parentCollection != null)
                    {
                        foreach (Entry parentEntry in parentCollection.Items)
                        {
                            if (Equals(entry.GetKey(this), parentEntry.GetKey(this)))
                            {
                                // for basic map collection where the key is actually an element
                                // we do not want the merging behavior or data will not get written
                                // out for the properties if they match the first element deamed to be a parent
                                // For example <allow verbs="NewVerb" users="*"/> will loose the users because
                                // an entry exists in the root element.
                                if (!IsElementName(entry.GetKey(this).ToString()))
                                {
                                    // For elements which are not keyed by the element name
                                    // need to be unmerged
                                    foundKeyInParent = true;
                                    parentEntrySaved = parentEntry;
                                }
                            }
 
                            if (!Equals(entry.Value, parentEntry.Value)) continue;
 
                            foundKeyInParent = true;
                            inParent = true; // in parent and the same exact values
                            parentEntrySaved = parentEntry;
                            break;
                        }
                    }
 
                    ConfigurationElement elem = CallCreateNewElement(entry.GetKey(this).ToString());
 
                    if (!foundKeyInParent)
                    {
                        // Unmerge is similar to a reset when used like this
                        // except that it handles the different update modes
                        // which Reset does not understand
                        elem.Unmerge(entry.Value, null, saveMode); // copy this entry
                        BaseAdd(-1, elem, true); // Add it
                    }
                    else
                    {
                        ConfigurationElement sourceItem = entry.Value;
                        if (inParent && ((saveMode != ConfigurationSaveMode.Modified) || !sourceItem.IsModified()) &&
                            (saveMode != ConfigurationSaveMode.Full))
                            continue;
 
                        elem.Unmerge(entry.Value, parentEntrySaved.Value, saveMode);
                        BaseAdd(-1, elem, true); // Add it
                    }
                }
            }
        }
 
        protected internal override void Reset(ConfigurationElement parentElement)
        {
            ConfigurationElementCollection parentCollection = parentElement as ConfigurationElementCollection;
            ResetLockLists(parentElement);
 
            if (parentCollection != null)
            {
                foreach (Entry entry in parentCollection.Items)
                {
                    ConfigurationElement elem = CallCreateNewElement(entry.GetKey(this).ToString());
                    elem.Reset(entry.Value);
 
                    if (((CollectionType == ConfigurationElementCollectionType.AddRemoveClearMap) ||
                        (CollectionType == ConfigurationElementCollectionType.AddRemoveClearMapAlternate)) &&
                        ((entry.EntryType == EntryType.Added) ||
                        (entry.EntryType == EntryType.Replaced)))
                    {
                        // do not add removed items from the parent
                        BaseAdd(elem, true, true); // This version combines dups and throws (unless overridden)
                    }
                    else
                    {
                        if ((CollectionType == ConfigurationElementCollectionType.BasicMap) ||
                            (CollectionType == ConfigurationElementCollectionType.BasicMapAlternate))
                            BaseAdd(-1, elem, true); // this version appends regardless of if it is a dup.
                    }
                }
                _inheritedCount = Count; // After reset the count is the number of items actually inherited.
            }
        }
 
        public void CopyTo(ConfigurationElement[] array, int index)
        {
            ((ICollection)this).CopyTo(array, index);
        }
 
        protected virtual void BaseAdd(ConfigurationElement element)
        {
            BaseAdd(element, ThrowOnDuplicate);
        }
 
        protected internal void BaseAdd(ConfigurationElement element, bool throwIfExists)
        {
            BaseAdd(element, throwIfExists, false);
        }
 
        private void BaseAdd(ConfigurationElement element, bool throwIfExists, bool ignoreLocks)
        {
            bool flagAsReplaced = false;
            bool localAddToEnd = InternalAddToEnd;
 
            if (IsReadOnly()) throw new ConfigurationErrorsException(SR.Config_base_read_only);
 
            if (LockItem && (ignoreLocks == false))
                throw new ConfigurationErrorsException(SR.Format(SR.Config_base_element_locked, _addElement));
 
            object key = GetElementKeyInternal(element);
            int iFoundItem = -1;
            for (int index = 0; index < Items.Count; index++)
            {
                Entry entry = (Entry)Items[index];
                if (!CompareKeys(key, entry.GetKey(this))) continue;
 
                if ((entry.Value != null) && entry.Value.LockItem && (ignoreLocks == false))
                    throw new ConfigurationErrorsException(SR.Config_base_collection_item_locked);
 
                if ((entry.EntryType != EntryType.Removed) && throwIfExists)
                {
                    if (!element.Equals(entry.Value))
                    {
                        throw new ConfigurationErrorsException(
                            SR.Format(SR.Config_base_collection_entry_already_exists, key),
                            element.PropertyFileName(""), element.PropertyLineNumber(""));
                    }
 
                    entry.Value = element;
                    return;
                }
 
                if (entry.EntryType != EntryType.Added)
                {
                    if (((CollectionType == ConfigurationElementCollectionType.AddRemoveClearMap) ||
                        (CollectionType == ConfigurationElementCollectionType.AddRemoveClearMapAlternate)) &&
                        (entry.EntryType == EntryType.Removed) &&
                        (_removedItemCount > 0))
                        _removedItemCount--; // account for the value
                    entry.EntryType = EntryType.Replaced;
                    flagAsReplaced = true;
                }
 
                if (localAddToEnd ||
                    (CollectionType == ConfigurationElementCollectionType.AddRemoveClearMapAlternate))
                {
                    iFoundItem = index;
                    if (entry.EntryType == EntryType.Added)
                    {
                        // this is a special case for defect number 529517 to emulate Everett behavior
                        localAddToEnd = true;
                    }
                    break;
                }
 
                // check to see if the element is trying to set a locked property.
                if (ignoreLocks == false)
                {
                    element.HandleLockedAttributes(entry.Value);
                    // copy the lock from the removed element before setting the new element
                    element.MergeLocks(entry.Value);
                }
 
                entry.Value = element;
                _modified = true;
                return;
            }
 
            // Brand new item.
            if (iFoundItem >= 0)
            {
                Items.RemoveAt(iFoundItem);
 
                // if the item being removed was inherited adjust the cout
                if ((CollectionType == ConfigurationElementCollectionType.AddRemoveClearMapAlternate) &&
                    (iFoundItem > Count + _removedItemCount - _inheritedCount))
                    _inheritedCount--;
            }
            BaseAddInternal(localAddToEnd ? -1 : iFoundItem, element, flagAsReplaced, ignoreLocks);
            _modified = true;
        }
 
        protected int BaseIndexOf(ConfigurationElement element)
        {
            int index = 0;
            object key = GetElementKeyInternal(element);
            foreach (Entry entry in Items)
            {
                if (entry.EntryType == EntryType.Removed) continue;
                if (CompareKeys(key, entry.GetKey(this))) return index;
                index++;
            }
            return -1;
        }
 
        internal int RealIndexOf(ConfigurationElement element)
        {
            int index = 0;
            object key = GetElementKeyInternal(element);
            foreach (Entry entry in Items)
            {
                if (CompareKeys(key, entry.GetKey(this))) return index;
                index++;
            }
            return -1;
        }
 
        private void BaseAddInternal(int index, ConfigurationElement element, bool flagAsReplaced, bool ignoreLocks)
        {
            // Allow the element to initialize itself after its
            // constructor has been run so that it may access
            // virtual methods.
 
            element.AssociateContext(_configRecord);
            element.CallInit();
 
            if (IsReadOnly()) throw new ConfigurationErrorsException(SR.Config_base_read_only);
 
            if (!ignoreLocks)
            {
                // during reset we ignore locks so we can copy the elements
                if ((CollectionType == ConfigurationElementCollectionType.BasicMap) ||
                    (CollectionType == ConfigurationElementCollectionType.BasicMapAlternate))
                {
                    if (BaseConfigurationRecord.IsReservedAttributeName(ElementName))
                        throw new ArgumentException(SR.Format(SR.Basicmap_item_name_reserved, ElementName));
                    CheckLockedElement(ElementName, null);
                }
 
                if ((CollectionType == ConfigurationElementCollectionType.AddRemoveClearMap) ||
                    (CollectionType == ConfigurationElementCollectionType.AddRemoveClearMapAlternate))
                    CheckLockedElement(_addElement, null);
            }
 
            if ((CollectionType == ConfigurationElementCollectionType.BasicMapAlternate) ||
                (CollectionType == ConfigurationElementCollectionType.AddRemoveClearMapAlternate))
            {
                if (index == -1)
                {
                    // insert before inherited, but after any removed
                    index = Count + _removedItemCount - _inheritedCount;
                }
                else
                {
                    if ((index > Count + _removedItemCount - _inheritedCount) && (flagAsReplaced == false))
                        throw new ConfigurationErrorsException(SR.Config_base_cannot_add_items_below_inherited_items);
                }
            }
 
            if ((CollectionType == ConfigurationElementCollectionType.BasicMap) &&
                (index >= 0) &&
                (index < _inheritedCount))
                throw new ConfigurationErrorsException(SR.Config_base_cannot_add_items_above_inherited_items);
 
            EntryType entryType = flagAsReplaced == false ? EntryType.Added : EntryType.Replaced;
 
            object key = GetElementKeyInternal(element);
 
            if (index >= 0)
            {
                if (index > Items.Count)
                    throw new ConfigurationErrorsException(SR.Format(SR.IndexOutOfRange, index));
                Items.Insert(index, new Entry(entryType, key, element));
            }
            else
            {
                Items.Add(new Entry(entryType, key, element));
            }
 
            _modified = true;
        }
 
        protected virtual void BaseAdd(int index, ConfigurationElement element)
        {
            BaseAdd(index, element, false);
        }
 
        private void BaseAdd(int index, ConfigurationElement element, bool ignoreLocks)
        {
            if (IsReadOnly()) throw new ConfigurationErrorsException(SR.Config_base_read_only);
            if (index < -1) throw new ConfigurationErrorsException(SR.Format(SR.IndexOutOfRange, index));
 
            if ((index != -1) &&
                ((CollectionType == ConfigurationElementCollectionType.AddRemoveClearMap) ||
                (CollectionType == ConfigurationElementCollectionType.AddRemoveClearMapAlternate)))
            {
                // If it's an AddRemoveClearMap*** collection, turn the index passed into a real internal index
                int realIndex = 0;
 
                if (index > 0)
                {
                    foreach (Entry entryfound in Items)
                    {
                        if (entryfound.EntryType != EntryType.Removed) index--;
                        if (index == 0) break;
                        realIndex++;
                    }
                    index = ++realIndex;
                }
 
                // check for duplicates
                object key = GetElementKeyInternal(element);
                foreach (Entry entry in Items)
                {
                    if (!CompareKeys(key, entry.GetKey(this))
                        || (entry.EntryType == EntryType.Removed))
                        continue;
 
                    if (!element.Equals(entry.Value))
                    {
                        throw new ConfigurationErrorsException(
                            SR.Format(SR.Config_base_collection_entry_already_exists, key),
                            element.PropertyFileName(""), element.PropertyLineNumber(""));
                    }
 
                    return;
                }
            }
 
            BaseAddInternal(index, element, false, ignoreLocks);
        }
 
        protected internal void BaseRemove(object key)
        {
            BaseRemove(key, false);
        }
 
        private void BaseRemove(object key, bool throwIfMissing)
        {
            if (IsReadOnly())
                throw new ConfigurationErrorsException(SR.Config_base_read_only);
 
            int index = 0;
            foreach (Entry entry in Items)
            {
                if (CompareKeys(key, entry.GetKey(this)))
                {
                    if (entry.Value == null) // A phoney delete is already present
                    {
                        if (throwIfMissing)
                        {
                            throw new ConfigurationErrorsException(
                                SR.Format(SR.Config_base_collection_entry_not_found, key));
                        }
                        return;
                    }
 
                    if (entry.Value.LockItem)
                        throw new ConfigurationErrorsException(SR.Format(SR.Config_base_attribute_locked, key));
 
                    if (entry.Value.ElementPresent == false)
                        CheckLockedElement(_removeElement, null); // has remove been locked?
 
                    switch (entry.EntryType)
                    {
                        case EntryType.Added:
                            if ((CollectionType != ConfigurationElementCollectionType.AddRemoveClearMap) &&
                                (CollectionType != ConfigurationElementCollectionType.AddRemoveClearMapAlternate))
                            {
                                if (CollectionType == ConfigurationElementCollectionType.BasicMapAlternate)
                                {
                                    if (index >= Count - _inheritedCount)
                                    {
                                        throw new ConfigurationErrorsException(
                                            SR.Config_base_cannot_remove_inherited_items);
                                    }
                                }
                                if (CollectionType == ConfigurationElementCollectionType.BasicMap)
                                {
                                    if (index < _inheritedCount)
                                    {
                                        throw new ConfigurationErrorsException(
                                            SR.Config_base_cannot_remove_inherited_items);
                                    }
                                }
 
                                Items.RemoveAt(index);
                            }
                            else
                            {
                                // don't really remove it from the collection just mark it removed
                                entry.EntryType = EntryType.Removed;
                                _removedItemCount++;
                            }
                            break;
                        case EntryType.Removed:
                            if (throwIfMissing)
                                throw new ConfigurationErrorsException(SR.Config_base_collection_entry_already_removed);
                            break;
                        default:
                            if ((CollectionType != ConfigurationElementCollectionType.AddRemoveClearMap) &&
                                (CollectionType != ConfigurationElementCollectionType.AddRemoveClearMapAlternate))
                            {
                                throw new ConfigurationErrorsException(
                                    SR.Config_base_collection_elements_may_not_be_removed);
                            }
                            entry.EntryType = EntryType.Removed;
                            _removedItemCount++;
                            break;
                    }
                    _modified = true;
                    return;
                }
                index++;
            }
 
            // Note because it is possible for removes to get orphaned by the API they will
            // not cause a throw from the base classes.  The scenerio is:
            //  Add an item in a parent level
            //  remove the item in a child level
            //  remove the item at the parent level.
 
            if (throwIfMissing)
                throw new ConfigurationErrorsException(SR.Format(SR.Config_base_collection_entry_not_found, key));
 
            if ((CollectionType != ConfigurationElementCollectionType.AddRemoveClearMap) &&
                (CollectionType != ConfigurationElementCollectionType.AddRemoveClearMapAlternate))
                return;
 
            if (CollectionType == ConfigurationElementCollectionType.AddRemoveClearMapAlternate)
            {
                Items.Insert(Count + _removedItemCount - _inheritedCount,
                    new Entry(EntryType.Removed, key, null));
            }
            else
                Items.Add(new Entry(EntryType.Removed, key, null));
 
            _removedItemCount++;
        }
 
        protected internal ConfigurationElement BaseGet(object key)
        {
            foreach (Entry entry in Items)
                if (entry.EntryType != EntryType.Removed)
                    if (CompareKeys(key, entry.GetKey(this))) return entry.Value;
            return null;
        }
 
        protected internal bool BaseIsRemoved(object key)
        {
            foreach (Entry entry in Items)
                if (CompareKeys(key, entry.GetKey(this)))
                    return entry.EntryType == EntryType.Removed;
            return false;
        }
 
        protected internal ConfigurationElement BaseGet(int index)
        {
            if (index < 0) throw new ConfigurationErrorsException(SR.Format(SR.IndexOutOfRange, index));
 
            int virtualIndex = 0;
            Entry entry = null;
 
            foreach (Entry entryfound in Items)
            {
                if ((virtualIndex == index) && (entryfound.EntryType != EntryType.Removed))
                {
                    entry = entryfound;
                    break;
                }
                if (entryfound.EntryType != EntryType.Removed) virtualIndex++;
            }
 
            if (entry != null)
                return entry.Value;
 
            throw new ConfigurationErrorsException(SR.Format(SR.IndexOutOfRange, index));
        }
 
        protected internal object[] BaseGetAllKeys()
        {
            object[] keys = new object[Count];
            int index = 0;
            foreach (Entry entry in Items)
            {
                if (entry.EntryType == EntryType.Removed) continue;
                keys[index] = entry.GetKey(this);
                index++;
            }
            return keys;
        }
 
        protected internal object BaseGetKey(int index)
        {
            int virtualIndex = 0;
            Entry entry = null;
            if (index < 0) throw new ConfigurationErrorsException(SR.Format(SR.IndexOutOfRange, index));
 
            foreach (Entry entryfound in Items)
            {
                if ((virtualIndex == index) && (entryfound.EntryType != EntryType.Removed))
                {
                    entry = entryfound;
                    break;
                }
 
                if (entryfound.EntryType != EntryType.Removed) virtualIndex++;
            }
 
            // Entry entry = (Entry)_items[index];
            if (entry == null) throw new ConfigurationErrorsException(SR.Format(SR.IndexOutOfRange, index));
 
            return entry.GetKey(this);
        }
 
        protected internal void BaseClear()
        {
            if (IsReadOnly()) throw new ConfigurationErrorsException(SR.Config_base_read_only);
 
            CheckLockedElement(_clearElement, null); // has clear been locked?
            CheckLockedElement(_removeElement, null); // has remove been locked? Clear implies remove
 
            _modified = true;
            _collectionCleared = true;
            if (((CollectionType == ConfigurationElementCollectionType.BasicMap) ||
                (CollectionType == ConfigurationElementCollectionType.BasicMapAlternate))
                && (_inheritedCount > 0))
            {
                int removeIndex = 0;
                if (CollectionType == ConfigurationElementCollectionType.BasicMapAlternate)
                    removeIndex = 0; // Inherited items are at the bottom and cannot be removed
                if (CollectionType == ConfigurationElementCollectionType.BasicMap)
                    removeIndex = _inheritedCount; // inherited items are at the top and cannot be removed
                while (Count - _inheritedCount > 0) Items.RemoveAt(removeIndex);
            }
            else
            {
                // do not clear any locked items
                // _items.Clear();
                int inheritedRemoved = 0;
                int removedRemoved = 0;
                int initialCount = Count;
 
                // check for locks before removing any items from the collection
                for (int checkIndex = 0; checkIndex < Items.Count; checkIndex++)
                {
                    Entry entry = (Entry)Items[checkIndex];
                    if ((entry.Value != null) && entry.Value.LockItem)
                        throw new ConfigurationErrorsException(SR.Config_base_collection_item_locked_cannot_clear);
                }
 
                for (int removeIndex = Items.Count - 1; removeIndex >= 0; removeIndex--)
                {
                    Entry entry = (Entry)Items[removeIndex];
                    if (((CollectionType == ConfigurationElementCollectionType.AddRemoveClearMap) &&
                        (removeIndex < _inheritedCount)) ||
                        ((CollectionType == ConfigurationElementCollectionType.AddRemoveClearMapAlternate) &&
                        (removeIndex >= initialCount - _inheritedCount)))
                        inheritedRemoved++;
                    if (entry.EntryType == EntryType.Removed) removedRemoved++;
 
                    Items.RemoveAt(removeIndex);
                }
                _inheritedCount -= inheritedRemoved;
                _removedItemCount -= removedRemoved;
            }
        }
 
        protected internal void BaseRemoveAt(int index)
        {
            if (IsReadOnly()) throw new ConfigurationErrorsException(SR.Config_base_read_only);
            int virtualIndex = 0;
            Entry entry = null;
 
            foreach (Entry entryfound in Items)
            {
                if ((virtualIndex == index) && (entryfound.EntryType != EntryType.Removed))
                {
                    entry = entryfound;
                    break;
                }
 
                if (entryfound.EntryType != EntryType.Removed) virtualIndex++;
            }
 
            if (entry == null) throw new ConfigurationErrorsException(SR.Format(SR.IndexOutOfRange, index));
            if (entry.Value.LockItem)
            {
                throw new ConfigurationErrorsException(SR.Format(SR.Config_base_attribute_locked,
                    entry.GetKey(this)));
            }
 
            if (entry.Value.ElementPresent == false)
                CheckLockedElement(_removeElement, null); // has remove been locked?
 
            switch (entry.EntryType)
            {
                case EntryType.Added:
                    if ((CollectionType != ConfigurationElementCollectionType.AddRemoveClearMap) &&
                        (CollectionType != ConfigurationElementCollectionType.AddRemoveClearMapAlternate))
                    {
                        if ((CollectionType == ConfigurationElementCollectionType.BasicMapAlternate) &&
                            (index >= Count - _inheritedCount))
                            throw new ConfigurationErrorsException(SR.Config_base_cannot_remove_inherited_items);
 
                        if ((CollectionType == ConfigurationElementCollectionType.BasicMap) && (index < _inheritedCount))
                            throw new ConfigurationErrorsException(SR.Config_base_cannot_remove_inherited_items);
 
                        Items.RemoveAt(index);
                    }
                    else
                    {
                        // don't really remove it from the collection just mark it removed
                        if (entry.Value.ElementPresent == false)
                            CheckLockedElement(_removeElement, null); // has remove been locked?
 
                        entry.EntryType = EntryType.Removed;
                        _removedItemCount++;
                    }
 
                    break;
 
                case EntryType.Removed:
                    throw new ConfigurationErrorsException(SR.Config_base_collection_entry_already_removed);
 
                default:
                    if ((CollectionType != ConfigurationElementCollectionType.AddRemoveClearMap) &&
                        (CollectionType != ConfigurationElementCollectionType.AddRemoveClearMapAlternate))
                        throw new ConfigurationErrorsException(SR.Config_base_collection_elements_may_not_be_removed);
 
                    entry.EntryType = EntryType.Removed;
                    _removedItemCount++;
                    break;
            }
            _modified = true;
        }
 
        protected internal override bool SerializeElement(XmlWriter writer, bool serializeCollectionKey)
        {
            ConfigurationElementCollectionType type = CollectionType;
            bool dataToWrite = false;
 
            dataToWrite |= base.SerializeElement(writer, serializeCollectionKey);
 
            if ((type == ConfigurationElementCollectionType.AddRemoveClearMap) ||
                (type == ConfigurationElementCollectionType.AddRemoveClearMapAlternate))
            {
                // it is possible that the collection only has to be cleared and contains
                // no real elements
                if (_emitClearTag && (_clearElement.Length != 0))
                {
                    if (writer != null)
                    {
                        writer.WriteStartElement(_clearElement);
                        writer.WriteEndElement();
                    }
                    dataToWrite = true;
                }
            }
 
            foreach (Entry entry in Items)
                switch (type)
                {
                    case ConfigurationElementCollectionType.BasicMap:
                    case ConfigurationElementCollectionType.BasicMapAlternate:
                        if ((entry.EntryType == EntryType.Added) || (entry.EntryType == EntryType.Replaced))
                        {
                            if (!string.IsNullOrEmpty(ElementName))
                            {
                                if (BaseConfigurationRecord.IsReservedAttributeName(ElementName))
                                {
                                    throw new ArgumentException(SR.Format(SR.Basicmap_item_name_reserved,
                                        ElementName));
                                }
 
                                dataToWrite |= entry.Value.SerializeToXmlElement(writer, ElementName);
                            }
                            else dataToWrite |= entry.Value.SerializeElement(writer, false);
                        }
                        break;
                    case ConfigurationElementCollectionType.AddRemoveClearMap:
                    case ConfigurationElementCollectionType.AddRemoveClearMapAlternate:
                        if (((entry.EntryType == EntryType.Removed) ||
                            (entry.EntryType == EntryType.Replaced)) &&
                            (entry.Value != null))
                        {
                            writer?.WriteStartElement(_removeElement);
                            entry.Value.SerializeElement(writer, true);
                            writer?.WriteEndElement();
                            dataToWrite = true;
                        }
 
                        if ((entry.EntryType == EntryType.Added) || (entry.EntryType == EntryType.Replaced))
                            dataToWrite |= entry.Value.SerializeToXmlElement(writer, _addElement);
 
                        break;
                }
 
            return dataToWrite;
        }
 
        protected override bool OnDeserializeUnrecognizedElement(string elementName, XmlReader reader)
        {
            if ((CollectionType == ConfigurationElementCollectionType.AddRemoveClearMap) ||
                (CollectionType == ConfigurationElementCollectionType.AddRemoveClearMapAlternate))
            {
                if (elementName == _addElement)
                {
                    ConfigurationElement elem = CallCreateNewElement();
                    elem.ResetLockLists(this);
                    elem.DeserializeElement(reader, false);
                    BaseAdd(elem);
                }
                else
                {
                    if (elementName == _removeElement)
                    {
                        ConfigurationElement elem = CallCreateNewElement();
                        elem.ResetLockLists(this);
                        elem.DeserializeElement(reader, true);
                        if (IsElementRemovable(elem)) BaseRemove(GetElementKeyInternal(elem), false);
                    }
                    else
                    {
                        if (elementName != _clearElement) return false;
 
                        if (reader.AttributeCount > 0)
                        {
                            while (reader.MoveToNextAttribute())
                            {
                                string propertyName = reader.Name;
                                throw new ConfigurationErrorsException(
                                    SR.Format(SR.Config_base_unrecognized_attribute, propertyName), reader);
                            }
                        }
 
                        CheckLockedElement(elementName, reader);
                        reader.MoveToElement();
                        BaseClear(); //
                        _emitClearTag = true;
                    }
                }
            }
            else
            {
                if (elementName == ElementName)
                {
                    if (BaseConfigurationRecord.IsReservedAttributeName(elementName))
                        throw new ArgumentException(SR.Format(SR.Basicmap_item_name_reserved, elementName));
                    ConfigurationElement elem = CallCreateNewElement();
                    elem.ResetLockLists(this);
                    elem.DeserializeElement(reader, false);
                    BaseAdd(elem);
                }
                else
                {
                    if (!IsElementName(elementName)) return false;
 
                    // this section handle the collection like the allow deny senario which
                    if (BaseConfigurationRecord.IsReservedAttributeName(elementName))
                        throw new ArgumentException(SR.Format(SR.Basicmap_item_name_reserved, elementName));
 
                    // have multiple tags for the collection
                    ConfigurationElement elem = CallCreateNewElement(elementName);
                    elem.ResetLockLists(this);
                    elem.DeserializeElement(reader, false);
                    BaseAdd(-1, elem);
                }
            }
            return true;
        }
 
        private ConfigurationElement CallCreateNewElement(string elementName)
        {
            ConfigurationElement elem = CreateNewElement(elementName);
            elem.AssociateContext(_configRecord);
            elem.CallInit();
            return elem;
        }
 
        private ConfigurationElement CallCreateNewElement()
        {
            ConfigurationElement elem = CreateNewElement();
            elem.AssociateContext(_configRecord);
            elem.CallInit();
            return elem;
        }
 
        protected virtual ConfigurationElement CreateNewElement(string elementName)
        {
            return CreateNewElement();
        }
 
        protected abstract ConfigurationElement CreateNewElement();
        protected abstract object GetElementKey(ConfigurationElement element);
 
        internal object GetElementKeyInternal(ConfigurationElement element)
        {
            object key = GetElementKey(element);
            if (key == null)
                throw new ConfigurationErrorsException(SR.Config_base_invalid_element_key);
            return key;
        }
 
        protected virtual bool IsElementRemovable(ConfigurationElement element)
        {
            return true;
        }
 
        private bool CompareKeys(object key1, object key2)
        {
            if (_comparer != null) return _comparer.Compare(key1, key2) == 0;
            return key1.Equals(key2);
        }
 
        protected virtual bool IsElementName(string elementName)
        {
            return false;
        }
 
        internal bool IsLockableElement(string elementName)
        {
            if ((CollectionType == ConfigurationElementCollectionType.AddRemoveClearMap) ||
                (CollectionType == ConfigurationElementCollectionType.AddRemoveClearMapAlternate))
            {
                return (elementName == AddElementName) ||
                    (elementName == RemoveElementName) ||
                    (elementName == ClearElementName);
            }
            return (elementName == ElementName) || IsElementName(elementName);
        }
 
        private enum InheritedType
        {
            InNeither = 0,
            InParent = 1,
            InSelf = 2,
            InBothSame = 3,
            InBothDiff = 4,
            InBothCopyNoRemove = 5,
        }
 
        private enum EntryType
        {
            Inherited,
            Replaced,
            Removed,
            Added,
        }
 
        private sealed class Entry
        {
            private readonly object _key;
            internal EntryType EntryType;
            internal ConfigurationElement Value;
 
            internal Entry(EntryType type, object key, ConfigurationElement value)
            {
                EntryType = type;
                _key = key;
                Value = value;
            }
 
            internal object GetKey(ConfigurationElementCollection thisCollection)
            {
                // For items that have been really inserted...
                return Value != null ? thisCollection.GetElementKeyInternal(Value) : _key;
            }
        }
 
        private sealed class Enumerator : IDictionaryEnumerator
        {
            private readonly IEnumerator _itemsEnumerator;
            private readonly ConfigurationElementCollection _thisCollection;
            private DictionaryEntry _current;
 
            internal Enumerator(ArrayList items, ConfigurationElementCollection collection)
            {
                _itemsEnumerator = items.GetEnumerator();
                _thisCollection = collection;
            }
 
            bool IEnumerator.MoveNext()
            {
                while (_itemsEnumerator.MoveNext())
                {
                    Entry entry = (Entry)_itemsEnumerator.Current;
                    if (entry.EntryType == EntryType.Removed) continue;
                    _current.Key = entry.GetKey(_thisCollection) ?? "key";
                    _current.Value = entry.Value;
                    return true;
                }
                return false;
            }
 
            void IEnumerator.Reset()
            {
                _itemsEnumerator.Reset();
            }
 
            object IEnumerator.Current => _current.Value;
 
            DictionaryEntry IDictionaryEnumerator.Entry => _current;
 
            object IDictionaryEnumerator.Key => _current.Key;
 
            object IDictionaryEnumerator.Value => _current.Value;
        }
    }
}