Click here to Skip to main content
15,896,606 members
Articles / DevOps / Testing

Composite Application Reloaded

Rate me:
Please Sign up or sign in to vote.
4.88/5 (38 votes)
11 May 2011CPOL12 min read 118.5K   1.5K   95  
A much simpler composite application library.
// -----------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// -----------------------------------------------------------------------
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.ComponentModel.Composition.Primitives;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;
using Microsoft.Internal;
using Microsoft.Internal.Collections;
using System.Collections;

namespace System.ComponentModel.Composition.Hosting
{
    /// <summary>
    ///     This class implements a threadsafe ICollection{T} of ComposablePartCatalog.
    ///     It is exposed as an ICollection(ComposablePartCatalog)
    ///     It is threadsafe, notifications are not marshalled using a SynchronizationContext.
    ///     It is Disposable.
    /// </summary>
    internal class ComposablePartCatalogCollection : ICollection<ComposablePartCatalog>, INotifyComposablePartCatalogChanged, IDisposable
    {
        private readonly Lock _lock = new Lock();
        private Action<ComposablePartCatalogChangeEventArgs> _onChanged;
        private Action<ComposablePartCatalogChangeEventArgs> _onChanging;
        private List<ComposablePartCatalog> _catalogs = new List<ComposablePartCatalog>();
        private volatile bool _isCopyNeeded = false;
        private volatile bool _isDisposed = false;
        private bool _hasChanged = false;

        public ComposablePartCatalogCollection(
            IEnumerable<ComposablePartCatalog> catalogs,
            Action<ComposablePartCatalogChangeEventArgs> onChanged,
            Action<ComposablePartCatalogChangeEventArgs> onChanging)
        {
            catalogs = catalogs ?? Enumerable.Empty<ComposablePartCatalog>();
            this._catalogs = new List<ComposablePartCatalog>(catalogs);
            this._onChanged = onChanged;
            this._onChanging = onChanging;

            SubscribeToCatalogNotifications(catalogs);
        }

        public void Add(ComposablePartCatalog item)
        {
            Requires.NotNull(item, "item");

            this.ThrowIfDisposed();

            var addedParts = new Lazy<IEnumerable<ComposablePartDefinition>>(() => item.Parts.ToArray(), LazyThreadSafetyMode.PublicationOnly);

            using (var atomicComposition = new AtomicComposition())
            {
                this.RaiseChangingEvent(addedParts, null, atomicComposition);
 
                using (new WriteLock(this._lock))
                {
                    if (this._isCopyNeeded)
                    {
                        this._catalogs = new List<ComposablePartCatalog>(this._catalogs);
                        this._isCopyNeeded = false;
                    }
                    this._hasChanged = true;
                    this._catalogs.Add(item);
                }
                
                this.SubscribeToCatalogNotifications(item);

                // Complete after the catalog changes are written
                atomicComposition.Complete();
            }

            this.RaiseChangedEvent(addedParts, null);
        }

        /// <summary>
        /// Notify when the contents of the Catalog has changed.
        /// </summary>
        public event EventHandler<ComposablePartCatalogChangeEventArgs> Changed;

        /// <summary>
        /// Notify when the contents of the Catalog has changing.
        /// </summary>
        public event EventHandler<ComposablePartCatalogChangeEventArgs> Changing;

        public void Clear()
        {
            this.ThrowIfDisposed();

            // No action is required if we are already empty
            ComposablePartCatalog[] catalogs = null;
            using (new ReadLock(this._lock))
            {
                if (this._catalogs.Count == 0)
                {
                    return;
                }
                catalogs = this._catalogs.ToArray();
            }

            //TODO-MT: This is pretty suspect - we can easily eliminate catalogs that aren't listed as being
            // removed.  Then again, the idea of trying to mutate the catalog on two threads at the same time is pretty
            // suspect to begin with.  When would that ever result in a meaningful composition?

            // We are doing this outside of the lock, so it's possible that the catalog will continute propagating events from things
            // we are about to unsubscribe from. Given the non-specificity of our event, in the worst case scenario we would simply fire 
            // unnecessary events.

            var removedParts = new Lazy<IEnumerable<ComposablePartDefinition>>(() => catalogs.SelectMany(catalog => catalog.Parts).ToArray(), LazyThreadSafetyMode.PublicationOnly);

            // Validate the changes before applying them
            using (var atomicComposition = new AtomicComposition())
            {
                this.RaiseChangingEvent(null, removedParts, atomicComposition);
                this.UnsubscribeFromCatalogNotifications(catalogs);

                using (new WriteLock(this._lock))
                {
                    this._catalogs = new List<ComposablePartCatalog>();

                    this._isCopyNeeded = false;
                    this._hasChanged = true;
                }

                // Complete after the catalog changes are written
                atomicComposition.Complete();
            }

            this.RaiseChangedEvent(null, removedParts);
        }

