File: System\DirectoryServices\PropertyCollection.cs
Web Access
Project: src\src\runtime\src\libraries\System.DirectoryServices\src\System.DirectoryServices.csproj (System.DirectoryServices)
// 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.Globalization;
using System.Runtime.InteropServices;

namespace System.DirectoryServices
{
    /// <devdoc>
    /// Contains the properties on a <see cref='System.DirectoryServices.DirectoryEntry'/>.
    /// </devdoc>
    public class PropertyCollection : IDictionary
    {
        private readonly DirectoryEntry _entry;
        internal readonly Hashtable valueTable;

        internal PropertyCollection(DirectoryEntry entry)
        {
            _entry = entry;
            Hashtable tempTable = new Hashtable();
            valueTable = Hashtable.Synchronized(tempTable);
        }

        /// <devdoc>
        /// Gets the property with the given name.
        /// </devdoc>
        public PropertyValueCollection this[string propertyName]
        {
            get
            {
                if (propertyName == null)
                    throw new ArgumentNullException(nameof(propertyName));

                string name = propertyName.ToLowerInvariant();
                if (valueTable.Contains(name))
                    return (PropertyValueCollection)valueTable[name]!;
                else
                {
                    PropertyValueCollection value = new PropertyValueCollection(_entry, propertyName);
                    valueTable.Add(name, value);
                    return value;
                }
            }
        }

        /// <devdoc>
        /// Gets the number of properties available on this entry.
        /// </devdoc>
        public int Count
        {
            get
            {
                if (!(_entry.AdsObject is UnsafeNativeMethods.IAdsPropertyList))
                    throw new NotSupportedException(SR.DSCannotCount);

                _entry.FillCache("");

                UnsafeNativeMethods.IAdsPropertyList propList = (UnsafeNativeMethods.IAdsPropertyList)_entry.AdsObject;

                return propList.PropertyCount;
            }
        }

        public ICollection PropertyNames => new KeysCollection(this);

        public ICollection Values => new ValuesCollection(this);

        public bool Contains(string propertyName)
        {
            object? var;
            int unmanagedResult = _entry.AdsObject.GetEx(propertyName, out var);
            if (unmanagedResult != 0)
            {
                //  property not found (IIS provider returns 0x80005006, other provides return 0x8000500D).
                if ((unmanagedResult == unchecked((int)0x8000500D)) || (unmanagedResult == unchecked((int)0x80005006)))
                {
                    return false;
                }
                else
                {
                    throw COMExceptionHelper.CreateFormattedComException(unmanagedResult);
                }
            }

            return true;
        }

        /// <devdoc>
        /// Copies the elements of this instance into an <see cref='System.Array'/>, starting at a particular index into the array.
        /// </devdoc>
        public void CopyTo(PropertyValueCollection[] array, int index)
        {
            ((ICollection)this).CopyTo((Array)array, index);
        }

        /// <devdoc>
        /// Returns an enumerator, which can be used to iterate through the collection.
        /// </devdoc>
        public IDictionaryEnumerator GetEnumerator()
        {
            if (!(_entry.AdsObject is UnsafeNativeMethods.IAdsPropertyList))
                throw new NotSupportedException(SR.DSCannotEmunerate);

            // Once an object has been used for an enumerator once, it can't be used again, because it only
            // maintains a single cursor. Re-bind to the ADSI object to get a new instance.
            // That's why we must clone entry here. It will be automatically disposed inside Enumerator.
            DirectoryEntry entryToUse = _entry.CloneBrowsable();
            entryToUse.FillCache("");

            _ = (UnsafeNativeMethods.IAdsPropertyList)entryToUse.AdsObject;

            entryToUse.propertiesAlreadyEnumerated = true;
            return new PropertyEnumerator(_entry, entryToUse);
        }

        object? IDictionary.this[object key]
        {
            get => this[(string)key];
            set => throw new NotSupportedException(SR.DSPropertySetSupported);
        }

        bool IDictionary.IsFixedSize => true;

        bool IDictionary.IsReadOnly => true;

        ICollection IDictionary.Keys => new KeysCollection(this);

        void IDictionary.Add(object key, object? value)
        {
            throw new NotSupportedException(SR.DSAddNotSupported);
        }

        void IDictionary.Clear()
        {
            throw new NotSupportedException(SR.DSClearNotSupported);
        }

        bool IDictionary.Contains(object value) => Contains((string)value);

        void IDictionary.Remove(object key)
        {
            throw new NotSupportedException(SR.DSRemoveNotSupported);
        }

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

        bool ICollection.IsSynchronized => false;

        object ICollection.SyncRoot => this;

