File: System\DirectoryServices\AccountManagement\PrincipalCollectionEnumerator.cs
Web Access
Project: src\src\runtime\src\libraries\System.DirectoryServices.AccountManagement\src\System.DirectoryServices.AccountManagement.csproj (System.DirectoryServices.AccountManagement)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;

namespace System.DirectoryServices.AccountManagement
{
    internal sealed class PrincipalCollectionEnumerator : IEnumerator<Principal>, IEnumerator
    {
        //
        // Public properties
        //

        public Principal Current
        {
            get
            {
                CheckDisposed();

                // Since MoveNext() saved off the current value for us, this is largely trivial.

                if (_endReached || _currentMode == CurrentEnumeratorMode.None)
                {
                    // Either we're at the end or before the beginning
                    //  (CurrentEnumeratorMode.None implies we're _before_ the first value)

                    GlobalDebug.WriteLineIf(
                                        GlobalDebug.Warn,
                                        "PrincipalCollectionEnumerator",
                                        "Current: bad position, endReached={0}, currentMode={1}",
                                        _endReached,
                                        _currentMode);

                    throw new InvalidOperationException(SR.PrincipalCollectionEnumInvalidPos);
                }

                Debug.Assert(_current != null);
                return _current;
            }
        }

        object IEnumerator.Current
        {
            get
            {
                return Current;
            }
        }

        //
        // Public methods
        //

        public bool MoveNext()
        {
            GlobalDebug.WriteLineIf(GlobalDebug.Info, "PrincipalCollectionEnumerator", "Entering MoveNext");

            CheckDisposed();
            CheckChanged();

            // We previously reached the end, nothing more to do
            if (_endReached)
            {
                GlobalDebug.WriteLineIf(GlobalDebug.Info, "PrincipalCollectionEnumerator", "MoveNext: endReached");
                return false;
            }

            lock (_resultSet)
            {
                if (_currentMode == CurrentEnumeratorMode.None)
                {
                    // At the very beginning

                    // In case this ResultSet was previously used with another PrincipalCollectionEnumerator instance
                    // (e.g., two foreach loops in a row)
                    _resultSet.Reset();

                    if (!_memberCollection.Cleared && !_memberCollection.ClearCompleted)
                    {
                        GlobalDebug.WriteLineIf(GlobalDebug.Info, "PrincipalCollectionEnumerator", "MoveNext: None mode, starting with existing values");

                        // Start by enumerating the existing values in the store
                        _currentMode = CurrentEnumeratorMode.ResultSet;
                        _enumerator = null;
                    }
                    else
                    {
                        GlobalDebug.WriteLineIf(GlobalDebug.Info, "PrincipalCollectionEnumerator", "MoveNext: None mode, skipping existing values");

                        // The member collection was cleared.  Skip the ResultSet phase
                        _currentMode = CurrentEnumeratorMode.InsertedValuesCompleted;
                        _enumerator = (IEnumerator<Principal>)_insertedValuesCompleted.GetEnumerator();
                    }
                }

                Debug.Assert(_resultSet != null);

                if (_currentMode == CurrentEnumeratorMode.ResultSet)
                {
                    GlobalDebug.WriteLineIf(GlobalDebug.Info, "PrincipalCollectionEnumerator", "MoveNext: ResultSet mode");

                    bool needToRepeat = false;

                    do
                    {
                        bool f = _resultSet.MoveNext();

                        if (f)
                        {
                            Principal principal = (Principal)_resultSet.CurrentAsPrincipal;

                            if (_removedValuesCompleted.Contains(principal) || _removedValuesPending.Contains(principal))
                            {
                                // It's a value that's been removed (either a pending remove that hasn't completed, or a remove
                                // that completed _after_ we loaded the ResultSet from the store).
                                GlobalDebug.WriteLineIf(GlobalDebug.Info, "PrincipalCollectionEnumerator", "MoveNext: ResultSet mode, found remove, skipping");

                                needToRepeat = true;
                                continue;
                            }
                            else if (_insertedValuesCompleted.Contains(principal) || _insertedValuesPending.Contains(principal))
                            {
                                // insertedValuesCompleted: We must have gotten the ResultSet after the inserted committed.
                                // We don't want to return
                                // the principal twice, so we'll skip it here and later return it in
                                // the CurrentEnumeratorMode.InsertedValuesCompleted mode.
                                //
                                // insertedValuesPending: The principal must have been originally in the ResultSet, but then
                                // removed, saved, and re-added, with the re-add still pending.
                                GlobalDebug.WriteLineIf(GlobalDebug.Info, "PrincipalCollectionEnumerator", "MoveNext: ResultSet mode, found insert, skipping");

                                needToRepeat = true;
                                continue;
                            }
                            else
                            {
                                needToRepeat = false;
                                _current = principal;
                                return true;
                            }
                        }
                        else
                        {
                            // No more values left to retrieve.  Now try the insertedValuesCompleted list.
                            GlobalDebug.WriteLineIf(GlobalDebug.Info, "PrincipalCollectionEnumerator", "MoveNext: ResultSet mode, moving to InsValuesComp mode");

                            _currentMode = CurrentEnumeratorMode.InsertedValuesCompleted;
                            _enumerator = (IEnumerator<Principal>)_insertedValuesCompleted.GetEnumerator();
                            needToRepeat = false;
                        }
                    }
                    while (needToRepeat);
                }

                // These are values whose insertion has completed, but after we already loaded the ResultSet from the store.
                if (_currentMode == CurrentEnumeratorMode.InsertedValuesCompleted)
                {
                    GlobalDebug.WriteLineIf(GlobalDebug.Info, "PrincipalCollectionEnumerator", "MoveNext: InsValuesComp mode");

                    bool f = _enumerator.MoveNext();

                    if (f)
                    {
                        _current = _enumerator.Current;
                        return true;
                    }
                    else
                    {
                        GlobalDebug.WriteLineIf(GlobalDebug.Info, "PrincipalCollectionEnumerator", "MoveNext: InsValuesComp mode, moving to InsValuesPend mode");

                        _currentMode = CurrentEnumeratorMode.InsertedValuesPending;
                        _enumerator = (IEnumerator<Principal>)_insertedValuesPending.GetEnumerator();
                    }
                }

                // These are values whose insertion has not yet been committed to the store.
                if (_currentMode == CurrentEnumeratorMode.InsertedValuesPending)
                {
                    GlobalDebug.WriteLineIf(GlobalDebug.Info, "PrincipalCollectionEnumerator", "MoveNext: InsValuesPend mode");

                    bool f = _enumerator.MoveNext();

                    if (f)
                    {
                        _current = _enumerator.Current;
                        return true;
                    }
                    else
                    {
                        GlobalDebug.WriteLineIf(GlobalDebug.Info, "PrincipalCollectionEnumerator", "MoveNext: InsValuesPend mode, nothing left");

                        _endReached = true;
                        return false;
                    }
                }
            }

            Debug.Fail($"PrincipalCollectionEnumerator.MoveNext: fell off end of function, mode = {_currentMode}");
            return false;
        }

