File: System\ComponentModel\Composition\Hosting\CatalogExportProvider.cs
Web Access
Project: src\src\libraries\System.ComponentModel.Composition\src\System.ComponentModel.Composition.csproj (System.ComponentModel.Composition)
// 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.Generic;
using System.ComponentModel.Composition.Primitives;
using System.ComponentModel.Composition.ReflectionModel;
using System.Composition.Diagnostics;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.Internal;
using Microsoft.Internal.Collections;
 
namespace System.ComponentModel.Composition.Hosting
{
    public partial class CatalogExportProvider : ExportProvider, IDisposable
    {
        private sealed class InnerCatalogExportProvider : ExportProvider
        {
            private readonly CatalogExportProvider _outerExportProvider;
 
            public InnerCatalogExportProvider(CatalogExportProvider outerExportProvider)
            {
                ArgumentNullException.ThrowIfNull(outerExportProvider);
 
                _outerExportProvider = outerExportProvider;
            }
 
            protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition? atomicComposition)
            {
                return _outerExportProvider.InternalGetExportsCore(definition, atomicComposition);
            }
        }
 
        private readonly CompositionLock _lock;
        private readonly Dictionary<ComposablePartDefinition, CatalogPart> _activatedParts = new Dictionary<ComposablePartDefinition, CatalogPart>();
        private readonly HashSet<ComposablePartDefinition> _rejectedParts = new HashSet<ComposablePartDefinition>();
        private ConditionalWeakTable<object, List<ComposablePart>>? _gcRoots;
        private readonly HashSet<IDisposable> _partsToDispose = new HashSet<IDisposable>();
        private ComposablePartCatalog _catalog;
        private volatile bool _isDisposed;
        private volatile bool _isRunning;
        private readonly bool _disableSilentRejection;
        private ExportProvider? _sourceProvider;
        private ImportEngine? _importEngine;
        private readonly CompositionOptions _compositionOptions;
        private ExportProvider? _innerExportProvider;
 
        /// <summary>
        /// Initializes a new instance of the <see cref="CatalogExportProvider"/> class.
        /// </summary>
        /// <param name="catalog">
        ///     The <see cref="ComposablePartCatalog"/> that the <see cref="CatalogExportProvider"/>
        ///     uses to produce <see cref="Export"/> objects.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="catalog"/> is <see langword="null"/>.
        /// </exception>
        public CatalogExportProvider(ComposablePartCatalog catalog)
            : this(catalog, CompositionOptions.Default)
        {
        }
 
        public CatalogExportProvider(ComposablePartCatalog catalog, bool isThreadSafe)
            : this(catalog, isThreadSafe ? CompositionOptions.IsThreadSafe : CompositionOptions.Default)
        {
        }
 
        public CatalogExportProvider(ComposablePartCatalog catalog, CompositionOptions compositionOptions)
        {
            Requires.NotNull(catalog, nameof(catalog));
            if (compositionOptions > (CompositionOptions.DisableSilentRejection | CompositionOptions.IsThreadSafe | CompositionOptions.ExportCompositionService))
            {
                throw new ArgumentOutOfRangeException(nameof(compositionOptions));
            }
 
            _catalog = catalog;
            _compositionOptions = compositionOptions;
            if (_catalog is INotifyComposablePartCatalogChanged notifyCatalogChanged)
            {
                notifyCatalogChanged.Changing += OnCatalogChanging;
            }
 
            if (_catalog is CompositionScopeDefinition scopeDefinition)
            {
                _innerExportProvider = new AggregateExportProvider(new ScopeManager(this, scopeDefinition), new InnerCatalogExportProvider(this));
            }
            else
            {
                _innerExportProvider = new InnerCatalogExportProvider(this);
            }
            _lock = new CompositionLock(compositionOptions.HasFlag(CompositionOptions.IsThreadSafe));
            _disableSilentRejection = compositionOptions.HasFlag(CompositionOptions.DisableSilentRejection);
        }
 