        void ICollection.CopyTo(Array array, int index)
        {
            if (array == null)
                throw new ArgumentNullException(nameof(array));

            if (array.Rank != 1)
                throw new ArgumentException(SR.OnlyAllowSingleDimension, nameof(array));

            if (index < 0)
                throw new ArgumentOutOfRangeException(SR.LessThanZero, nameof(index));

            if (((index + Count) > array.Length) || ((index + Count) < index))
                throw new ArgumentException(SR.DestinationArrayNotLargeEnough);

            foreach (PropertyValueCollection value in this)
            {
                array.SetValue(value, index);
                index++;
            }
        }

        private sealed class PropertyEnumerator : IDictionaryEnumerator, IDisposable
        {
            private readonly DirectoryEntry _entry;               // clone (to be disposed)
            private readonly DirectoryEntry _parentEntry;         // original entry to pass to PropertyValueCollection
            private string? _currentPropName;

            public PropertyEnumerator(DirectoryEntry parent, DirectoryEntry clone)
            {
                _entry = clone;
                _parentEntry = parent;
            }

            ~PropertyEnumerator() => Dispose(true);

            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }

            private void Dispose(bool disposing)
            {
                if (disposing)
                {
                    _entry.Dispose();
                }
            }

            public object Current => Entry.Value!;

            public DictionaryEntry Entry
            {
                get
                {
                    if (_currentPropName == null)
                        throw new InvalidOperationException(SR.DSNoCurrentProperty);

                    return new DictionaryEntry(_currentPropName, new PropertyValueCollection(_parentEntry, _currentPropName));
                }
            }

            public object Key => Entry.Key;

            public object Value => Entry.Value!;

            public bool MoveNext()
            {
                object? prop;
                int hr = 0;
                try
                {
                    hr = ((UnsafeNativeMethods.IAdsPropertyList)_entry.AdsObject).Next(out prop);
                }
                catch (COMException e)
                {
                    hr = e.ErrorCode;
                    prop = null;
                }
                if (hr == 0)
                {
                    if (prop != null)
                        _currentPropName = ((UnsafeNativeMethods.IAdsPropertyEntry)prop).Name;
                    else
                        _currentPropName = null;

                    return true;
                }
                else
                {
                    _currentPropName = null;
                    return false;
                }
            }

            public void Reset()
            {
                ((UnsafeNativeMethods.IAdsPropertyList)_entry.AdsObject).Reset();
                _currentPropName = null;
            }
        }

        private class ValuesCollection : ICollection
        {
            protected PropertyCollection props;

            public ValuesCollection(PropertyCollection props)
            {
                this.props = props;
            }

            public int Count => props.Count;

            public bool IsReadOnly => true;

            public bool IsSynchronized => false;

            public object SyncRoot => ((ICollection)props).SyncRoot;

            public void CopyTo(Array array, int index)
            {
                foreach (object value in this)
                    array.SetValue(value, index++);
            }

            public virtual IEnumerator GetEnumerator() => new ValuesEnumerator(props);
        }

        private sealed class KeysCollection : ValuesCollection
        {
            public KeysCollection(PropertyCollection props) : base(props)
            {
            }

            public override IEnumerator GetEnumerator()
            {
                props._entry.FillCache("");
                return new KeysEnumerator(props);
            }
        }

        private class ValuesEnumerator : IEnumerator
        {
            private int _currentIndex = -1;
            protected PropertyCollection propCollection;

            public ValuesEnumerator(PropertyCollection propCollection)
            {
                this.propCollection = propCollection;
            }

            protected int CurrentIndex
            {
                get
                {
                    if (_currentIndex == -1)
                        throw new InvalidOperationException(SR.DSNoCurrentValue);
                    return _currentIndex;
                }
            }

            public virtual object Current
            {
                get
                {
                    UnsafeNativeMethods.IAdsPropertyList propList = (UnsafeNativeMethods.IAdsPropertyList)propCollection._entry.AdsObject;
                    return propCollection[((UnsafeNativeMethods.IAdsPropertyEntry)propList.Item(CurrentIndex)).Name];
                }
            }

            public bool MoveNext()
            {
                _currentIndex++;
                if (_currentIndex >= propCollection.Count)
                {
                    _currentIndex = -1;
                    return false;
                }
                else
                    return true;
            }

            public void Reset() => _currentIndex = -1;
        }

        private sealed class KeysEnumerator : ValuesEnumerator
        {
            public KeysEnumerator(PropertyCollection collection) : base(collection)
            {
            }

            public override object Current
            {
                get
                {
                    UnsafeNativeMethods.IAdsPropertyList propList = (UnsafeNativeMethods.IAdsPropertyList)propCollection._entry.AdsObject;

                    return ((UnsafeNativeMethods.IAdsPropertyEntry)propList.Item(CurrentIndex)).Name;
                }
            }
        }
    }
}