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);
}
}
}