using Invercargill; using Invercargill.DataStructures; namespace Inversion { private Dictionary> injection_contexts_by_thread; private Mutex global_injection_context_lock; public class InjectionContext { public Registration registration { get; set; } public Type requested_type { get; set; } public Scope scope { get; set; } public Error? error { get; set;} public HashSet stack_registrations { get; set; } public InjectionContext? previous_context { get; set; } public InjectionContext(Scope scope, Type requested_type, Registration registration, InjectionContext? previous = null) { this.scope = scope; this.registration = registration; this.previous_context = previous; this.requested_type = requested_type; stack_registrations = new HashSet(); stack_registrations.add(registration); if(previous != null) { stack_registrations.union_with(previous.stack_registrations); } } } public InjectionContext enter_new_injection_context(Scope scope, Type requested_type, Registration registration) throws Error { if(injection_contexts_by_thread == null) { global_injection_context_lock.lock(); if(injection_contexts_by_thread == null) { injection_contexts_by_thread = new Dictionary>(); } global_injection_context_lock.unlock(); } var thread = Thread.self(); Lifo stack; if(!injection_contexts_by_thread.try_get(thread, out stack)) { stack = new Lifo(); stack.unblock(); injection_contexts_by_thread[thread] = stack; } InjectionContext current = null; if(stack.try_peek(out current)) { if(current.error != null) { throw current.error; } if(current.stack_registrations.has(registration)) { // Cycle detected var cycle = registration.implementation_type.name(); if(requested_type != registration.implementation_type) { cycle += @" (as $(requested_type.name()))"; } var ctx = current; while(ctx != null && ctx.stack_registrations.has(registration)) { var label = ctx.registration.implementation_type.name(); if(ctx.requested_type != ctx.registration.implementation_type) { label += @" (as $(ctx.requested_type.name()))"; } cycle = @"$label => $cycle"; ctx = ctx.previous_context; } throw new ContainerError.CYCLE_DETECTED(@"Cycle detected during dependency injection: $cycle ad infinitum."); } } var context = new InjectionContext(scope, requested_type, registration, current); stack.push(context); return context; } public void exit_injection_context() throws Error { var thread = Thread.self(); var stack = injection_contexts_by_thread[thread]; var result = stack.pop(); if(stack.peek_count() == 0) { injection_contexts_by_thread.remove(thread); } if(result.error != null) { throw result.error; } } public Object create_with_injection_context(Scope scope, Type requested_type, Registration registration) throws Error { enter_new_injection_context(scope, requested_type, registration); Error? error = null; Object object = null; try { object = registration.factory.create(scope); } catch(Error e) { error = e; } exit_injection_context(); if(error != null) { throw error; } return object; } public InjectionContext get_injection_context() { if(injection_contexts_by_thread == null) { error("Injection context has not been initialised"); } var thread = Thread.self(); var result = injection_contexts_by_thread.get_or_default(thread); if(result == null) { error("Injection context has not been initialised for this thread"); } try { return result.peek(); } catch { error("Empty injection context stack"); } } public InjectionContext? try_get_injection_context() { if(injection_contexts_by_thread == null) { return null; } var thread = Thread.self(); var result = injection_contexts_by_thread.get_or_default(thread); if(result == null) { return null; } try { return result.peek(); } catch { return null; } } public T inject() { var context = get_injection_context(); if(context.error != null) { return null; } try { return context.scope.resolve(); } catch(Error e) { context.error = e; return null; } } public Lot inject_all() { var context = get_injection_context(); if(context.error != null) { return Iterate.nothing().to_immutable_buffer(); } try { return context.scope.resolve_all() .to_immutable_buffer(); } catch(Error e) { context.error = e; return Iterate.nothing().to_immutable_buffer(); } } public T? try_inject() { var context = try_get_injection_context(); if(context == null || context.error != null) { return null; } try { return context.scope.resolve(); } catch(Error e) { return null; } } public Lot try_inject_all() { var context = try_get_injection_context(); if(context == null || context.error != null) { return Iterate.nothing().to_immutable_buffer(); } try { return context.scope.resolve_all() .to_immutable_buffer(); } catch(Error e) { return Iterate.nothing().to_immutable_buffer(); } } }