2019-04-17 02:21:51 +00:00
|
|
|
import 'dart:async';
|
2018-07-10 18:18:10 +00:00
|
|
|
import 'exception.dart';
|
|
|
|
import 'reflector.dart';
|
|
|
|
|
|
|
|
class Container {
|
|
|
|
final Reflector reflector;
|
|
|
|
final Map<Type, dynamic> _singletons = {};
|
2018-08-20 04:40:30 +00:00
|
|
|
final Map<Type, dynamic Function(Container)> _factories = {};
|
2018-10-22 15:35:35 +00:00
|
|
|
final Map<String, dynamic> _namedSingletons = {};
|
2018-08-20 04:40:30 +00:00
|
|
|
final Container _parent;
|
2018-07-10 18:18:10 +00:00
|
|
|
|
2018-08-20 04:40:30 +00:00
|
|
|
Container(this.reflector) : _parent = null;
|
2018-07-10 18:18:10 +00:00
|
|
|
|
2018-08-20 04:40:30 +00:00
|
|
|
Container._child(this._parent) : reflector = _parent.reflector;
|
2018-07-10 18:18:10 +00:00
|
|
|
|
2018-08-20 04:40:30 +00:00
|
|
|
bool get isRoot => _parent == null;
|
2018-08-11 19:07:35 +00:00
|
|
|
|
2018-08-20 04:40:30 +00:00
|
|
|
/// Creates a child [Container] that can define its own singletons and factories.
|
|
|
|
///
|
|
|
|
/// Use this to create children of a global "scope."
|
|
|
|
Container createChild() {
|
2019-10-12 13:34:15 +00:00
|
|
|
return Container._child(this);
|
2018-08-20 04:40:30 +00:00
|
|
|
}
|
2018-07-10 18:18:10 +00:00
|
|
|
|
2019-04-10 19:20:02 +00:00
|
|
|
/// Determines if the container has an injection of the given type.
|
2018-08-26 22:55:21 +00:00
|
|
|
bool has<T>([Type t]) {
|
|
|
|
var search = this;
|
|
|
|
t ??= T == dynamic ? t : T;
|
|
|
|
|
|
|
|
while (search != null) {
|
|
|
|
if (search._singletons.containsKey(t)) {
|
|
|
|
return true;
|
|
|
|
} else if (search._factories.containsKey(t)) {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
search = search._parent;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-04-10 19:20:02 +00:00
|
|
|
/// Determines if the container has a named singleton with the given [name].
|
|
|
|
bool hasNamed(String name) {
|
|
|
|
var search = this;
|
|
|
|
|
|
|
|
while (search != null) {
|
|
|
|
if (search._namedSingletons.containsKey(name)) {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
search = search._parent;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-04-17 02:21:51 +00:00
|
|
|
/// Instantiates an instance of [T], asynchronously.
|
|
|
|
///
|
|
|
|
/// It is similar to [make], but resolves an injection of either
|
|
|
|
/// `Future<T>` or `T`.
|
2019-04-17 19:31:58 +00:00
|
|
|
Future<T> makeAsync<T>([Type type]) {
|
|
|
|
type ??= T;
|
2019-10-12 13:34:15 +00:00
|
|
|
Type futureType; //.Future<T>.value(null).runtimeType;
|
2019-04-17 19:31:58 +00:00
|
|
|
|
|
|
|
if (T == dynamic) {
|
|
|
|
try {
|
|
|
|
futureType = reflector.reflectFutureOf(type).reflectedType;
|
|
|
|
} on UnsupportedError {
|
|
|
|
// Ignore this.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (has<T>(type)) {
|
|
|
|
return Future<T>.value(make(type));
|
|
|
|
} else if (has<Future<T>>()) {
|
2019-04-17 02:21:51 +00:00
|
|
|
return make<Future<T>>();
|
2019-04-17 19:31:58 +00:00
|
|
|
} else if (futureType != null) {
|
|
|
|
return make(futureType);
|
2019-04-17 02:21:51 +00:00
|
|
|
} else {
|
2019-10-12 13:34:15 +00:00
|
|
|
throw ReflectionException(
|
2019-04-17 19:31:58 +00:00
|
|
|
'No injection for Future<$type> or $type was found.');
|
2019-04-17 02:21:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-20 04:40:30 +00:00
|
|
|
/// Instantiates an instance of [T].
|
|
|
|
///
|
|
|
|
/// In contexts where a static generic type cannot be used, use
|
|
|
|
/// the [type] argument, instead of [T].
|
|
|
|
T make<T>([Type type]) {
|
|
|
|
type ??= T;
|
2018-07-10 18:18:10 +00:00
|
|
|
|
2018-08-20 04:40:30 +00:00
|
|
|
var search = this;
|
2018-07-10 18:18:10 +00:00
|
|
|
|
2018-08-20 04:40:30 +00:00
|
|
|
while (search != null) {
|
|
|
|
if (search._singletons.containsKey(type)) {
|
2018-08-26 22:55:21 +00:00
|
|
|
// Find a singleton, if any.
|
2018-08-20 04:40:30 +00:00
|
|
|
return search._singletons[type] as T;
|
2018-08-26 22:55:21 +00:00
|
|
|
} else if (search._factories.containsKey(type)) {
|
|
|
|
// Find a factory, if any.
|
2018-08-20 04:40:30 +00:00
|
|
|
return search._factories[type](this) as T;
|
|
|
|
} else {
|
|
|
|
search = search._parent;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var reflectedType = reflector.reflectType(type);
|
|
|
|
var positional = [];
|
|
|
|
var named = <String, dynamic>{};
|
|
|
|
|
|
|
|
if (reflectedType is ReflectedClass) {
|
|
|
|
bool isDefault(String name) {
|
|
|
|
return name.isEmpty || name == reflectedType.name;
|
2018-07-10 18:18:10 +00:00
|
|
|
}
|
2018-08-20 04:40:30 +00:00
|
|
|
|
|
|
|
var constructor = reflectedType.constructors.firstWhere(
|
|
|
|
(c) => isDefault(c.name),
|
2019-10-12 13:34:15 +00:00
|
|
|
orElse: () => throw ReflectionException(
|
2018-08-20 04:40:30 +00:00
|
|
|
'${reflectedType.name} has no default constructor, and therefore cannot be instantiated.'));
|
|
|
|
|
|
|
|
for (var param in constructor.parameters) {
|
|
|
|
var value = make(param.type.reflectedType);
|
|
|
|
|
|
|
|
if (param.isNamed) {
|
|
|
|
named[param.name] = value;
|
|
|
|
} else {
|
|
|
|
positional.add(value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return reflectedType.newInstance(
|
|
|
|
isDefault(constructor.name) ? '' : constructor.name,
|
|
|
|
positional,
|
2018-11-30 14:23:34 +00:00
|
|
|
named, []).reflectee as T;
|
2018-08-20 04:40:30 +00:00
|
|
|
} else {
|
2019-10-12 13:34:15 +00:00
|
|
|
throw ReflectionException(
|
2018-08-20 04:40:30 +00:00
|
|
|
'$type is not a class, and therefore cannot be instantiated.');
|
2018-07-10 18:18:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-22 15:21:58 +00:00
|
|
|
/// Shorthand for registering a factory that injects a singleton when it runs.
|
2018-10-22 15:35:35 +00:00
|
|
|
///
|
2018-10-22 15:21:58 +00:00
|
|
|
/// In many cases, you might prefer this to [registerFactory].
|
|
|
|
void registerLazySingleton<T>(T Function(Container) f, {Type as}) {
|
|
|
|
registerFactory<T>(
|
|
|
|
(container) {
|
|
|
|
var r = f(container);
|
|
|
|
container.registerSingleton<T>(r, as: as);
|
|
|
|
return r;
|
|
|
|
},
|
|
|
|
as: as,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-08-20 04:40:30 +00:00
|
|
|
void registerFactory<T>(T Function(Container) f, {Type as}) {
|
|
|
|
as ??= T;
|
|
|
|
|
|
|
|
if (_factories.containsKey(as)) {
|
2019-10-12 13:34:15 +00:00
|
|
|
throw StateError('This container already has a factory for $as.');
|
2018-08-20 04:40:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_factories[as] = f;
|
|
|
|
}
|
|
|
|
|
|
|
|
void registerSingleton<T>(T object, {Type as}) {
|
|
|
|
as ??= T == dynamic ? as : T;
|
|
|
|
|
2018-07-10 18:18:10 +00:00
|
|
|
if (_singletons.containsKey(as ?? object.runtimeType)) {
|
2019-10-12 13:34:15 +00:00
|
|
|
throw StateError(
|
2018-08-11 19:07:35 +00:00
|
|
|
'This container already has a singleton for ${as ?? object.runtimeType}.');
|
2018-07-10 18:18:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_singletons[as ?? object.runtimeType] = object;
|
|
|
|
}
|
2018-10-22 15:35:35 +00:00
|
|
|
|
|
|
|
/// Finds a named singleton.
|
2018-10-22 15:35:45 +00:00
|
|
|
///
|
2018-10-22 15:35:35 +00:00
|
|
|
/// In general, prefer using [registerSingleton] and [registerFactory].
|
2018-10-22 15:35:45 +00:00
|
|
|
///
|
2018-10-22 15:35:35 +00:00
|
|
|
/// [findByName] is best reserved for internal logic that end users of code should
|
|
|
|
/// not see.
|
|
|
|
T findByName<T>(String name) {
|
|
|
|
if (_namedSingletons.containsKey(name)) {
|
|
|
|
return _namedSingletons[name] as T;
|
|
|
|
} else if (_parent != null) {
|
|
|
|
return _parent.findByName<T>(name);
|
|
|
|
} else {
|
2019-10-12 13:34:15 +00:00
|
|
|
throw StateError(
|
2018-10-22 15:35:35 +00:00
|
|
|
'This container does not have a singleton named "$name".');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Registers a *named* singleton.
|
|
|
|
///
|
|
|
|
/// Note that this is not related to type-based injections, and exists as a mechanism
|
|
|
|
/// to enable injecting multiple instances of a type within the same container hierarchy.
|
|
|
|
void registerNamedSingleton<T>(String name, T object) {
|
|
|
|
if (_namedSingletons.containsKey(name)) {
|
2019-10-12 13:34:15 +00:00
|
|
|
throw StateError('This container already has a singleton named "$name".');
|
2018-10-22 15:35:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_namedSingletons[name] = object;
|
|
|
|
}
|
2018-07-10 18:18:10 +00:00
|
|
|
}
|