        bool IEnumerator.MoveNext()
        {
            return MoveNext();
        }

        public void Reset()
        {
            GlobalDebug.WriteLineIf(GlobalDebug.Info, "PrincipalCollectionEnumerator", "Reset");

            CheckDisposed();
            CheckChanged();

            // Set us up to start enumerating from the very beginning again
            _endReached = false;
            _enumerator = null;
            _currentMode = CurrentEnumeratorMode.None;
        }

        void IEnumerator.Reset()
        {
            Reset();
        }

        public void Dispose()   // IEnumerator<Principal> inherits from IDisposable
        {
            _disposed = true;
        }

        //
        // Internal constructors
        //
        internal PrincipalCollectionEnumerator(
                                    ResultSet resultSet,
                                    PrincipalCollection memberCollection,
                                    List<Principal> removedValuesCompleted,
                                    List<Principal> removedValuesPending,
                                    List<Principal> insertedValuesCompleted,
                                    List<Principal> insertedValuesPending
                                    )
        {
            GlobalDebug.WriteLineIf(GlobalDebug.Info, "PrincipalCollectionEnumerator", "Ctor");

            Debug.Assert(resultSet != null);

            _resultSet = resultSet;
            _memberCollection = memberCollection;
            _removedValuesCompleted = removedValuesCompleted;
            _removedValuesPending = removedValuesPending;
            _insertedValuesCompleted = insertedValuesCompleted;
            _insertedValuesPending = insertedValuesPending;
        }

        //
        // Private implementation
        //

        private Principal _current;

        // Remember: these are references to objects held by the PrincipalCollection class from which we came.
        // We don't own these, and shouldn't Dispose the ResultSet.
        //
        // SYNCHRONIZATION
        //   Access to:
        //      resultSet
        //   must be synchronized, since multiple enumerators could be iterating over us at once.
        //   Synchronize by locking on resultSet.

        private readonly ResultSet _resultSet;
        private readonly List<Principal> _insertedValuesPending;
        private readonly List<Principal> _insertedValuesCompleted;
        private readonly List<Principal> _removedValuesPending;
        private readonly List<Principal> _removedValuesCompleted;

        private bool _endReached;    // true if there are no results left to iterate over

        private IEnumerator<Principal> _enumerator;   // The insertedValues{Completed,Pending} enumerator, used by MoveNext

        private enum CurrentEnumeratorMode          // The set of values that MoveNext is currently iterating over
        {
            None,
            ResultSet,
            InsertedValuesCompleted,
            InsertedValuesPending
        }

        private CurrentEnumeratorMode _currentMode = CurrentEnumeratorMode.None;

        // To support IDisposable
        private bool _disposed;

        private void CheckDisposed()
        {
            if (_disposed)
            {
                GlobalDebug.WriteLineIf(GlobalDebug.Warn, "PrincipalCollectionEnumerator", "CheckDisposed: accessing disposed object");
                throw new ObjectDisposedException("PrincipalCollectionEnumerator");
            }
        }

        // When this enumerator was constructed, to detect changes made to the PrincipalCollection after it was constructed
        private readonly DateTime _creationTime = DateTime.UtcNow;

        private readonly PrincipalCollection _memberCollection;

        private void CheckChanged()
        {
            // Make sure the app hasn't changed our underlying list
            if (_memberCollection.LastChange > _creationTime)
            {
                GlobalDebug.WriteLineIf(
                            GlobalDebug.Warn,
                            "PrincipalCollectionEnumerator",
                            "CheckChanged: has changed (last change={0}, creation={1})",
                            _memberCollection.LastChange,
                            _creationTime);

                throw new InvalidOperationException(SR.PrincipalCollectionEnumHasChanged);
            }
        }
    }
}