Injection.vala 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. using Invercargill;
  2. using Invercargill.DataStructures;
  3. namespace Inversion {
  4. private Dictionary<Thread, Lifo<InjectionContext>> injection_contexts_by_thread;
  5. private Mutex global_injection_context_lock;
  6. public class InjectionContext {
  7. public Registration registration { get; set; }
  8. public Type requested_type { get; set; }
  9. public Scope scope { get; set; }
  10. public Error? error { get; set;}
  11. public HashSet<Registration> stack_registrations { get; set; }
  12. public InjectionContext? previous_context { get; set; }
  13. public InjectionContext(Scope scope, Type requested_type, Registration registration, InjectionContext? previous = null) {
  14. this.scope = scope;
  15. this.registration = registration;
  16. this.previous_context = previous;
  17. this.requested_type = requested_type;
  18. stack_registrations = new HashSet<Registration>();
  19. stack_registrations.add(registration);
  20. if(previous != null) {
  21. stack_registrations.union_with(previous.stack_registrations);
  22. }
  23. }
  24. }
  25. public InjectionContext enter_new_injection_context(Scope scope, Type requested_type, Registration registration) throws Error {
  26. if(injection_contexts_by_thread == null) {
  27. global_injection_context_lock.lock();
  28. if(injection_contexts_by_thread == null) {
  29. injection_contexts_by_thread = new Dictionary<Thread, Lifo<InjectionContext>>();
  30. }
  31. global_injection_context_lock.unlock();
  32. }
  33. var thread = Thread.self<int>();
  34. Lifo<InjectionContext> stack;
  35. if(!injection_contexts_by_thread.try_get(thread, out stack)) {
  36. stack = new Lifo<InjectionContext>();
  37. stack.unblock();
  38. injection_contexts_by_thread[thread] = stack;
  39. }
  40. InjectionContext current = null;
  41. if(stack.try_peek(out current)) {
  42. if(current.error != null) {
  43. throw current.error;
  44. }
  45. if(current.stack_registrations.has(registration)) {
  46. // Cycle detected
  47. var cycle = registration.implementation_type.name();
  48. if(requested_type != registration.implementation_type) {
  49. cycle += @" (as $(requested_type.name()))";
  50. }
  51. var ctx = current;
  52. while(ctx != null && ctx.stack_registrations.has(registration)) {
  53. var label = ctx.registration.implementation_type.name();
  54. if(ctx.requested_type != ctx.registration.implementation_type) {
  55. label += @" (as $(ctx.requested_type.name()))";
  56. }
  57. cycle = @"$label => $cycle";
  58. ctx = ctx.previous_context;
  59. }
  60. throw new ContainerError.CYCLE_DETECTED(@"Cycle detected during dependency injection: $cycle ad infinitum.");
  61. }
  62. }
  63. var context = new InjectionContext(scope, requested_type, registration, current);
  64. stack.push(context);
  65. return context;
  66. }
  67. public void exit_injection_context() throws Error {
  68. var thread = Thread.self<int>();
  69. var stack = injection_contexts_by_thread[thread];
  70. var result = stack.pop();
  71. if(stack.peek_count() == 0) {
  72. injection_contexts_by_thread.remove(thread);
  73. }
  74. if(result.error != null) {
  75. throw result.error;
  76. }
  77. }
  78. public Object create_with_injection_context(Scope scope, Type requested_type, Registration registration) throws Error {
  79. enter_new_injection_context(scope, requested_type, registration);
  80. Error? error = null;
  81. Object object = null;
  82. try {
  83. object = registration.factory.create(scope);
  84. }
  85. catch(Error e) {
  86. error = e;
  87. }
  88. exit_injection_context();
  89. if(error != null) {
  90. throw error;
  91. }
  92. return object;
  93. }
  94. public InjectionContext get_injection_context() {
  95. if(injection_contexts_by_thread == null) {
  96. error("Injection context has not been initialised");
  97. }
  98. var thread = Thread.self<int>();
  99. var result = injection_contexts_by_thread.get_or_default(thread);
  100. if(result == null) {
  101. error("Injection context has not been initialised for this thread");
  102. }
  103. try {
  104. return result.peek();
  105. }
  106. catch {
  107. error("Empty injection context stack");
  108. }
  109. }
  110. public InjectionContext? try_get_injection_context() {
  111. if(injection_contexts_by_thread == null) {
  112. return null;
  113. }
  114. var thread = Thread.self<int>();
  115. var result = injection_contexts_by_thread.get_or_default(thread);
  116. if(result == null) {
  117. return null;
  118. }
  119. try {
  120. return result.peek();
  121. }
  122. catch {
  123. return null;
  124. }
  125. }
  126. public T inject<T>() {
  127. var context = get_injection_context();
  128. if(context.error != null) {
  129. return null;
  130. }
  131. try {
  132. return context.scope.resolve<T>();
  133. }
  134. catch(Error e) {
  135. context.error = e;
  136. return null;
  137. }
  138. }
  139. public Lot<T> inject_all<T>() {
  140. var context = get_injection_context();
  141. if(context.error != null) {
  142. return Iterate.nothing<T>().to_immutable_buffer();
  143. }
  144. try {
  145. return context.scope.resolve_all<T>()
  146. .to_immutable_buffer();
  147. }
  148. catch(Error e) {
  149. context.error = e;
  150. return Iterate.nothing<T>().to_immutable_buffer();
  151. }
  152. }
  153. public T? try_inject<T>() {
  154. var context = try_get_injection_context();
  155. if(context == null || context.error != null) {
  156. return null;
  157. }
  158. try {
  159. return context.scope.resolve<T>();
  160. }
  161. catch(Error e) {
  162. return null;
  163. }
  164. }
  165. public Lot<T> try_inject_all<T>() {
  166. var context = try_get_injection_context();
  167. if(context == null || context.error != null) {
  168. return Iterate.nothing<T>().to_immutable_buffer();
  169. }
  170. try {
  171. return context.scope.resolve_all<T>()
  172. .to_immutable_buffer();
  173. }
  174. catch(Error e) {
  175. return Iterate.nothing<T>().to_immutable_buffer();
  176. }
  177. }
  178. }