        public bool Contains(ComposablePartCatalog item)
        {
            Requires.NotNull(item, "item");

            this.ThrowIfDisposed();

            using (new ReadLock(this._lock))
            {
                return this._catalogs.Contains(item);
            }
        }

        public void CopyTo(ComposablePartCatalog[] array, int arrayIndex)
        {
            this.ThrowIfDisposed();

            using (new ReadLock(this._lock))
            {
                this._catalogs.CopyTo(array, arrayIndex);
            }
        }

        public int Count
        {
            get 
            {
                this.ThrowIfDisposed();

                using (new ReadLock(this._lock))
                {
                    return this._catalogs.Count;
                }
            }
        }

        public bool IsReadOnly
        {
            get
            {
                this.ThrowIfDisposed();

                return false;
            }
        }

        public bool Remove(ComposablePartCatalog item)
        {
            Requires.NotNull(item, "item");

            this.ThrowIfDisposed();

            using (new ReadLock(this._lock))
            {
                if (!this._catalogs.Contains(item))
                {
                    return false;
                }
            }

            bool isSuccessfulRemoval = false;

            var removedParts = new Lazy<IEnumerable<ComposablePartDefinition>>(() => item.Parts.ToArray(), LazyThreadSafetyMode.PublicationOnly);
            using (var atomicComposition = new AtomicComposition())
            {
                this.RaiseChangingEvent(null, removedParts, atomicComposition);

                using (new WriteLock(this._lock))
                {
                    if (_isCopyNeeded)
                    {
                        this._catalogs = new List<ComposablePartCatalog>(this._catalogs);
                        this._isCopyNeeded = false;
                    }

                    isSuccessfulRemoval = this._catalogs.Remove(item);
                    if (isSuccessfulRemoval)
                    {
                        this._hasChanged = true;
                    }
                }

                this.UnsubscribeFromCatalogNotifications(item);

                // Complete after the catalog changes are written
                atomicComposition.Complete();
            }

            this.RaiseChangedEvent(null, removedParts);

            return isSuccessfulRemoval;
        }

        internal bool HasChanged
        {
            get
            {
                this.ThrowIfDisposed();

                using (new ReadLock(this._lock))
                {
                    return this._hasChanged;
                }
            }
        }

        public IEnumerator<ComposablePartCatalog> GetEnumerator()
        {
            this.ThrowIfDisposed();

            using (new ReadLock(this._lock))
            {
                IEnumerator<ComposablePartCatalog> enumerator = this._catalogs.GetEnumerator();
                this._isCopyNeeded = true;
                return enumerator;
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

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

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (!this._isDisposed)
                {
                    bool disposeLock = false;
                    IEnumerable<ComposablePartCatalog> catalogs = null;
                    try
                    {
                        using (new WriteLock(this._lock))
                        {
                            if (!this._isDisposed)
                            {
                                disposeLock = true;

                                catalogs = this._catalogs;
                                this._catalogs = null;

                                this._isDisposed = true;
                            }
                        }
                    }
                    finally
                    {
                        if (catalogs != null)
                        {
                            this.UnsubscribeFromCatalogNotifications(catalogs);
                            catalogs.ForEach(catalog => catalog.Dispose());
                        }

                        if (disposeLock)
                        {
                            this._lock.Dispose();
                        }
                    }
                }
            }
        }

