896 lines
26 KiB
Dart
896 lines
26 KiB
Dart
import 'dart:mirrors';
|
|
import 'package:platform_contracts/contracts.dart';
|
|
import 'package:ioc_container/src/bound_method.dart';
|
|
import 'package:ioc_container/src/contextual_binding_builder.dart';
|
|
import 'package:ioc_container/src/entry_not_found_exception.dart';
|
|
import 'package:ioc_container/src/util.dart';
|
|
|
|
class Container implements ContainerContract {
|
|
static Container? _instance;
|
|
|
|
final Map<String, bool> _resolved = {};
|
|
final Map<String, Map<String, dynamic>> _bindings = {};
|
|
|
|
final Map<String, Function> _methodBindings = {};
|
|
|
|
final Map<String, Object> _instances = {};
|
|
final List<String> _scopedInstances = [];
|
|
final Map<String, String> _aliases = {};
|
|
final Map<String, List<String>> _abstractAliases = {};
|
|
final Map<String, List<Function>> _extenders = {};
|
|
final Map<String, List<String>> _tags = {};
|
|
final List<String> _buildStack = [];
|
|
final List<Map<String, dynamic>> _with = [];
|
|
final Map<String, Map<String, dynamic>> _contextual = {};
|
|
final Map<String, Function> _contextualAttributes = {};
|
|
final Map<String, List<Function>> _reboundCallbacks = {};
|
|
final List<Function> _globalBeforeResolvingCallbacks = [];
|
|
final List<Function> _globalResolvingCallbacks = [];
|
|
final List<Function> _globalAfterResolvingCallbacks = [];
|
|
final Map<String, List<Function>> _beforeResolvingCallbacks = {};
|
|
final Map<String, List<Function>> _resolvingCallbacks = {};
|
|
final Map<String, List<Function>> _afterResolvingCallbacks = {};
|
|
final Map<String, List<Function>> _afterResolvingAttributeCallbacks = {};
|
|
|
|
Container();
|
|
|
|
static Container getInstance() {
|
|
return _instance ??= Container();
|
|
}
|
|
|
|
static void setInstance(Container? container) {
|
|
_instance = container;
|
|
}
|
|
|
|
Function wrap(Function callback, [List<dynamic> parameters = const []]) {
|
|
return () => call(callback, parameters);
|
|
}
|
|
|
|
dynamic refresh(String abstract, dynamic target, String method) {
|
|
return rebinding(abstract, (Container container, dynamic instance) {
|
|
Function.apply(target[method], [instance]);
|
|
});
|
|
}
|
|
|
|
dynamic rebinding(String abstract, Function callback) {
|
|
abstract = getAlias(abstract);
|
|
|
|
_reboundCallbacks[abstract] ??= [];
|
|
_reboundCallbacks[abstract]!.add(callback);
|
|
|
|
if (bound(abstract)) {
|
|
return make(abstract);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
@override
|
|
void bindMethod(dynamic method, Function callback) {
|
|
_methodBindings[_parseBindMethod(method)] = callback;
|
|
}
|
|
|
|
String _parseBindMethod(dynamic method) {
|
|
if (method is List && method.length == 2) {
|
|
return '${method[0]}@${method[1]}';
|
|
}
|
|
return method.toString();
|
|
}
|
|
|
|
bool hasMethodBinding(String method) {
|
|
return _methodBindings.containsKey(method);
|
|
}
|
|
|
|
dynamic callMethodBinding(String method, dynamic instance) {
|
|
if (!hasMethodBinding(method)) {
|
|
throw Exception("Method binding not found for $method");
|
|
}
|
|
return _methodBindings[method]!(instance);
|
|
}
|
|
|
|
@override
|
|
bool bound(String abstract) {
|
|
return _bindings.containsKey(abstract) ||
|
|
_instances.containsKey(abstract) ||
|
|
isAlias(abstract);
|
|
}
|
|
|
|
@override
|
|
void alias(String abstract, String alias) {
|
|
if (alias == abstract) {
|
|
throw ArgumentError("[$abstract] is aliased to itself.");
|
|
}
|
|
|
|
_aliases[alias] = abstract;
|
|
|
|
_abstractAliases[abstract] ??= [];
|
|
_abstractAliases[abstract]!.add(alias);
|
|
}
|
|
|
|
@override
|
|
void tag(dynamic abstracts, String tag,
|
|
[List<String> additionalTags = const []]) {
|
|
var tags = [tag, ...additionalTags];
|
|
var abstractList = abstracts is List ? abstracts : [abstracts];
|
|
|
|
for (var tag in tags) {
|
|
if (!_tags.containsKey(tag)) {
|
|
_tags[tag] = [];
|
|
}
|
|
|
|
_tags[tag]!.addAll(abstractList.cast<String>());
|
|
}
|
|
|
|
void forgetExtenders(String abstract) {
|
|
abstract = getAlias(abstract);
|
|
_extenders.remove(abstract);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Iterable<dynamic> tagged(String tag) {
|
|
if (!_tags.containsKey(tag)) {
|
|
return [];
|
|
}
|
|
|
|
return _tags[tag]!.map((abstract) => make(abstract));
|
|
}
|
|
|
|
@override
|
|
void bind(String abstract, dynamic concrete, {bool shared = false}) {
|
|
_dropStaleInstances(abstract);
|
|
|
|
if (concrete == null) {
|
|
concrete = abstract;
|
|
}
|
|
|
|
// If concrete is not a function and not null, we store it directly
|
|
if (concrete is! Function && concrete != null) {
|
|
_bindings[abstract] = {'concrete': concrete, 'shared': shared};
|
|
} else {
|
|
// For functions or null, we wrap it in a closure
|
|
_bindings[abstract] = {
|
|
'concrete': (Container container) =>
|
|
concrete is Function ? concrete(container) : concrete,
|
|
'shared': shared
|
|
};
|
|
}
|
|
|
|
if (resolved(abstract)) {
|
|
_rebound(abstract);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void bindIf(String abstract, dynamic concrete, {bool shared = false}) {
|
|
if (!bound(abstract)) {
|
|
bind(abstract, concrete, shared: shared);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void singleton(String abstract, [dynamic concrete]) {
|
|
bind(abstract, concrete ?? abstract, shared: true);
|
|
}
|
|
|
|
@override
|
|
void singletonIf(String abstract, [dynamic concrete]) {
|
|
if (!bound(abstract)) {
|
|
singleton(abstract, concrete);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void scoped(String abstract, [dynamic concrete]) {
|
|
_scopedInstances.add(abstract);
|
|
singleton(abstract, concrete);
|
|
}
|
|
|
|
@override
|
|
void scopedIf(String abstract, [dynamic concrete]) {
|
|
if (!bound(abstract)) {
|
|
scoped(abstract, concrete);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void extend(String abstract, Function(dynamic service) closure) {
|
|
abstract = getAlias(abstract);
|
|
|
|
if (_instances.containsKey(abstract)) {
|
|
_instances[abstract] = closure(_instances[abstract]!);
|
|
_rebound(abstract);
|
|
} else {
|
|
_extenders[abstract] ??= [];
|
|
_extenders[abstract]!.add(closure);
|
|
|
|
if (resolved(abstract)) {
|
|
_rebound(abstract);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
T instance<T>(String abstract, T instance) {
|
|
_removeAbstractAlias(abstract);
|
|
|
|
bool isBound = bound(abstract);
|
|
|
|
_aliases.remove(abstract);
|
|
|
|
_instances[abstract] = instance as Object;
|
|
|
|
if (isBound) {
|
|
_rebound(abstract);
|
|
}
|
|
|
|
return instance;
|
|
}
|
|
|
|
@override
|
|
void addContextualBinding(
|
|
String concrete, String abstract, dynamic implementation) {
|
|
_contextual[concrete] ??= {};
|
|
_contextual[concrete]![getAlias(abstract)] = implementation;
|
|
}
|
|
|
|
@override
|
|
ContextualBindingBuilderContract when(dynamic concrete) {
|
|
return ContextualBindingBuilder(
|
|
this, Util.arrayWrap(concrete).map((c) => getAlias(c)).toList());
|
|
}
|
|
|
|
@override
|
|
void whenHasAttribute(String attribute, Function handler) {
|
|
_contextualAttributes[attribute] = handler;
|
|
}
|
|
|
|
@override
|
|
Function factory(String abstract) {
|
|
return () => make(abstract);
|
|
}
|
|
|
|
@override
|
|
void flush() {
|
|
_aliases.clear();
|
|
_resolved.clear();
|
|
_bindings.clear();
|
|
_instances.clear();
|
|
_abstractAliases.clear();
|
|
_scopedInstances.clear();
|
|
}
|
|
|
|
@override
|
|
T make<T>(String abstract, [List<dynamic> parameters = const []]) {
|
|
return resolve(abstract, parameters) as T;
|
|
}
|
|
|
|
@override
|
|
dynamic call(dynamic callback,
|
|
[List<dynamic> parameters = const [], String? defaultMethod]) {
|
|
return BoundMethod.call(this, callback, parameters, defaultMethod);
|
|
}
|
|
|
|
@override
|
|
bool resolved(String abstract) {
|
|
if (isAlias(abstract)) {
|
|
abstract = getAlias(abstract);
|
|
}
|
|
|
|
return _resolved.containsKey(abstract) || _instances.containsKey(abstract);
|
|
}
|
|
|
|
@override
|
|
void beforeResolving(dynamic abstract, [Function? callback]) {
|
|
if (abstract is String) {
|
|
abstract = getAlias(abstract);
|
|
}
|
|
|
|
if (abstract is Function && callback == null) {
|
|
_globalBeforeResolvingCallbacks.add(abstract);
|
|
} else {
|
|
_beforeResolvingCallbacks[abstract.toString()] ??= [];
|
|
if (callback != null) {
|
|
_beforeResolvingCallbacks[abstract.toString()]!.add(callback);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
void resolving(dynamic abstract, [Function? callback]) {
|
|
if (abstract is String) {
|
|
abstract = getAlias(abstract);
|
|
}
|
|
|
|
if (callback == null && abstract is Function) {
|
|
_globalResolvingCallbacks.add(abstract);
|
|
} else {
|
|
_resolvingCallbacks[abstract.toString()] ??= [];
|
|
if (callback != null) {
|
|
_resolvingCallbacks[abstract.toString()]!.add(callback);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
void afterResolving(dynamic abstract, [Function? callback]) {
|
|
if (abstract is String) {
|
|
abstract = getAlias(abstract);
|
|
}
|
|
|
|
if (abstract is Function && callback == null) {
|
|
_globalAfterResolvingCallbacks.add(abstract);
|
|
} else {
|
|
_afterResolvingCallbacks[abstract.toString()] ??= [];
|
|
if (callback != null) {
|
|
_afterResolvingCallbacks[abstract.toString()]!.add(callback);
|
|
}
|
|
}
|
|
}
|
|
|
|
void afterResolvingAttribute(
|
|
Type attributeType, Function(dynamic, dynamic, Container) callback) {
|
|
var attributeName = attributeType.toString();
|
|
_afterResolvingAttributeCallbacks[attributeName] ??= [];
|
|
_afterResolvingAttributeCallbacks[attributeName]!.add(callback);
|
|
|
|
// Ensure the attribute type is bound
|
|
if (!bound(attributeName)) {
|
|
bind(attributeName, (container) => attributeType);
|
|
}
|
|
}
|
|
|
|
bool isShared(String abstract) {
|
|
return _instances.containsKey(abstract) ||
|
|
(_bindings.containsKey(abstract) &&
|
|
_bindings[abstract]!['shared'] == true);
|
|
}
|
|
|
|
bool isAlias(String name) {
|
|
return _aliases.containsKey(name);
|
|
}
|
|
|
|
@override
|
|
dynamic get(String id) {
|
|
try {
|
|
return resolve(id);
|
|
} catch (e) {
|
|
if (has(id) || e is CircularDependencyException) {
|
|
rethrow;
|
|
}
|
|
throw EntryNotFoundException(id);
|
|
}
|
|
}
|
|
|
|
@override
|
|
bool has(String id) {
|
|
return bound(id);
|
|
}
|
|
|
|
void _dropStaleInstances(String abstract) {
|
|
_instances.remove(abstract);
|
|
_aliases.remove(abstract);
|
|
}
|
|
|
|
void _removeAbstractAlias(String abstract) {
|
|
if (!_aliases.containsKey(abstract)) return;
|
|
|
|
for (var entry in _abstractAliases.entries) {
|
|
entry.value.remove(abstract);
|
|
}
|
|
}
|
|
|
|
void _rebound(String abstract) {
|
|
var instance = make(abstract);
|
|
|
|
for (var callback in _getReboundCallbacks(abstract)) {
|
|
callback(this, instance);
|
|
}
|
|
}
|
|
|
|
List<Function> _getReboundCallbacks(String abstract) {
|
|
return _reboundCallbacks[abstract] ?? [];
|
|
}
|
|
|
|
dynamic resolve(String abstract,
|
|
[List<dynamic> parameters = const [], bool raiseEvents = true]) {
|
|
abstract = getAlias(abstract);
|
|
|
|
if (_buildStack.contains(abstract)) {
|
|
throw CircularDependencyException([..._buildStack, abstract]);
|
|
}
|
|
|
|
_buildStack.add(abstract);
|
|
|
|
try {
|
|
if (raiseEvents) {
|
|
_fireBeforeResolvingCallbacks(abstract, parameters);
|
|
}
|
|
|
|
var concrete = _getContextualConcrete(abstract);
|
|
|
|
var needsContextualBuild = parameters.isNotEmpty || concrete != null;
|
|
|
|
if (_instances.containsKey(abstract) && !needsContextualBuild) {
|
|
return _instances[abstract];
|
|
}
|
|
|
|
_with.add(Map<String, dynamic>.fromEntries(parameters
|
|
.asMap()
|
|
.entries
|
|
.map((e) => MapEntry(e.key.toString(), e.value))));
|
|
|
|
if (concrete == null) {
|
|
concrete = _getConcrete(abstract);
|
|
}
|
|
|
|
var object;
|
|
if (_isBuildable(concrete, abstract)) {
|
|
object = build(concrete);
|
|
// If the result is still a function, execute it
|
|
if (object is Function) {
|
|
object = object(this);
|
|
}
|
|
} else {
|
|
object = make(concrete);
|
|
}
|
|
|
|
for (var extender in _getExtenders(abstract)) {
|
|
object = extender(object);
|
|
}
|
|
|
|
if (isShared(abstract) && !needsContextualBuild) {
|
|
_instances[abstract] = object;
|
|
}
|
|
|
|
if (raiseEvents) {
|
|
_fireResolvingCallbacks(abstract, object);
|
|
}
|
|
|
|
if (!needsContextualBuild) {
|
|
_resolved[abstract] = true;
|
|
}
|
|
|
|
_with.removeLast();
|
|
|
|
return object;
|
|
} finally {
|
|
_buildStack.removeLast();
|
|
}
|
|
}
|
|
|
|
dynamic _getConcrete(String abstract) {
|
|
if (_bindings.containsKey(abstract)) {
|
|
return _bindings[abstract]!['concrete'];
|
|
}
|
|
|
|
return abstract;
|
|
}
|
|
|
|
bool _isBuildable(dynamic concrete, String abstract) {
|
|
return concrete == abstract || concrete is Function;
|
|
}
|
|
|
|
dynamic _resolveContextualAttribute(ContextualAttribute attribute) {
|
|
var attributeType = attribute.runtimeType.toString();
|
|
if (_contextualAttributes.containsKey(attributeType)) {
|
|
return _contextualAttributes[attributeType]!(attribute, this);
|
|
}
|
|
// Try to find a handler based on superclasses
|
|
for (var handler in _contextualAttributes.entries) {
|
|
if (reflectClass(attribute.runtimeType)
|
|
.isSubclassOf(reflectClass(handler.key as Type))) {
|
|
return handler.value(attribute, this);
|
|
}
|
|
}
|
|
throw BindingResolutionException(
|
|
"No handler registered for ContextualAttribute: $attributeType");
|
|
}
|
|
|
|
void fireBeforeResolvingAttributeCallbacks(
|
|
List<InstanceMirror> annotations, dynamic object) {
|
|
for (var annotation in annotations) {
|
|
if (annotation.reflectee is ContextualAttribute) {
|
|
var instance = annotation.reflectee as ContextualAttribute;
|
|
var attributeType = instance.runtimeType.toString();
|
|
if (_beforeResolvingCallbacks.containsKey(attributeType)) {
|
|
for (var callback in _beforeResolvingCallbacks[attributeType]!) {
|
|
callback(instance, object, this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void fireAfterResolvingAttributeCallbacks(
|
|
List<InstanceMirror> annotations, dynamic object) {
|
|
for (var annotation in annotations) {
|
|
var instance = annotation.reflectee;
|
|
var attributeType = instance.runtimeType.toString();
|
|
if (_afterResolvingAttributeCallbacks.containsKey(attributeType)) {
|
|
for (var callback
|
|
in _afterResolvingAttributeCallbacks[attributeType]!) {
|
|
callback(instance, object, this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void forgetInstance(String abstract) {
|
|
_instances.remove(abstract);
|
|
}
|
|
|
|
void forgetInstances() {
|
|
_instances.clear();
|
|
}
|
|
|
|
void forgetScopedInstances() {
|
|
for (var scoped in _scopedInstances) {
|
|
_instances.remove(scoped);
|
|
}
|
|
}
|
|
|
|
void forgetExtenders(String abstract) {
|
|
abstract = getAlias(abstract);
|
|
_extenders.remove(abstract);
|
|
}
|
|
|
|
T makeScoped<T>(String abstract) {
|
|
// This is similar to make, but ensures the instance is scoped
|
|
var instance = make<T>(abstract);
|
|
if (!_scopedInstances.contains(abstract)) {
|
|
_scopedInstances.add(abstract);
|
|
}
|
|
return instance;
|
|
}
|
|
|
|
Map<String, Map<String, dynamic>> getBindings() {
|
|
return Map.from(_bindings);
|
|
}
|
|
|
|
dynamic build(dynamic concrete) {
|
|
if (concrete is Function) {
|
|
_buildStack.add(concrete.toString());
|
|
try {
|
|
return concrete(this);
|
|
} finally {
|
|
_buildStack.removeLast();
|
|
}
|
|
}
|
|
|
|
if (concrete is String) {
|
|
// First, check if it's a simple value binding
|
|
if (_bindings.containsKey(concrete) &&
|
|
_bindings[concrete]!['concrete'] is! Function) {
|
|
return _bindings[concrete]!['concrete'];
|
|
}
|
|
|
|
// If it's not a simple value, proceed with class resolution
|
|
try {
|
|
Symbol classSymbol = MirrorSystem.getSymbol(concrete)!;
|
|
ClassMirror? classMirror;
|
|
|
|
// Search for the class in all libraries
|
|
for (var lib in currentMirrorSystem().libraries.values) {
|
|
if (lib.declarations.containsKey(classSymbol)) {
|
|
var declaration = lib.declarations[classSymbol]!;
|
|
if (declaration is ClassMirror) {
|
|
classMirror = declaration;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (classMirror == null) {
|
|
// If we can't find a class, return the string as is
|
|
return concrete;
|
|
}
|
|
|
|
var classAttributes = classMirror.metadata;
|
|
fireBeforeResolvingAttributeCallbacks(classAttributes, null);
|
|
|
|
MethodMirror? constructor = classMirror.declarations.values
|
|
.whereType<MethodMirror>()
|
|
.firstWhere((d) => d.isConstructor,
|
|
orElse: () => null as MethodMirror);
|
|
|
|
if (constructor == null) {
|
|
throw BindingResolutionException(
|
|
"No constructor found for [$concrete]");
|
|
}
|
|
|
|
List parameters = _resolveDependencies(constructor.parameters);
|
|
var instance =
|
|
classMirror.newInstance(Symbol.empty, parameters).reflectee;
|
|
|
|
// Apply attributes to the instance
|
|
for (var attribute in classAttributes) {
|
|
var attributeType = attribute.reflectee.runtimeType;
|
|
var attributeTypeName = attributeType.toString();
|
|
if (_afterResolvingAttributeCallbacks
|
|
.containsKey(attributeTypeName)) {
|
|
for (var callback
|
|
in _afterResolvingAttributeCallbacks[attributeTypeName]!) {
|
|
callback(attribute.reflectee, instance, this);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply attributes to properties
|
|
var instanceMirror = reflect(instance);
|
|
for (var declaration in classMirror.declarations.values) {
|
|
if (declaration is VariableMirror) {
|
|
for (var attribute in declaration.metadata) {
|
|
var attributeType = attribute.reflectee.runtimeType;
|
|
var attributeTypeName = attributeType.toString();
|
|
if (_afterResolvingAttributeCallbacks
|
|
.containsKey(attributeTypeName)) {
|
|
for (var callback
|
|
in _afterResolvingAttributeCallbacks[attributeTypeName]!) {
|
|
var propertyValue =
|
|
instanceMirror.getField(declaration.simpleName).reflectee;
|
|
callback(attribute.reflectee, propertyValue, this);
|
|
instanceMirror.setField(
|
|
declaration.simpleName, propertyValue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply after resolving callbacks
|
|
fireAfterResolvingAttributeCallbacks(classAttributes, instance);
|
|
_fireAfterResolvingCallbacks(concrete, instance);
|
|
|
|
// Apply extenders after all callbacks
|
|
for (var extender in _getExtenders(concrete)) {
|
|
instance = extender(instance);
|
|
}
|
|
|
|
// Store the instance if it's shared
|
|
if (isShared(concrete)) {
|
|
_instances[concrete] = instance;
|
|
}
|
|
|
|
// Ensure the instance is stored before returning
|
|
if (_instances.containsKey(concrete)) {
|
|
return _instances[concrete];
|
|
}
|
|
|
|
return instance;
|
|
} catch (e) {
|
|
// If any error occurs during class instantiation, return the string as is
|
|
return concrete;
|
|
}
|
|
}
|
|
|
|
// If concrete is neither a Function nor a String, return it as is
|
|
return concrete;
|
|
}
|
|
|
|
dynamic _getContextualConcrete(String abstract) {
|
|
if (_buildStack.isNotEmpty) {
|
|
var building = _buildStack.last;
|
|
if (_contextual.containsKey(building) &&
|
|
_contextual[building]!.containsKey(abstract)) {
|
|
return _contextual[building]![abstract];
|
|
}
|
|
|
|
// Check for attribute-based contextual bindings
|
|
try {
|
|
var buildingType = MirrorSystem.getSymbol(building);
|
|
var buildingMirror = currentMirrorSystem()
|
|
.findLibrary(buildingType)
|
|
?.declarations[buildingType];
|
|
if (buildingMirror is ClassMirror) {
|
|
for (var attribute in buildingMirror.metadata) {
|
|
if (attribute.reflectee is ContextualAttribute) {
|
|
var contextualAttribute =
|
|
attribute.reflectee as ContextualAttribute;
|
|
if (_contextualAttributes
|
|
.containsKey(contextualAttribute.runtimeType.toString())) {
|
|
var handler = _contextualAttributes[
|
|
contextualAttribute.runtimeType.toString()]!;
|
|
return handler(contextualAttribute, this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// If we can't find the class, just continue
|
|
}
|
|
}
|
|
|
|
if (_buildStack.isNotEmpty) {
|
|
if (_contextual.containsKey(_buildStack.last) &&
|
|
_contextual[_buildStack.last]!.containsKey(abstract)) {
|
|
return _contextual[_buildStack.last]![abstract];
|
|
}
|
|
}
|
|
|
|
if (_abstractAliases.containsKey(abstract)) {
|
|
for (var alias in _abstractAliases[abstract]!) {
|
|
if (_buildStack.isNotEmpty &&
|
|
_contextual.containsKey(_buildStack.last) &&
|
|
_contextual[_buildStack.last]!.containsKey(alias)) {
|
|
return _contextual[_buildStack.last]![alias];
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
dynamic resolveFromAnnotation(InstanceMirror annotation) {
|
|
var instance = annotation.reflectee;
|
|
|
|
if (instance is ContextualAttribute) {
|
|
// Handle ContextualAttribute
|
|
return _resolveContextualAttribute(instance);
|
|
}
|
|
|
|
// Add more annotation handling as needed
|
|
|
|
throw BindingResolutionException(
|
|
"Unsupported annotation type: ${annotation.type}");
|
|
}
|
|
|
|
void fireAfterResolvingAnnotationCallbacks(
|
|
List<InstanceMirror> annotations, dynamic object) {
|
|
for (var annotation in annotations) {
|
|
if (annotation.reflectee is ContextualAttribute) {
|
|
var instance = annotation.reflectee as ContextualAttribute;
|
|
if (_afterResolvingAttributeCallbacks
|
|
.containsKey(instance.runtimeType.toString())) {
|
|
for (var callback in _afterResolvingAttributeCallbacks[
|
|
instance.runtimeType.toString()]!) {
|
|
callback(instance, object, this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
List<dynamic> _resolveDependencies(List<ParameterMirror> parameters) {
|
|
var results = <dynamic>[];
|
|
for (var parameter in parameters) {
|
|
var parameterName = MirrorSystem.getName(parameter.simpleName);
|
|
if (_hasParameterOverride(parameterName)) {
|
|
results.add(_getParameterOverride(parameterName));
|
|
} else {
|
|
var annotations = parameter.metadata;
|
|
if (annotations.isNotEmpty) {
|
|
results.add(resolveFromAnnotation(annotations.first));
|
|
} else if (parameter.type.reflectedType != dynamic) {
|
|
results.add(make(parameter.type.reflectedType.toString()));
|
|
} else if (parameter.isOptional && parameter.defaultValue != null) {
|
|
results.add(parameter.defaultValue!.reflectee);
|
|
} else {
|
|
throw BindingResolutionException(
|
|
"Unable to resolve parameter $parameterName");
|
|
}
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
bool _hasParameterOverride(String parameterName) {
|
|
return _getLastParameterOverride().containsKey(parameterName);
|
|
}
|
|
|
|
dynamic _getParameterOverride(String parameterName) {
|
|
return _getLastParameterOverride()[parameterName];
|
|
}
|
|
|
|
List<Function> _getExtenders(String abstract) {
|
|
return _extenders[getAlias(abstract)] ?? [];
|
|
}
|
|
|
|
Map<String, dynamic> _getLastParameterOverride() {
|
|
return _with.isNotEmpty ? _with.last : {};
|
|
}
|
|
|
|
void _fireBeforeResolvingCallbacks(
|
|
String abstract, List<dynamic> parameters) {
|
|
_fireCallbackArray(abstract, parameters, _globalBeforeResolvingCallbacks);
|
|
|
|
for (var entry in _beforeResolvingCallbacks.entries) {
|
|
if (entry.key == abstract || isSubclassOf(abstract, entry.key)) {
|
|
_fireCallbackArray(abstract, parameters, entry.value);
|
|
}
|
|
}
|
|
}
|
|
|
|
void _fireResolvingCallbacks(String abstract, dynamic object) {
|
|
_fireCallbackArray(object, null, _globalResolvingCallbacks);
|
|
|
|
var callbacks = _getCallbacksForType(abstract, object, _resolvingCallbacks);
|
|
_fireCallbackArray(object, null, callbacks);
|
|
|
|
_fireAfterResolvingCallbacks(abstract, object);
|
|
}
|
|
|
|
void _fireAfterResolvingCallbacks(String abstract, dynamic object) {
|
|
_fireCallbackArray(object, null, _globalAfterResolvingCallbacks);
|
|
|
|
var callbacks =
|
|
_getCallbacksForType(abstract, object, _afterResolvingCallbacks);
|
|
_fireCallbackArray(object, null, callbacks);
|
|
}
|
|
|
|
void _fireCallbackArray(
|
|
dynamic argument, List<dynamic>? parameters, List<Function> callbacks) {
|
|
for (var callback in callbacks) {
|
|
if (parameters != null) {
|
|
callback(argument, parameters, this);
|
|
} else {
|
|
callback(argument, this);
|
|
}
|
|
}
|
|
}
|
|
|
|
List<Function> _getCallbacksForType(String abstract, dynamic object,
|
|
Map<String, List<Function>> callbacksPerType) {
|
|
var results = <Function>[];
|
|
|
|
for (var entry in callbacksPerType.entries) {
|
|
if (entry.key == abstract || object.runtimeType.toString() == entry.key) {
|
|
results.addAll(entry.value);
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
bool isSubclassOf(String child, String parent) {
|
|
ClassMirror? childClass = _getClassMirror(child);
|
|
ClassMirror? parentClass = _getClassMirror(parent);
|
|
|
|
if (childClass == null || parentClass == null) {
|
|
return false;
|
|
}
|
|
|
|
if (childClass == parentClass) {
|
|
return true;
|
|
}
|
|
|
|
ClassMirror? currentClass = childClass.superclass;
|
|
while (currentClass != null) {
|
|
if (currentClass == parentClass) {
|
|
return true;
|
|
}
|
|
currentClass = currentClass.superclass;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
ClassMirror? _getClassMirror(String className) {
|
|
Symbol classSymbol = MirrorSystem.getSymbol(className)!;
|
|
for (var lib in currentMirrorSystem().libraries.values) {
|
|
if (lib.declarations.containsKey(classSymbol)) {
|
|
var declaration = lib.declarations[classSymbol]!;
|
|
if (declaration is ClassMirror) {
|
|
return declaration;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
String getAlias(String abstract) {
|
|
if (!_aliases.containsKey(abstract)) {
|
|
return abstract;
|
|
}
|
|
|
|
if (_aliases[abstract] == abstract) {
|
|
throw Exception("[$abstract] is aliased to itself.");
|
|
}
|
|
|
|
return getAlias(_aliases[abstract]!);
|
|
}
|
|
|
|
// Implement ArrayAccess-like functionality
|
|
dynamic operator [](String key) => make(key);
|
|
void operator []=(String key, dynamic value) => bind(key, value);
|
|
}
|