        /// <summary>
        ///     Gets the composable part catalog that the provider users to
        ///     produce exports.
        /// </summary>
        /// <value>
        ///     The <see cref="ComposablePartCatalog"/> that the
        ///     <see cref="CatalogExportProvider"/>
        ///     uses to produce <see cref="Export"/> objects.
        /// </value>
        /// <exception cref="ObjectDisposedException">
        ///     The <see cref="CompositionContainer"/> has been disposed of.
        /// </exception>
        public ComposablePartCatalog Catalog
        {
            get
            {
                ThrowIfDisposed();
                Debug.Assert(_catalog != null);
 
                return _catalog;
            }
        }
 
        /// <summary>
        ///     Gets the export provider which provides the provider access to additional
        ///     exports.
        /// </summary>
        /// <value>
        ///     The <see cref="ExportProvider"/> which provides the
        ///     <see cref="CatalogExportProvider"/> access to additional
        ///     <see cref="Export"/> objects. The default is <see langword="null"/>.
        /// </value>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="value"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="InvalidOperationException">
        ///     This property has already been set.
        ///     <para>
        ///         -or-
        ///     </para>
        ///     The methods on the <see cref="CatalogExportProvider"/>
        ///     have already been accessed.
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        ///     The <see cref="CatalogExportProvider"/> has been disposed of.
        /// </exception>
        /// <remarks>
        ///     This property must be set before accessing any methods on the
        ///     <see cref="CatalogExportProvider"/>.
        /// </remarks>
        [DisallowNull]
        public ExportProvider? SourceProvider
        {
            get
            {
                ThrowIfDisposed();
                using (_lock.LockStateForRead())
                {
                    return _sourceProvider;
                }
            }
            set
            {
                ThrowIfDisposed();
 
                Requires.NotNull(value!, nameof(value));
 
                ImportEngine? newImportEngine = null;
                AggregateExportProvider? aggregateExportProvider = null;
                ExportProvider sourceProvider = value!;
 
                bool isThrowing = true;
                try
                {
                    newImportEngine = new ImportEngine(sourceProvider, _compositionOptions);
 
                    sourceProvider.ExportsChanging += OnExportsChangingInternal;
 
                    using (_lock.LockStateForWrite())
                    {
                        EnsureCanSet(_sourceProvider);
 
                        _sourceProvider = sourceProvider;
                        _importEngine = newImportEngine;
 
                        isThrowing = false;
                    }
                }
                finally
                {
                    if (isThrowing)
                    {
                        sourceProvider.ExportsChanging -= OnExportsChangingInternal;
                        newImportEngine!.Dispose();
                        aggregateExportProvider?.Dispose();
                    }
                }
            }
        }
 
        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
 
        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (!_isDisposed)
                {
                    //Note:  We do not dispose _lock on dispose because DisposePart needs it to check isDisposed state
                    //          to eliminate race conditions between it and Dispose
                    INotifyComposablePartCatalogChanged? catalogToUnsubscribeFrom = null;
                    HashSet<IDisposable>? partsToDispose = null;
                    ImportEngine? importEngine = null;
                    ExportProvider? sourceProvider = null;
                    AggregateExportProvider? aggregateExportProvider = null;
                    try
                    {
                        using (_lock.LockStateForWrite())
                        {
                            if (!_isDisposed)
                            {
                                catalogToUnsubscribeFrom = _catalog as INotifyComposablePartCatalogChanged;
                                _catalog = null!;
 
                                aggregateExportProvider = _innerExportProvider as AggregateExportProvider;
                                _innerExportProvider = null;
 
                                sourceProvider = _sourceProvider;
                                _sourceProvider = null;
 
                                importEngine = _importEngine;
                                _importEngine = null;
 
                                partsToDispose = _partsToDispose;
                                _gcRoots = null;
                                _isDisposed = true;
                            }
                        }
                    }
                    finally
                    {
                        if (catalogToUnsubscribeFrom != null)
                        {
                            catalogToUnsubscribeFrom.Changing -= OnCatalogChanging;
                        }
 
                        aggregateExportProvider?.Dispose();
 
                        if (sourceProvider != null)
                        {
                            sourceProvider.ExportsChanging -= OnExportsChangingInternal;
                        }
 
                        importEngine?.Dispose();
 
                        if (partsToDispose != null)
                        {
                            foreach (var part in partsToDispose)
                            {
                                part.Dispose();
                            }
                        }
                    }
                }
            }
        }
 
        /// <summary>
        /// Returns all exports that match the conditions of the specified import.
        /// </summary>
        /// <param name="definition">The <see cref="ImportDefinition"/> that defines the conditions of the
        /// <param name="atomicComposition">The transactional container for the composition.</param>
        /// <see cref="Export"/> to get.</param>
        /// <returns></returns>
        /// <result>
        /// An <see cref="IEnumerable{T}"/> of <see cref="Export"/> objects that match
        /// the conditions defined by <see cref="ImportDefinition"/>, if found; otherwise, an
        /// empty <see cref="IEnumerable{T}"/>.
        /// </result>
        /// <remarks>
        /// <note type="inheritinfo">
        /// The implementers should not treat the cardinality-related mismatches as errors, and are not
        /// expected to throw exceptions in those cases.
        /// For instance, if the import requests exactly one export and the provider has no matching exports or more than one,
        /// it should return an empty <see cref="IEnumerable{T}"/> of <see cref="Export"/>.
        /// </note>
        /// </remarks>
        protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition? atomicComposition)
        {
            ThrowIfDisposed();
            EnsureRunning();
 
            if (_innerExportProvider == null)
            {
                throw new Exception(SR.Diagnostic_InternalExceptionMessage);
            }
 
            _innerExportProvider.TryGetExports(definition, atomicComposition, out IEnumerable<Export>? exports);
            return exports!;
        }
 
        private List<Export> InternalGetExportsCore(ImportDefinition definition, AtomicComposition? atomicComposition)
        {
            ThrowIfDisposed();
            EnsureRunning();
 
            // Use the version of the catalog appropriate to this atomicComposition
            ComposablePartCatalog currentCatalog = atomicComposition.GetValueAllowNull(_catalog);
 
            bool isExportFactory = false;
 
            if (definition is IPartCreatorImportDefinition partCreatorDefinition)
            {
                definition = partCreatorDefinition.ProductImportDefinition;
                isExportFactory = true;
            }
 
            CreationPolicy importPolicy = definition.GetRequiredCreationPolicy();
 
            List<Export> exports = new List<Export>();
            bool ensureRejection = EnsureRejection(atomicComposition);
            foreach (var partDefinitionAndExportDefinition in currentCatalog.GetExports(definition))
            {
                bool isPartRejected = ensureRejection && IsRejected(partDefinitionAndExportDefinition.Item1, atomicComposition);
                if (!isPartRejected)
                {
                    exports.Add(CreateExport(partDefinitionAndExportDefinition.Item1, partDefinitionAndExportDefinition.Item2, isExportFactory, importPolicy));
                }
            }
 
            return exports;
        }
 
        private Export CreateExport(ComposablePartDefinition partDefinition, ExportDefinition exportDefinition, bool isExportFactory, CreationPolicy importPolicy)
        {
            if (isExportFactory)
            {
                return new PartCreatorExport(this,
                            partDefinition,
                            exportDefinition);
            }
            else
            {
                return CatalogExport.CreateExport(this,
                            partDefinition,
                            exportDefinition,
                            importPolicy);
            }
        }
 
        private void OnExportsChangingInternal(object? sender, ExportsChangeEventArgs e)
        {
            UpdateRejections(e.AddedExports.Concat(e.RemovedExports), e.AtomicComposition);
        }
 
        private static ExportDefinition[] GetExportsFromPartDefinitions(IEnumerable<ComposablePartDefinition> partDefinitions)
        {
            List<ExportDefinition> exports = new List<ExportDefinition>();
 
            foreach (var partDefinition in partDefinitions)
            {
                foreach (var export in partDefinition.ExportDefinitions)
                {
                    exports.Add(export);
 
                    // While creating a PartCreatorExportDefinition for every changed definition may not be the most
                    // efficient way to do this the PartCreatorExportDefinition is very efficient and doesn't do any
                    // real work unless its metadata is pulled on. If this turns out to be a bottleneck then we
                    // will need to start tracking all the PartCreator's we hand out and only send those which we
                    // have handed out. In fact we could do the same thing for all the Exports if we wished but
                    // that requires a cache management which we don't want to do at this point.
                    exports.Add(new PartCreatorExportDefinition(export));
                }
            }
 
            return exports.ToArray();
        }
 
        private void OnCatalogChanging(object? sender, ComposablePartCatalogChangeEventArgs e)
        {
            using (var atomicComposition = new AtomicComposition(e.AtomicComposition))
            {
                // Save the preview catalog to use in place of the original while handling
                // this event
                atomicComposition.SetValue(_catalog,
                    new CatalogChangeProxy(_catalog, e.AddedDefinitions, e.RemovedDefinitions));
 
                IEnumerable<ExportDefinition> addedExports = GetExportsFromPartDefinitions(e.AddedDefinitions);
                IEnumerable<ExportDefinition> removedExports = GetExportsFromPartDefinitions(e.RemovedDefinitions);
 
                // Remove any parts based on eliminated definitions (in a atomicComposition-friendly
                // fashion)
                foreach (var definition in e.RemovedDefinitions)
                {
                    CatalogPart? removedPart = null;
                    bool removed = false;
 
                    using (_lock.LockStateForRead())
                    {
                        removed = _activatedParts.TryGetValue(definition, out removedPart);
                    }
 
                    if (removed)
                    {
                        var capturedDefinition = definition;
                        DisposePart(null, removedPart!, atomicComposition);
                        atomicComposition.AddCompleteActionAllowNull(() =>
                        {
                            using (_lock.LockStateForWrite())
                            {
                                _activatedParts.Remove(capturedDefinition);
                            }
                        });
                    }
                }
 
                UpdateRejections(addedExports.ConcatAllowingNull(removedExports)!, atomicComposition);
 
                OnExportsChanging(
                    new ExportsChangeEventArgs(addedExports, removedExports, atomicComposition));
 
                atomicComposition.AddCompleteAction(() => OnExportsChanged(
                    new ExportsChangeEventArgs(addedExports, removedExports, null)));
 
                atomicComposition.Complete();
            }
        }
 
        private CatalogPart GetComposablePart(ComposablePartDefinition partDefinition, bool isSharedPart)
        {
            ThrowIfDisposed();
            EnsureRunning();
 
            CatalogPart catalogPart;
 
            if (isSharedPart)
            {
                catalogPart = GetSharedPart(partDefinition);
            }
            else
            {
                ComposablePart part = partDefinition.CreatePart();
                catalogPart = new CatalogPart(part);
 
                if (part is IDisposable disposablePart)
                {
                    using (_lock.LockStateForWrite())
                    {
                        _partsToDispose.Add(disposablePart);
                    }
                }
            }
 
            return catalogPart;
        }
 
        private CatalogPart GetSharedPart(ComposablePartDefinition partDefinition)
        {
            CatalogPart? catalogPart = null;
 
            // look up the part
            using (_lock.LockStateForRead())
            {
                if (_activatedParts.TryGetValue(partDefinition, out catalogPart))
                {
                    return catalogPart;
                }
            }
 
            // create a part outside of the lock
            ComposablePart? newPart = partDefinition.CreatePart();
            IDisposable? disposableNewPart = newPart as IDisposable;
 
            using (_lock.LockStateForWrite())
            {
                // check if the part is still not there
                if (!_activatedParts.TryGetValue(partDefinition, out catalogPart))
                {
                    catalogPart = new CatalogPart(newPart);
                    _activatedParts.Add(partDefinition, catalogPart);
                    if (disposableNewPart != null)
                    {
                        _partsToDispose.Add(disposableNewPart);
                    }
 
                    // indicates the part has been added
                    newPart = null;
                    disposableNewPart = null;
                }
            }
 
            // if disposableNewPart != null, this means we have created a new instance of something disposable and not used it
            // Dispose of it now
            disposableNewPart?.Dispose();
 
            return catalogPart;
        }
 
        private object? GetExportedValue(CatalogPart part, ExportDefinition export, bool isSharedPart)
        {
            ThrowIfDisposed();
            EnsureRunning();
            if (part == null)
            {
                throw new ArgumentNullException(nameof(part));
            }
 
            if (export == null)
            {
                throw new ArgumentNullException(nameof(export));
            }
 
            // We don't protect against thread racing here, as "importsSatisfied" is merely an optimization
            // if two threads satisfy imports twice, the results is the same, just the perf hit is heavier.
 
            bool importsSatisfied = part.ImportsSatisfied;
            ImportEngine? importEngine = importsSatisfied ? null : _importEngine;
 
            object? exportedValue = CompositionServices.GetExportedValueFromComposedPart(
                importEngine, part.Part, export);
 
            if (!importsSatisfied)
            {
                // and set "ImportsSatisfied" to true
                part.ImportsSatisfied = true;
            }
 
            // Only hold conditional references for recomposable non-shared parts because we are
            // already holding strong references to the shared parts.
            if (exportedValue != null && !isSharedPart && part.Part.IsRecomposable())
            {
                PreventPartCollection(exportedValue, part.Part);
            }
 
            return exportedValue;
        }
 
        private void ReleasePart(object exportedValue, CatalogPart catalogPart, AtomicComposition atomicComposition)
        {
            ThrowIfDisposed();
            EnsureRunning();
 
            DisposePart(exportedValue, catalogPart, atomicComposition);
        }
 
        private void DisposePart(object? exportedValue, CatalogPart catalogPart, AtomicComposition? atomicComposition)
        {
            ArgumentNullException.ThrowIfNull(catalogPart);
 
            if (_isDisposed)
                return;
 
            ImportEngine? importEngine = null;
            using (_lock.LockStateForWrite())
            {
                if (_isDisposed)
                    return;
 
                importEngine = _importEngine;
            }
            importEngine?.ReleaseImports(catalogPart.Part, atomicComposition);
            if (exportedValue != null)
            {
                atomicComposition.AddCompleteActionAllowNull(() =>
                {
                    AllowPartCollection(exportedValue);
                });
            }
 
            if (catalogPart.Part is IDisposable diposablePart)
            {
                atomicComposition.AddCompleteActionAllowNull(() =>
                {
                    bool removed = false;
 
                    if (_isDisposed)
                        return;
                    using (_lock.LockStateForWrite())
                    {
                        if (_isDisposed)
                            return;
 
                        removed = _partsToDispose.Remove(diposablePart);
                    }
 
                    if (removed)
                    {
                        diposablePart.Dispose();
                    }
                });
            }
        }
 
        private void PreventPartCollection(object exportedValue, ComposablePart part)
        {
            ArgumentNullException.ThrowIfNull(exportedValue);
            ArgumentNullException.ThrowIfNull(part);
 
            using (_lock.LockStateForWrite())
            {
                List<ComposablePart>? partList;
 
                ConditionalWeakTable<object, List<ComposablePart>>? gcRoots = _gcRoots;
                gcRoots ??= new ConditionalWeakTable<object, List<ComposablePart>>();
 
                if (!gcRoots.TryGetValue(exportedValue, out partList))
                {
                    partList = new List<ComposablePart>();
                    gcRoots.Add(exportedValue, partList);
                }
 
                partList.Add(part);
 
                if (_gcRoots == null)
                {
                    Thread.MemoryBarrier();
                    _gcRoots = gcRoots;
                }
            }
        }
 
        private void AllowPartCollection(object gcRoot)
        {
            if (_gcRoots != null)
            {
                using (_lock.LockStateForWrite())
                {
                    _gcRoots.Remove(gcRoot);
                }
            }
        }
 
        private bool IsRejected(ComposablePartDefinition definition, AtomicComposition? atomicComposition)
        {
            // Check to see if we're currently working on the definition in question.
            // Recursive queries always answer optimistically, as if the definition hasn't
            // been rejected - because if it is we can discard all decisions that were based
            // on the faulty assumption in the first place.
            var forceRejectionTest = false;
            if (atomicComposition != null)
            {
                AtomicCompositionQueryState state = QueryPartState(atomicComposition, definition);
                switch (state)
                {
                    case AtomicCompositionQueryState.TreatAsRejected:
                        return true;
                    case AtomicCompositionQueryState.TreatAsValidated:
                        return false;
                    case AtomicCompositionQueryState.NeedsTesting:
                        forceRejectionTest = true;
                        break;
                    default:
                        if (state != AtomicCompositionQueryState.Unknown)
                        {
                            throw new Exception(SR.Diagnostic_InternalExceptionMessage);
                        }
                        // Need to do the work to determine the state
                        break;
                }
            }
 
            if (!forceRejectionTest)
            {
                // Next, anything that has been activated is not rejected
                using (_lock.LockStateForRead())
                {
                    if (_activatedParts.ContainsKey(definition))
                    {
                        return false;
                    }
 
                    // Last stop before doing the hard work: check a specific registry of rejected parts
                    if (_rejectedParts.Contains(definition))
                    {
                        return true;
                    }
                }
            }
 
            // Determine whether or not the definition's imports can be satisfied
            return DetermineRejection(definition, atomicComposition);
        }
 
        private bool EnsureRejection(AtomicComposition? atomicComposition)
        {
            return !(_disableSilentRejection && (atomicComposition == null));
        }
 
        private bool DetermineRejection(ComposablePartDefinition definition, AtomicComposition? parentAtomicComposition)
        {
            ChangeRejectedException? exception = null;
 
            // if there is no active atomic composition and rejection is disabled, there's no need to do any of the below
            if (!EnsureRejection(parentAtomicComposition))
            {
                return false;
            }
 
            using (var localAtomicComposition = new AtomicComposition(parentAtomicComposition))
            {
                // The part definition we're currently working on is treated optimistically
                // as if we know it hasn't been rejected.  This handles recursion, and if we
                // later decide that it has been rejected we'll discard all nested progress so
                // all side-effects of the mistake are erased.
                //
                // Note that this means that recursive failures that would be detected by the
                // import engine are not discovered by rejection currently.  Loops among
                // prerequisites, runaway import chains involving factories, and prerequisites
                // that cannot be fully satisfied still result in runtime errors.  Doing
                // otherwise would be possible but potentially expensive - and could be a v2
                // improvement if deemed worthwhile.
                UpdateAtomicCompositionQueryForPartEquals(localAtomicComposition,
                    definition, AtomicCompositionQueryState.TreatAsValidated);
 
                var newPart = definition.CreatePart();
                try
                {
                    Debug.Assert(_importEngine != null);
                    _importEngine.PreviewImports(newPart, localAtomicComposition);
 
                    // Reuse the partially-fleshed out part the next time we need a shared
                    // instance to keep the expense of pre-validation to a minimum.  Note that
                    // _activatedParts holds references to both shared and non-shared parts.
                    // The non-shared parts will only be used for rejection purposes only but
                    // the shared parts will be handed out when requested via GetExports as
                    // well as be used for rejection purposes.
                    localAtomicComposition.AddCompleteActionAllowNull(() =>
                    {
                        using (_lock.LockStateForWrite())
                        {
                            if (!_activatedParts.ContainsKey(definition))
                            {
                                _activatedParts.Add(definition, new CatalogPart(newPart));
                                if (newPart is IDisposable newDisposablePart)
                                {
                                    _partsToDispose.Add(newDisposablePart);
                                }
                            }
                        }
                    });
 
                    // Success! Complete any recursive work that was conditioned on this part's validation
                    localAtomicComposition.Complete();
 
                    return false;
                }
                catch (ChangeRejectedException ex)
                {
                    exception = ex;
                }
            }
 
            // If we've reached this point then this part has been rejected so we need to
            // record the rejection in our parent composition or execute it immediately if
            // one doesn't exist.
            parentAtomicComposition.AddCompleteActionAllowNull(() =>
            {
                using (_lock.LockStateForWrite())
                {
                    _rejectedParts.Add(definition);
                }
 
                CompositionTrace.PartDefinitionRejected(definition, exception);
 
            });
            if (parentAtomicComposition != null)
            {
                UpdateAtomicCompositionQueryForPartEquals(parentAtomicComposition,
                    definition, AtomicCompositionQueryState.TreatAsRejected);
            }
 
            return true;
        }
 
        private void UpdateRejections(IEnumerable<ExportDefinition> changedExports, AtomicComposition? atomicComposition)
        {
            using (var localAtomicComposition = new AtomicComposition(atomicComposition))
            {
                // Reconsider every part definition that has been previously
                // rejected to see if any of them can be added back.
                var affectedRejections = new HashSet<ComposablePartDefinition>();
 
                ComposablePartDefinition[] rejectedParts;
                using (_lock.LockStateForRead())
                {
                    rejectedParts = _rejectedParts.ToArray();
                }
                foreach (var definition in rejectedParts)
                {
                    if (QueryPartState(localAtomicComposition, definition) == AtomicCompositionQueryState.TreatAsValidated)
                    {
                        continue;
                    }
 
                    foreach (var import in definition.ImportDefinitions.Where(ImportEngine.IsRequiredImportForPreview))
                    {
                        if (changedExports.Any(import.IsConstraintSatisfiedBy))
                        {
                            affectedRejections.Add(definition);
                            break;
                        }
                    }
                }
                UpdateAtomicCompositionQueryForPartInHashSet(localAtomicComposition,
                    affectedRejections, AtomicCompositionQueryState.NeedsTesting);
 
                // Determine if any of the resurrectable parts is now available so that we can
                // notify listeners of the exact changes to exports
                var resurrectedExports = new List<ExportDefinition>();
 
                foreach (var partDefinition in affectedRejections)
                {
                    if (!IsRejected(partDefinition, localAtomicComposition))
                    {
                        // Notify listeners of the newly available exports and
                        // prepare to remove the rejected part from the list of rejections
                        resurrectedExports.AddRange(partDefinition.ExportDefinitions);
 
                        // Capture the local so that the closure below refers to the current definition
                        // in the loop and not the value of 'partDefinition' when the closure executes
                        var capturedPartDefinition = partDefinition;
                        localAtomicComposition.AddCompleteAction(() =>
                        {
                            using (_lock.LockStateForWrite())
                            {
                                _rejectedParts.Remove(capturedPartDefinition);
                            }
 
                            CompositionTrace.PartDefinitionResurrected(capturedPartDefinition);
                        });
                    }
                }
 
                // Notify anyone sourcing exports that the resurrected exports have appeared
                if (resurrectedExports.Count != 0)
                {
                    OnExportsChanging(
                        new ExportsChangeEventArgs(resurrectedExports, Array.Empty<ExportDefinition>(), localAtomicComposition));
 
                    localAtomicComposition.AddCompleteAction(() => OnExportsChanged(
                        new ExportsChangeEventArgs(resurrectedExports, Array.Empty<ExportDefinition>(), null)));
                }
 
                localAtomicComposition.Complete();
            }
        }
 
        [DebuggerStepThrough]
        private void ThrowIfDisposed()
        {
            if (_isDisposed)
            {
                throw ExceptionBuilder.CreateObjectDisposed(this);
            }
        }
 
        /// <summary>
        ///  EnsureCanRun must be called from within a lock.
        /// </summary>
        [DebuggerStepThrough]
        private void EnsureCanRun()
        {
            if ((_sourceProvider == null) || (_importEngine == null))
            {
                throw new InvalidOperationException(SR.Format(SR.ObjectMustBeInitialized, "SourceProvider")); // NOLOC
            }
        }
 
        [DebuggerStepThrough]
        private void EnsureRunning()
        {
            if (!_isRunning)
            {
                using (_lock.LockStateForWrite())
                {
                    if (!_isRunning)
                    {
                        EnsureCanRun();
                        _isRunning = true;
                    }
                }
            }
        }
 
        /// <summary>
        /// EnsureCanSet{T} must be called from within a lock.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="currentValue"></param>
        [DebuggerStepThrough]
        private void EnsureCanSet<T>(T? currentValue)
            where T : class
        {
            if ((_isRunning) || (currentValue != null))
            {
                throw new InvalidOperationException(SR.ObjectAlreadyInitialized);
            }
        }
 
        private AtomicCompositionQueryState QueryPartState(AtomicComposition atomicComposition, ComposablePartDefinition definition)
        {
            PartQueryStateNode? node = GetPartQueryStateNode(atomicComposition);
            if (node == null)
            {
                return AtomicCompositionQueryState.Unknown;
            }
            else
            {
                return node.GetQueryState(definition);
            }
        }
 
        private PartQueryStateNode? GetPartQueryStateNode(AtomicComposition atomicComposition)
        {
            atomicComposition.TryGetValue(this, out PartQueryStateNode? node);
            return node;
        }
 
        private void UpdateAtomicCompositionQueryForPartEquals(
            AtomicComposition atomicComposition,
            ComposablePartDefinition part,
            AtomicCompositionQueryState state)
        {
            PartQueryStateNode? previousNode = GetPartQueryStateNode(atomicComposition);
            atomicComposition.SetValue(this, new PartEqualsQueryStateNode(part, previousNode, state));
        }
 
        private void UpdateAtomicCompositionQueryForPartInHashSet(
            AtomicComposition atomicComposition,
            HashSet<ComposablePartDefinition> hashset,
            AtomicCompositionQueryState state)
        {
            PartQueryStateNode? previousNode = GetPartQueryStateNode(atomicComposition);
            atomicComposition.SetValue(this, new PartInHashSetQueryStateNode(hashset, previousNode, state));
        }
 
        private enum AtomicCompositionQueryState
        {
            Unknown,
            TreatAsRejected,
            TreatAsValidated,
            NeedsTesting
        };
 
        private abstract class PartQueryStateNode
        {
            private readonly PartQueryStateNode? _previousNode;
            private readonly AtomicCompositionQueryState _state;
 
            protected PartQueryStateNode(PartQueryStateNode? previousNode, AtomicCompositionQueryState state)
            {
                _previousNode = previousNode;
                _state = state;
            }
 
            protected abstract bool IsMatchingDefinition(ComposablePartDefinition part, int partHashCode);
 
            public AtomicCompositionQueryState GetQueryState(ComposablePartDefinition definition)
            {
                int hashCode = definition.GetHashCode();
                PartQueryStateNode? node = this;
                do
                {
                    if (node.IsMatchingDefinition(definition, hashCode))
                    {
                        return node._state;
                    }
                    node = node._previousNode;
                }
                while (node != null);
 
                return AtomicCompositionQueryState.Unknown;
            }
        }
 
        private sealed class PartEqualsQueryStateNode : PartQueryStateNode
        {
            private readonly ComposablePartDefinition _part;
            private readonly int _hashCode;
            public PartEqualsQueryStateNode(ComposablePartDefinition part, PartQueryStateNode? previousNode, AtomicCompositionQueryState state) :
                base(previousNode, state)
            {
                _part = part;
                _hashCode = part.GetHashCode();
            }
 
            protected override bool IsMatchingDefinition(ComposablePartDefinition part, int partHashCode)
            {
                if (partHashCode != _hashCode)
                {
                    return false;
                }
                return _part.Equals(part);
            }
        }
 
        private sealed class PartInHashSetQueryStateNode : PartQueryStateNode
        {
            private readonly HashSet<ComposablePartDefinition> _parts;
            public PartInHashSetQueryStateNode(HashSet<ComposablePartDefinition> parts, PartQueryStateNode? previousNode, AtomicCompositionQueryState state) :
                base(previousNode, state)
            {
                _parts = parts;
            }
 
            protected override bool IsMatchingDefinition(ComposablePartDefinition part, int partHashCode)
            {
                return _parts.Contains(part);
            }
        }
 
        private sealed class CatalogPart
        {
            private volatile bool _importsSatisfied;
            public CatalogPart(ComposablePart part)
            {
                Part = part;
            }
            public ComposablePart Part { get; }
 
            public bool ImportsSatisfied
            {
                get
                {
                    return _importsSatisfied;
                }
                set
                {
                    _importsSatisfied = value;
                }
            }
        }
    }
}