        private void RaiseChangedEvent(
            Lazy<IEnumerable<ComposablePartDefinition>> addedDefinitions,
            Lazy<IEnumerable<ComposablePartDefinition>> removedDefinitions)
        {
            if (this._onChanged == null || this.Changed == null)
            {
                return;
            }

            var added = (addedDefinitions == null ? Enumerable.Empty<ComposablePartDefinition>() : addedDefinitions.Value);
            var removed = (removedDefinitions == null ? Enumerable.Empty<ComposablePartDefinition>() : removedDefinitions.Value);

            this._onChanged.Invoke(new ComposablePartCatalogChangeEventArgs(added, removed, null));
        }

        public void OnChanged(object sender, ComposablePartCatalogChangeEventArgs e)
        {
            var changedEvent = this.Changed;
            if (changedEvent != null)
            {
                changedEvent(sender, e);
            }
        }

        private void RaiseChangingEvent(
           Lazy<IEnumerable<ComposablePartDefinition>> addedDefinitions,
           Lazy<IEnumerable<ComposablePartDefinition>> removedDefinitions,
           AtomicComposition atomicComposition)
        {
            if (this._onChanging == null || this.Changing == null)
            {
                return;
            }
            var added = (addedDefinitions == null ? Enumerable.Empty<ComposablePartDefinition>() : addedDefinitions.Value);
            var removed = (removedDefinitions == null ? Enumerable.Empty<ComposablePartDefinition>() : removedDefinitions.Value);

            this._onChanging.Invoke(new ComposablePartCatalogChangeEventArgs(added, removed, atomicComposition));
        }

        public void OnChanging(object sender, ComposablePartCatalogChangeEventArgs e)
        {
            var changingEvent = this.Changing;
            if (changingEvent != null)
            {
                changingEvent(sender, e);
            }
        }

        private void OnContainedCatalogChanged(object sender, ComposablePartCatalogChangeEventArgs e)
        {
            if (this._onChanged == null || this.Changed == null)
            {
                return;
            }

            this._onChanged.Invoke(e);
        }

        private void OnContainedCatalogChanging(object sender, ComposablePartCatalogChangeEventArgs e)
        {
            if (this._onChanging == null || this.Changing == null)
            {
                return;
            }

            this._onChanging.Invoke(e);
        }

        private void SubscribeToCatalogNotifications(ComposablePartCatalog catalog)
        {
            INotifyComposablePartCatalogChanged notifyCatalog = catalog as INotifyComposablePartCatalogChanged;
            if (notifyCatalog != null)
            {
                notifyCatalog.Changed += this.OnContainedCatalogChanged;
                notifyCatalog.Changing += this.OnContainedCatalogChanging;
            }
        }

        private void SubscribeToCatalogNotifications(IEnumerable<ComposablePartCatalog> catalogs)
        {
            foreach (var catalog in catalogs)
            {
                SubscribeToCatalogNotifications(catalog);
            }
        }

        private void UnsubscribeFromCatalogNotifications(ComposablePartCatalog catalog)
        {
            INotifyComposablePartCatalogChanged notifyCatalog = catalog as INotifyComposablePartCatalogChanged;
            if (notifyCatalog != null)
            {
                notifyCatalog.Changed -= this.OnContainedCatalogChanged;
                notifyCatalog.Changing -= this.OnContainedCatalogChanging;
            }
        }

        private void UnsubscribeFromCatalogNotifications(IEnumerable<ComposablePartCatalog> catalogs)
        {
            foreach (var catalog in catalogs)
            {
                UnsubscribeFromCatalogNotifications(catalog);
            }
        }

        private void ThrowIfDisposed()
        {
            if (this._isDisposed)
            {
                throw ExceptionBuilder.CreateObjectDisposed(this);
            }
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) http://www.ansibleww.com.au
Australia Australia
The Australia born French man who went back to Australia later in life...
Finally got over life long (and mostly hopeless usually, yay!) chronic sicknesses.
Worked in Sydney, Brisbane, Darwin, Billinudgel, Darwin and Melbourne.

Comments and Discussions