platform/incubation/ioc_container/lib/src/bound_method.dart

261 lines
9.5 KiB
Dart

import 'dart:mirrors';
import 'package:ioc_container/src/container.dart';
import 'package:ioc_container/src/util.dart';
import 'package:platform_contracts/contracts.dart';
class BoundMethod {
static dynamic call(Container container, dynamic callback,
[List<dynamic> parameters = const [], String? defaultMethod]) {
if (callback is String) {
if (defaultMethod == null && _hasInvokeMethod(callback)) {
defaultMethod = '__invoke';
}
return _callClass(container, callback, parameters, defaultMethod);
}
if (callback is List && callback.length == 2) {
var instance = container.make(callback[0].toString());
var method = callback[1].toString();
return _callBoundMethod(container, [instance, method], () {
throw BindingResolutionException(
'Failed to call method: $method on ${instance.runtimeType}');
}, parameters);
}
if (callback is Function) {
return _callBoundMethod(container, callback, () {
throw BindingResolutionException('Failed to call function');
}, parameters);
}
if (_isCallableWithAtSign(callback)) {
return _callClass(container, callback, parameters, defaultMethod);
}
throw ArgumentError('Invalid callback type: ${callback.runtimeType}');
}
static dynamic _callBoundMethod(
Container container, dynamic callback, Function defaultCallback,
[List<dynamic> parameters = const []]) {
if (callback is List && callback.length == 2) {
var instance = callback[0];
var method = callback[1];
if (instance is String) {
instance = container.make(instance);
}
if (method is String) {
if (instance is Function && method == '__invoke') {
return Function.apply(instance, parameters);
}
if (instance is Map && instance.containsKey(method)) {
// Handle the case where instance is a Map and method is a key
var result = instance[method];
return result is Function
? Function.apply(result, parameters)
: result;
}
var instanceMirror = reflect(instance);
var methodSymbol = Symbol(method);
if (instanceMirror.type.instanceMembers.containsKey(methodSymbol)) {
var dependencies =
_getMethodDependencies(container, instance, method, parameters);
var result = Function.apply(
instanceMirror.getField(methodSymbol).reflectee, dependencies);
return result is Function
? Function.apply(result, parameters)
: result;
} else if (method == '__invoke' && instance is Function) {
return Function.apply(instance, parameters);
} else if (instance is Type) {
// Handle static methods
var classMirror = reflectClass(instance);
if (classMirror.staticMembers.containsKey(Symbol(method))) {
var dependencies =
_getMethodDependencies(container, instance, method, parameters);
var result = Function.apply(
classMirror.getField(Symbol(method)).reflectee, dependencies);
return result is Function
? Function.apply(result, parameters)
: result;
}
}
// Try to find the method in the global scope
var globalMethod = _findGlobalMethod(method);
if (globalMethod != null) {
var result = Function.apply(globalMethod, parameters);
return result is Function
? Function.apply(result, parameters)
: result;
}
throw BindingResolutionException(
'Method $method not found on ${instance.runtimeType}');
} else if (method is Function) {
var result = Function.apply(method, [instance, ...parameters]);
return result is Function ? Function.apply(result, parameters) : result;
}
} else if (callback is Function) {
var result = Function.apply(callback, parameters);
return result is Function ? Function.apply(result, parameters) : result;
}
return Util.unwrapIfClosure(defaultCallback);
}
static dynamic _callClass(Container container, String target,
List<dynamic> parameters, String? defaultMethod) {
var segments = target.split('@');
var className = segments[0];
var method = segments.length == 2 ? segments[1] : defaultMethod;
method ??= '__invoke';
var instance = container.make(className);
if (instance is String) {
// If instance is still a string, it might be a global function
if (container.bound(instance)) {
return container.make(instance);
}
// If it's not bound, treat it as a string value
return instance;
}
if (instance is Function && method == '__invoke') {
return Function.apply(instance, parameters);
}
return _callBoundMethod(container, [instance, method], () {
throw BindingResolutionException(
'Failed to call method: $method on $className');
}, parameters);
}
static Function? _findGlobalMethod(String methodName) {
var currentMirror = currentMirrorSystem();
for (var library in currentMirror.libraries.values) {
if (library.declarations.containsKey(Symbol(methodName))) {
var declaration = library.declarations[Symbol(methodName)]!;
if (declaration is MethodMirror && declaration.isTopLevel) {
return library.getField(Symbol(methodName)).reflectee as Function;
}
}
}
return null;
}
static List _getMethodDependencies(Container container, dynamic instance,
dynamic method, List<dynamic> parameters) {
var dependencies = <dynamic>[];
var reflector = _getCallReflector(instance, method);
if (reflector != null) {
for (var parameter in reflector.parameters) {
_addDependencyForCallParameter(
container, parameter, parameters, dependencies);
}
} else if (instance is Map &&
method is String &&
instance.containsKey(method)) {
// If instance is a Map and method is a key, return the value
return [instance[method]];
} else {
// If we couldn't get a reflector, just return the original parameters
return parameters;
}
return dependencies;
}
static dynamic _resolveInstance(Container container, dynamic instance) {
if (instance is String) {
return container.make(instance);
}
return instance;
}
static bool _hasInvokeMethod(String className) {
ClassMirror? classMirror = _getClassMirror(className);
return classMirror?.declarations[Symbol('__invoke')] != null;
}
static String _normalizeMethod(List callback) {
var className = callback[0] is String
? callback[0]
: MirrorSystem.getName(
reflectClass(callback[0].runtimeType).simpleName);
return '$className@${callback[1]}';
}
static MethodMirror? _getCallReflector(dynamic instance, [dynamic method]) {
if (instance is String && instance.contains('::')) {
var parts = instance.split('::');
instance = parts[0];
method = parts[1];
} else if (instance is! Function && instance is! List && method == null) {
method = '__invoke';
}
if (instance is List && method == null) {
instance = instance[0];
method = instance[1];
}
if (method != null) {
var classMirror =
reflectClass(instance is Type ? instance : instance.runtimeType);
var methodSymbol = Symbol(method);
return classMirror.instanceMembers[methodSymbol] ??
classMirror.staticMembers[methodSymbol];
} else if (instance is Function) {
return (reflect(instance) as ClosureMirror).function;
}
return null;
}
static void _addDependencyForCallParameter(Container container,
ParameterMirror parameter, List<dynamic> parameters, List dependencies) {
var pendingDependencies = <dynamic>[];
var paramName = MirrorSystem.getName(parameter.simpleName);
if (parameters.any((p) => p is Map && p.containsKey(paramName))) {
var param =
parameters.firstWhere((p) => p is Map && p.containsKey(paramName));
pendingDependencies.add(param[paramName]);
parameters.remove(param);
} else if (parameter.type.reflectedType != dynamic) {
var className = parameter.type.reflectedType.toString();
if (parameters.any((p) => p is Map && p.containsKey(className))) {
var param =
parameters.firstWhere((p) => p is Map && p.containsKey(className));
pendingDependencies.add(param[className]);
parameters.remove(param);
} else if (parameter.isNamed) {
var variadicDependencies = container.make(className);
pendingDependencies.addAll(variadicDependencies is List
? variadicDependencies
: [variadicDependencies]);
} else {
pendingDependencies.add(container.make(className));
}
} else if (parameter.hasDefaultValue) {
pendingDependencies.add(parameter.defaultValue?.reflectee);
} else if (!parameter.isOptional &&
!parameters.any((p) => p is Map && p.containsKey(paramName))) {
throw Exception(
"Unable to resolve dependency [$parameter] in class ${parameter.owner?.qualifiedName ?? 'Unknown'}");
}
dependencies.addAll(pendingDependencies);
}
static bool _isCallableWithAtSign(dynamic callback) {
return callback is String && callback.contains('@');
}
static ClassMirror? _getClassMirror(String className) {
try {
return reflectClass(className as Type);
} catch (_) {
return null;
}
}
}