using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using LightInject; 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 : IBuilderCollection { private readonly List _types = new List(); private readonly object _locker = new object(); private Func, TCollection> _collectionCtor; private ServiceRegistration[] _registrations; /// /// Initializes a new instance of the /// class with a service container. /// /// A service container. protected CollectionBuilderBase(IServiceContainer container) { Container = container; // ReSharper disable once DoNotCallOverridableMethodsInConstructor Initialize(); } /// /// Gets the service container. /// protected IServiceContainer Container { get; } /// /// Gets the internal list of types as an IEnumerable (immutable). /// public IEnumerable GetTypes() => _types; /// /// Initializes a new instance of the builder. /// /// This is called by the constructor and, by default, registers the /// collection automatically. protected virtual void Initialize() { // compile the auto-collection constructor var argType = typeof(IEnumerable); var ctorArgTypes = new[] { argType }; var constructor = typeof(TCollection).GetConstructor(ctorArgTypes); if (constructor != null) { var exprArg = Expression.Parameter(argType, "items"); var exprNew = Expression.New(constructor, exprArg); var expr = Expression.Lambda, TCollection>>(exprNew, exprArg); _collectionCtor = expr.Compile(); } // else _collectionCtor remains null, assuming CreateCollection has been overriden // we just don't want to support re-registering collections here var registration = Container.GetAvailableService(); if (registration != null) throw new InvalidOperationException("Collection builders cannot be registered once the collection itself has been registered."); // register the collection Container.Register(_ => CreateCollection(), CollectionLifetime); } /// /// Gets the collection lifetime. /// /// Return null for transient collections. protected virtual ILifetime CollectionLifetime => new PerContainerLifetime(); /// /// 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 (_registrations != null) throw new InvalidOperationException("Cannot configure a collection builder after its types have been resolved."); 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() { lock (_locker) { if (_registrations != null) return; var types = GetRegisteringTypes(_types).ToArray(); foreach (var type in types) EnsureType(type, "register"); var prefix = GetType().FullName + "_"; var i = 0; foreach (var type in types) { var name = $"{prefix}{i++:00000}"; Container.Register(typeof(TItem), type, name); } _registrations = Container.AvailableServices .Where(x => x.ServiceName.StartsWith(prefix)) .OrderBy(x => x.ServiceName) .ToArray(); } } /// /// Creates the collection items. /// /// The arguments. /// The collection items. protected virtual IEnumerable CreateItems(params object[] args) { RegisterTypes(); // will do it only once var type = typeof (TItem); return _registrations .Select(x => (TItem) Container.GetInstanceOrThrow(type, x.ServiceName, x.ImplementingType, args)) .ToArray(); // safe } /// /// Creates a collection. /// /// A collection. /// Creates a new collection each time it is invoked. public virtual TCollection CreateCollection() { if (_collectionCtor == null) throw new InvalidOperationException("Collection auto-creation is not possible."); return _collectionCtor(CreateItems()); } 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); } } }