using System; using System.Collections.Generic; using System.Linq; namespace Umbraco.Core.Composing { /// /// Provides a base class for collection builders. /// /// The type of the builder. /// The type of the collection. /// The type of the items. public abstract class CollectionBuilderBase : ICollectionBuilder where TBuilder: CollectionBuilderBase where TCollection : class, IBuilderCollection { private readonly List _types = new List(); private readonly object _locker = new object(); private Type[] _registeredTypes; /// /// Gets the internal list of types as an IEnumerable (immutable). /// public IEnumerable GetTypes() => _types; /// public virtual void RegisterWith(IRegister register) { if (_registeredTypes != null) throw new InvalidOperationException("This builder has already been registered."); // register the collection register.Register(CreateCollection, CollectionLifetime); // register the types RegisterTypes(register); } /// /// Gets the collection lifetime. /// protected virtual Lifetime CollectionLifetime => Lifetime.Singleton; /// /// Configures the internal list of types. /// /// The action to execute. /// Throws if the types have already been registered. protected void Configure(Action> action) { lock (_locker) { if (_registeredTypes != null) throw new InvalidOperationException("Cannot configure a collection builder after it has been registered."); action(_types); } } /// /// Gets the types. /// /// The internal list of types. /// The list of types to register. /// Used by implementations to add types to the internal list, sort the list, etc. protected virtual IEnumerable GetRegisteringTypes(IEnumerable types) { return types; } private void RegisterTypes(IRegister register) { lock (_locker) { if (_registeredTypes != null) return; var types = GetRegisteringTypes(_types).ToArray(); // ensure they are safe foreach (var type in types) EnsureType(type, "register"); // register them - ensuring that each item is registered with the same lifetime as the collection. // NOTE: Previously each one was not registered with the same lifetime which would mean that if there // was a dependency on an individual item, it would resolve a brand new transient instance which isn't what // we would expect to happen. The same item should be resolved from the container as the collection. foreach (var type in types) register.Register(type, CollectionLifetime); _registeredTypes = types; } } /// /// Creates the collection items. /// /// The collection items. protected virtual IEnumerable CreateItems(IFactory factory) { if (_registeredTypes == null) throw new InvalidOperationException("Cannot create items before the collection builder has been registered."); return _registeredTypes // respect order .Select(x => CreateItem(factory, x)) .ToArray(); // safe } /// /// Creates a collection item. /// protected virtual TItem CreateItem(IFactory factory, Type itemType) => (TItem) factory.GetInstance(itemType); /// /// Creates a collection. /// /// A collection. /// Creates a new collection each time it is invoked. public virtual TCollection CreateCollection(IFactory factory) { return factory.CreateInstance(CreateItems(factory)); } protected Type EnsureType(Type type, string action) { if (typeof(TItem).IsAssignableFrom(type) == false) throw new InvalidOperationException($"Cannot {action} type {type.FullName} as it does not inherit from/implement {typeof(TItem).FullName}."); return type; } /// /// Gets a value indicating whether the collection contains a type. /// /// The type to look for. /// A value indicating whether the collection contains the type. /// Some builder implementations may use this to expose a public Has{T}() method, /// when it makes sense. Probably does not make sense for lazy builders, for example. public virtual bool Has() where T : TItem { return _types.Contains(typeof (T)); } /// /// Gets a value indicating whether the collection contains a type. /// /// The type to look for. /// A value indicating whether the collection contains the type. /// Some builder implementations may use this to expose a public Has{T}() method, /// when it makes sense. Probably does not make sense for lazy builders, for example. public virtual bool Has(Type type) { EnsureType(type, "find"); return _types.Contains(type); } } }