wirer 0.4.7

Sign up to get free protection for your applications and to get access to all the features.
data/README.rb ADDED
@@ -0,0 +1,398 @@
1
+ # Wirer
2
+ #
3
+ # A lightweight dependency injection framework to help wire up objects in Ruby.
4
+ #
5
+ # Some usage examples for now:
6
+
7
+ container = Wirer do |c|
8
+
9
+ # SHOWING THE CONTAINER HOW TO CONSTRUCT AN EXISTING CLASS:
10
+
11
+ # This is registered as providing class Logger.
12
+ # It will be constructed via Logger.new('/option_for_logger.txt')
13
+ c.add Logger, '/option_for_logger.txt'
14
+
15
+ # This is registered as providing class Logger, and also providing feature :special_logger
16
+ # which can then be used to request it in particular situations
17
+ c.add :special_logger, Logger, '/special_log.txt'
18
+
19
+ # You can supply a custom block for constructing the dependency if you want;
20
+ # specifying the class upfront means it still knows what class is provided by the block
21
+ c.add(:other_special_logger, Logger) do
22
+ Logger.new(foo, bar, baz)
23
+ end
24
+
25
+ # You don't actually have to specify the class that's provided; it will just
26
+ # provide_class Object by default. In this case you really need to specify
27
+ # a feature name for it, or else you'll have no way to refer to it:
28
+ c.add(:mystery_meat) do
29
+ rand(2) == 0 ? Mystery.new : Meat.new
30
+ end
31
+
32
+ # add_new_factory is the more explicit but verbose way to do this.
33
+ # note in this case you need to specify a :method_name separately if you want a method defined on
34
+ # the container for it.
35
+ c.add_new_factory(:class => Foo, :features => [:foo, :bar], :method_name => :foo) {Foo.new(...)}
36
+ c.add_new_factory(:class => Logger, :features => [:logger], :method_name => :logger, :args => ['/arg_for_logger.txt'])
37
+
38
+
39
+
40
+
41
+ # SPECIFYING DEPENDENCIES (which will then automatically get constructed and passed into your constructor)
42
+
43
+
44
+ # This will be constructed via LogSpewer.new(:logger => logger)
45
+ c.add LogSpewer, :logger => Logger
46
+
47
+ # however since two Loggers are available, we might want to specify
48
+ # a particular one, by making it depend on the feature :special_logger
49
+ # provided by only one of them.
50
+ c.add :special_log_spewer, LogSpewer, :logger => :special_logger
51
+
52
+ # You can specify a combination of class/module and feature name requirements for a
53
+ # dependency:
54
+ c.add :fussy_log_spewer, LogSpewer, :logger => [SpecialLogger, :providing, :these, :features]
55
+
56
+
57
+
58
+ # USING DEFAULTS and PREFERRED FEATURES TO CHOOSE FROM MANY AVAILABLE DEPENDENCIES
59
+
60
+ # If there are many Loggers available, and you have a dependency on a Logger, how does it
61
+ # decide which one to give you?
62
+ #
63
+ # Answer: It will never make an arbitrary choice for you. If there are multiple matching
64
+ # factories and it has no way to differentiate between them, it will raise an error complaining
65
+ # about it and let you fix the issue.
66
+ #
67
+ # You can either refine the dependency to a *particular* logger, as in the example above
68
+ # where we asked for a :special_logger.
69
+ #
70
+ # But it would also be nice if you could nominate one logger as the default to use in the
71
+ # case of multiple loggers, without having to specifically request it in each case:
72
+
73
+ c.add Logger, :default => true
74
+
75
+ # which is shorthand for:
76
+ c.add Logger, :features => [:default]
77
+
78
+
79
+ # this will then be chosen over any other options when resolving a dependency.
80
+ # (if more than one 'default' is available, it will still complain).
81
+
82
+ # Defaults are actually implemented under the hood using 'preferred features'.
83
+ # These are extra feature names in addition to the required features for a dependency,
84
+ # which you'd kinda like if possible but if not then no worries.
85
+ # eg:
86
+
87
+ c.add ColourfulLogSpewer, :logger => {:class => Logger, :prefer => :colour_capable_logger}
88
+
89
+ # If there are multiple matches for the logger dependency here, it will prefer one which
90
+ # provides the :colour_capable_logger feature.
91
+ # (if there are multiple :colour_capable_loggers, it will still complain).
92
+
93
+ # By default, dependencies come with a preferred feature of :default, as though they
94
+ # were constructed via:
95
+ c.add LogSpewer, :logger => {:class => Logger, :prefer => :default}
96
+
97
+ # You can even have multiple preferred_features, in which case it'll try to pick the
98
+ # dependency providing the greatest number of them. However if you need more advanced
99
+ # logic to choose the particular dependency you want
100
+
101
+
102
+
103
+
104
+ # MULTIPLE AND OPTIONAL DEPENDENCIES
105
+
106
+ # intended to be useful for extension points in plugin systems - you can have for example a
107
+ # multiple dependency on 'anything interested in listening to me' or 'anything interested
108
+ # in plugging in to this extension point'.
109
+
110
+ # You can specify cardinality options on dependencies via a longer argument form:
111
+ # by default one and only one dependency is required, but you can make it
112
+ # :multiple to get an array of all matching dependencies.
113
+ # This will be constructed as NoisyLogSpewer.new(:loggers => [logger1, logger2, ...])
114
+ c.add :i_spew_to_all_logs, NoisyLogSpewer, :loggers => {:class => Logger, :multiple => true}
115
+
116
+ # if you don't mind getting a nil if there dependency isn't available, you can make it :optional
117
+ c.add :i_spew_to_a_log_if_present, HesitantLogSpewer, :logger => {:class => Logger, :optional => true}
118
+
119
+ # or maybe you want as many are as available but don't mind if that number is zero:
120
+ # if you don't mind getting a nil if there dependency isn't available, you can make it :optional
121
+ c.add :i_spew_to_what_i_can_get, HesitantLogSpewer, :loggers => {:class => Logger, :multiple => true, :optional => true}
122
+
123
+
124
+
125
+ # a particularly complicated dependency example:
126
+ c.add :complicated, LogSpewer, :loggers => {:class => Logger, :features => [:foo, :bar], :multiple => true, :optional => true}
127
+
128
+
129
+ # CUSTOM ARGS OR CONSTRUCTOR BLOCK
130
+
131
+ # By default, dependencies are passed to the class's new method as a hash argument.
132
+ # you can customize this with a block though:
133
+ c.add(:foo, Foo, :logger => Logger) do |dependencies|
134
+ Foo.new(dependencies[:logger])
135
+ end
136
+
137
+ # And you can specify initial :args which will be passed before the dependencies hash.
138
+ # in this case it'll be constructed as
139
+ # Foo.new('initial arg', :logger => logger)
140
+ c.add(:foo, Foo, 'initial arg', :logger => Logger)
141
+
142
+
143
+
144
+ # If you need to specify any other keyword arguments for the factory, :dependencies need to be supplied separately, eg:
145
+ c.add(:foo, Foo, :dependencies => {:logger => Logger}, :args => ['initial arg'], :features => [:extra, :feature, :names])
146
+
147
+
148
+
149
+
150
+ # SETTER DEPENDENCIES AND TWO-PHASE INITIALIZATION
151
+
152
+ # Sometimes you need depdendencies to be supplied after the object has been constructed.
153
+ # Eg if you need to break a cyclic dependency.
154
+ # These kinds of dependencies can be specified as :setter_dependencies.
155
+ # An example:
156
+
157
+ c.add(Foo, :setter_dependencies => {:bar => Bar})
158
+ c.add(Bar, :setter_dependencies => {:bar => Foo})
159
+
160
+ # this situation will be wired up like so:
161
+ #
162
+ # foo = Foo.new
163
+ # bar = Bar.new
164
+ # foo.send(:bar=, bar)
165
+ # bar.send(:foo=, foo)
166
+ # foo.send(:post_initialize) if foo.respond_to?(:post_initialize)
167
+ # bar.send(:post_initialize) if bar.respond_to?(:post_initialize)
168
+ #
169
+ # Note you can get a post_initialize callback once your entire dependency graph
170
+ # is wired up and ready for action.
171
+ #
172
+ # Note that the setters and post_initialize hook used for this purpose
173
+ # can be private, if you want to limit them only to use by the container
174
+ # during two-phase initialization.
175
+
176
+
177
+ # If you need precise control over two-phase initialization, you can add your own
178
+ # Factory provided it implements Wirer::Factory::Interface.
179
+ #
180
+ # The factory implementation can, if it wants, override the default mechanism for
181
+ # injecting dependencies into instances created from it, and the default mechanism
182
+ # for post_initializing them.
183
+ #
184
+ # It can also make the setter_dependencies requested conditional on the particular
185
+ # instance constructed, which may be useful if they vary depending on arguments to
186
+ # the constructor.
187
+ add_factory_instance(my_custom_factory, :method_name => :my_custom_factory)
188
+
189
+
190
+
191
+ # ADDING AN EXISTING GLOBAL OBJECT
192
+
193
+ # Useful if you're using some (doubtless third-party ;-) library which has
194
+ # hardcoded global state or singletons in a global scope, but you want to add them
195
+ # to your container anyway so they at least appear as modular components for use by
196
+ # other stuff.
197
+
198
+ # this will work provided the global thing is not itself a class or module:
199
+ c.add :naughty_global_state, SomeLibraryWithA::GLOBAL_THINGUMY
200
+
201
+ # or this is more explicit:
202
+ c.add_instance SomeLibraryWithA::GLOBAL_THINGUMY, :method_name => :naughty_global_state
203
+
204
+ # the object will be added as providing the class of which it is an instance,
205
+ # together with any extra feature name or names that you specify.
206
+ # here multiple feature names are specified
207
+ c.add :instance => SomeLibraryWithA::GLOBAL_THINGUMY, :features => [:foo, :bar]
208
+
209
+
210
+
211
+
212
+ # NON-SINGLETON FACTORIES
213
+
214
+ # So far every factory we added to our container has been a singleton in the scope of the container.
215
+ # This is the default and means that the container will only ever construct one instance of it, and
216
+ # will cache that instance.
217
+ #
218
+ # You can turn this off it you want though, via eg:
219
+ c.add :foo, Foo, :singleton => false
220
+
221
+ # The container will then construct a new instance whenever a Foo is required.
222
+ #
223
+ # Factories which are added as singletons can also support arguments, eg:
224
+ # container.foo(args, for, factory)
225
+ #
226
+ # These will then be passed on as additional arguments to the constructor
227
+ # block where you supply one, eg:
228
+ c.add(:foo, Foo, :singleton => false, :dependencies => {:logger => Logger}) do |dependencies, *other_args|
229
+ Foo.new(other_args, dependencies[:logger])
230
+ end
231
+
232
+ # Where you only supply a class, by default they'll be passed as additional
233
+ # arguments to the new method before the dependencies hash.
234
+ # If the last argument is a hash, dependencies will be merged into it. eg:
235
+ #
236
+ c.add(:foo, Foo, :singleton => false, :dependencies => {:logger => Logger})
237
+ # here,
238
+ # c.foo(:other => arg)
239
+ # will lead to
240
+ # Foo.new(:other => arg, :logger => logger)
241
+ # and
242
+ # c.foo(arg1, arg2, :arg3 => 'foo')
243
+ # to
244
+ # Foo.new(arg1, arg2, :arg3 => 'foo', :logger => logger)
245
+ #
246
+ # If you don't like this, just make sure to supply a constructor block.
247
+
248
+
249
+
250
+ # Note that the singleton-ness or otherwise, is not a property of the factory itself, rather
251
+ # it's specific to the context of that factory within a particular container.
252
+
253
+ # Note that when using non-singleton factories, all bets are off when it comes to wiring up
254
+ # object graphs which have cycles in them - since it can't keep constructing new instances
255
+ # all the way down.
256
+ #
257
+ # Similarly, if the same dependency occurs twice in your dependency graph,
258
+ # where a non-singleton factory is used for it, you'll obviously get multiple distinct instances
259
+ # rather than references to one shared instance.
260
+ #
261
+ # I considered providing more fine-grained control over this (eg making things a singleton in the
262
+ # scope of one particular 'construction session', but able to construct new instance for each
263
+ # such construction session) but this is out of scope for now.
264
+ end
265
+
266
+ # GETTING STUFF OUT OF A CONTAINER
267
+
268
+ # Pretty crucial eh!
269
+
270
+ # Things added via 'add' with a symbol method name as the first argument, are made available via
271
+ # corresponding methods on the container:
272
+ container.special_logger
273
+ container.mystery_meat
274
+ # You can also specify this via an explicit :method_name parameter (and in fact you need to
275
+ # specify it this if you use the slightly-lower-level add_factory / add_new_factory / add_instance
276
+ # calls)
277
+
278
+ # You can also ask the container to find any kind of dependency, via
279
+ # passing dependency specification arguments to []:
280
+ container[Logger]
281
+ container[:special_logger]
282
+ container[Logger, :multiple => true]
283
+ container[SomeClass, :and, :some, :features]
284
+ container[SomeModule]
285
+ container[SomeModule, :optional => true]
286
+ # unless you specify :optional => true, it'll whinge if the dependency can't be fulfilled.
287
+
288
+
289
+
290
+ # DSL FOR EXPRESSING DEPENDENCIES FOR A PARTICULAR CLASS
291
+ #
292
+ # This is really handy if you're writing classes which are designed to be
293
+ # components that are ready to be wired up by a Wirer::Container.
294
+ #
295
+ # Using the DSL makes your class instance itself expose Wirer::Factory::Interface,
296
+ # meaning it can be added to a container without having to manually state
297
+ # its dependencies or provided features. The container will 'just know'.
298
+ #
299
+ # (although as we shall see, you can refine the dependencies when adding it to a
300
+ # container, to override the defaults within that particular context if you need to;
301
+ # you can also specify extra provided features within the container context)
302
+
303
+ class Foo
304
+ wireable # extends with the DSL methods (Wirer::Factory::ClassDSL)
305
+
306
+ # Declare some dependencies.
307
+ dependency :logger, Logger
308
+ dependency :arg_name, DesiredClass, :other, :desired, :feature, :names, :optional => true
309
+ dependency :arg_name, :class => DesiredClass, :features => [:desired, :feature, :names], :optional => true
310
+
311
+ # to avoid cyclic load-order dependencies between classes using this DSL, you can specify a class or module
312
+ # name as a string to be resolved later. In this case you need to use the explicit :class => foo args style.
313
+ dependency :something, :class => "Some::Thing"
314
+
315
+ # you can declare extra features which the class factory provides:
316
+ provides_feature :foo, :bar, :baz
317
+
318
+ # and setter dependencies
319
+ setter_dependency :foo, Foo
320
+ # (by default this will also define a private attr_writer :foo for you, which is
321
+ # what it will use by default to inject the dependency)
322
+
323
+ # you can also override the factory methods which the class has been extended with.
324
+ # the most common case would be where you want to customize how instances are
325
+ # constructed from the named dependencies (and any other args), via eg:
326
+ def self.new_from_dependencies(dependencies, *other_args)
327
+ new(dependencies[:foo], dependencies[:bar], *other_args)
328
+ end
329
+
330
+ # you could also add extra instance-specific setter dependencies, eg via:
331
+ def self.setter_dependencies(instance)
332
+ result = super
333
+ result[:extra] = Wirer::Dependency.new(...) if instance.something?
334
+ result
335
+ end
336
+
337
+ # Or to customize the way that setter dependencies are injected:
338
+ def self.inject_dependency(instance, arg_name, dependency)
339
+ instance.instance_variable_set(:"@#{arg_name}", dependency)
340
+ end
341
+ end
342
+
343
+ class Bar < Foo
344
+ # when using Wirer::Factory::ClassDSL, subclasses inherit their superclass's dependencies
345
+ # and features, but you can add new ones:
346
+ dependency :another_thing, Wotsit
347
+ provides_feature :extra
348
+
349
+ # or override existing dependencies
350
+ dependency :logger, :special_logger
351
+
352
+ # or if you don't want this inheritance between the class factory instances of subclasses,
353
+ # you can just extend with Wirer::Factory::ClassMixin instead of using the DSL, or you
354
+ # can override constructor_dependencies / setter_dependencies / provides_features class
355
+ # methods, or both.
356
+ end
357
+
358
+ # Adding these classes into a container is then quite simple:
359
+ Wirer do |c|
360
+
361
+ # It will see that Foo.is_a?(Wirer::Factory::Interface) and add it directly as a factory
362
+ # taking into account its dependencies etc
363
+
364
+ c.add Foo
365
+
366
+ # You can *refine* the dependencies of an existing factory when adding it, eg:
367
+
368
+ c.add Foo, :logger => :special_logger
369
+
370
+ # its original dependency was just on a Logger, but now it's on a Logger which also
371
+ # provides_feature :special_logger.
372
+ #
373
+ # This allows you to customize which particular instance of a given dependency this
374
+ # class gets constructed with. It will be added using a Wirer::Factory::Wrapped around
375
+ # the original factory.
376
+
377
+ # You can also specify extra features when adding a factory, which then give you a handle
378
+ # by which to refer to it when you want it passed to some other thing. Eg to provide the
379
+ # special logger above:
380
+ c.add :special_logger, Logger
381
+
382
+ # or both at once: adding an existing factory with some extra features and some refined
383
+ # dependencies within this container's context.
384
+ c.add Foo, :features => [:special_foo], :dependencies => {:logger => :special_logger}
385
+
386
+ # (if you want to specify other arguments for Factory::Wrapped, the dependency refining
387
+ # arguments need to go in their own :dependencies arg)
388
+
389
+ # then could then eg
390
+ c.add Bar, :foo => :special_foo
391
+
392
+ # If you have an existing factory which takes arguments, you can wrap it with specific
393
+ # (initial) arguments, allowing it to be added as a singleton, eg:
394
+ c.add Foo, 'args', 'for', 'foo', :logger => Logger
395
+
396
+ # or to be more explicit:
397
+ c.add Foo, :args => ['args', 'for', 'foo'], :dependencies => {:logger => Logger}
398
+ end
@@ -0,0 +1,283 @@
1
+ require 'thread'
2
+
3
+ module Wirer
4
+ # A container is a collection of factories, together with logic for constructing
5
+ # instances from these factories in a way that satisfies their dependencies.
6
+ #
7
+ # By default, a Factory acts as a singleton in the context of a Container which
8
+ # it's added to, meaning that only one instance of that factory will be created by
9
+ # the Container. This instance is created lazily and cached within the container.
10
+ #
11
+ # Alternatively if don't want this, specify :singleton => false when adding it.
12
+ class Container
13
+ attr_reader :factories
14
+
15
+ def initialize
16
+ @singleton_factories_instances = {}
17
+ @factories = []
18
+ @factories_by_method_name = {}
19
+ @construction_mutex = Mutex.new
20
+ yield self if block_given?
21
+ end
22
+
23
+ # add Logger
24
+ #
25
+ # add :special_logger, Logger, '/special_logger.txt'
26
+ # add :special_logger, Logger, :args => ['/special_logger.txt']
27
+ # add(:special_logger, Logger) {|deps| Logger.new('/special_logger.txt')}
28
+ #
29
+ # add Thing, :logger => :special_logger
30
+ # add Thing, :logger => :special_logger
31
+
32
+ ADD_OPTION_NAMES = (Factory::Wrapped::OPTION_NAMES | Factory::FromArgs::OPTION_NAMES | [:method_name, :singleton]).freeze
33
+
34
+ # Provides a bunch of different convenient argument styles for adding things
35
+ # to the container.
36
+ #
37
+ # add is effectively syntactic sugar around add_factory, add_instance and add_new_factory;
38
+ # if you prefer a more explicit approach feel free to use these directly.
39
+ #
40
+ # (or if you like it *really* explcit, see add_factory_instance)
41
+ def add(*add_args, &add_block_arg)
42
+ add_options = if add_args.last.is_a?(Hash) then add_args.pop else {} end
43
+
44
+ (add_options[:features] ||= []) << :default if add_options.delete(:default)
45
+
46
+ unless add_options.empty? || ADD_OPTION_NAMES.any? {|o| add_options.include?(o)}
47
+ add_options = {:dependencies => add_options}
48
+ end
49
+
50
+ if add_args.first.is_a?(Symbol)
51
+ extra_named_feature = add_args.shift
52
+ add_options[:method_name] ||= extra_named_feature
53
+ (add_options[:features] ||= []) << extra_named_feature
54
+ end
55
+
56
+ main_arg = add_args.shift
57
+ case main_arg
58
+ when Factory::Interface
59
+ add_options[:args] = add_args unless add_args.empty?
60
+ add_factory(main_arg, add_options, &add_block_arg)
61
+ when ::Module
62
+ add_options[:class] = main_arg
63
+ add_options[:args] = add_args unless add_args.empty?
64
+ add_new_factory(add_options, &add_block_arg)
65
+ when NilClass
66
+ add_new_factory(add_options, &add_block_arg)
67
+ else
68
+ add_instance(main_arg, add_options)
69
+ end
70
+ end
71
+
72
+ # Adds an existing factory.
73
+ #
74
+ # If options for Factory::Wrapped are specified, will wrap the factory with these
75
+ # extra options / overrides prior to adding it.
76
+ def add_factory(factory, options={}, &wrapped_constructor_block)
77
+ add_options = {
78
+ :method_name => options.delete(:method_name),
79
+ :singleton => options.delete(:singleton)
80
+ }
81
+ unless options.empty? && !wrapped_constructor_block
82
+ factory = factory.wrapped_with(options, &wrapped_constructor_block)
83
+ end
84
+ add_factory_instance(factory, add_options)
85
+ end
86
+
87
+ # Adds a new Factory::FromArgs constructed from the given args.
88
+ def add_new_factory(options={}, &constructor_block)
89
+ factory = Factory::FromArgs.new(options, &constructor_block)
90
+ add_factory_instance(factory, options)
91
+ end
92
+
93
+ # Adds an instance wrapped via Factory::FromInstance
94
+ def add_instance(instance, options={})
95
+ features = options[:features]
96
+ factory = case features
97
+ when nil then Factory::FromInstance.new(instance)
98
+ when Array then Factory::FromInstance.new(instance, *features)
99
+ else Factory::FromInstance.new(instance, features)
100
+ end
101
+ add_factory_instance(factory, options)
102
+ end
103
+
104
+ # Adds a factory object, without any wrapping.
105
+ # only options are :method_name, and :singleton (default true)
106
+ def add_factory_instance(factory, options={})
107
+ method_name = options[:method_name]
108
+ singleton = (options[:singleton] != false)
109
+
110
+ raise TypeError, "expected Wirer::Factory::Interface, got #{factory.class}" unless factory.is_a?(Factory::Interface)
111
+ @factories << factory
112
+ @singleton_factories_instances[factory] = nil if singleton
113
+ if method_name
114
+ @factories_by_method_name[method_name] = factory
115
+ if respond_to?(method_name, true)
116
+ warn("Can't add constructor method because #{method_name.inspect} already defined on container")
117
+ else
118
+ instance_eval <<-EOS, __FILE__, __LINE__
119
+ def #{method_name}(*args, &block_arg)
120
+ construct_factory_by_method_name(#{method_name.inspect}, *args, &block_arg)
121
+ end
122
+ EOS
123
+ end
124
+ end
125
+ end
126
+
127
+ def factory(name)
128
+ @factories_by_method_name[name]
129
+ end
130
+
131
+ def construct_factory_by_method_name(method_name, *args, &block_arg)
132
+ factory = @factories_by_method_name[method_name]
133
+ begin
134
+ construction_session do
135
+ construct_factory(factory, *args, &block_arg)
136
+ end
137
+ rescue
138
+ raise DependencyConstructionError.new("Unable to construct factory with name #{method_name}", $!)
139
+ end
140
+ end
141
+
142
+ def [](*dep_args)
143
+ construction_session do
144
+ construct_dependency(Dependency.new_from_args(*dep_args))
145
+ end
146
+ end
147
+
148
+ # Injects (ie monkey-patches) constructor methods into a given object,
149
+ # which delegate to the corresponding constructor methods defined on the
150
+ # container.
151
+ #
152
+ # This is primarily for use as a convenience by the top-level code which is
153
+ # driving an application, to inject application services from a container
154
+ # into the context of (say) an integration test or a driver script.
155
+ #
156
+ # If you're considering using this to supply dependencies to objects
157
+ # *within* your application: instead you would usually want to add that object to
158
+ # the container with dependencies specified, and let the container construct
159
+ # it with the right dependencies. Google for discussion about 'service locator'
160
+ # pattern vs 'dependency injection' / 'IoC container' pattern for more on this.
161
+ def inject_methods_into(instance, *names)
162
+ _self = self
163
+ names.each do |name|
164
+ class << instance; self; end.send(:define_method, name) do |*args|
165
+ _self.construct_factory_by_method_name(name, *args)
166
+ end
167
+ end
168
+ end
169
+
170
+ private
171
+
172
+ # N.B. all calls to the private construct_ methods below must be wrapped with
173
+ # this at the top-level entry point.
174
+ # It wraps with a mutex, to avoid race conditions with concurrent
175
+ # attempts to construct a singleton factory, and also ensures that everything
176
+ # constructed in this session gets post_initialized at the end.
177
+ def construction_session
178
+ @construction_mutex.synchronize do
179
+ begin
180
+ @phase_1_in_progress = []
181
+ @queued_for_phase_2 = []
182
+ @queued_for_post_initialize = []
183
+
184
+ result = yield
185
+
186
+ until @queued_for_phase_2.empty?
187
+ factory_instance = @queued_for_phase_2.pop
188
+ construct_and_inject_setter_dependencies(*factory_instance)
189
+ @queued_for_post_initialize.push(factory_instance)
190
+ end
191
+
192
+ result
193
+ ensure
194
+ post_initialize(@queued_for_post_initialize)
195
+ remove_instance_variable(:@phase_1_in_progress)
196
+ remove_instance_variable(:@queued_for_post_initialize)
197
+ end
198
+ end
199
+ end
200
+
201
+ def construct_dependencies(dependencies)
202
+ result = {}
203
+ dependencies.each do |arg_name, dependency|
204
+ result[arg_name] = construct_dependency(dependency)
205
+ end
206
+ result
207
+ end
208
+
209
+ def construct_dependency(dependency)
210
+ dependency.match_factories(@factories) do |factory|
211
+ if dependency.factory?
212
+ if @singleton_factories_instances.has_key?(factory)
213
+ raise Error, "Problem with :factory => true dependency: #{factory} was added to the container as a singleton, so only a singleton instance can be supplied, not a wrapped factory"
214
+ end
215
+ curry_factory_with_constructed_dependencies(factory)
216
+ else
217
+ construct_factory_without_args(factory)
218
+ end
219
+ end
220
+ end
221
+
222
+ def construct_factory(factory, *args, &block_arg)
223
+ if args.empty? && !block_arg
224
+ construct_factory_without_args(factory)
225
+ else
226
+ construct_factory_with_args(factory, *args, &block_arg)
227
+ end
228
+ end
229
+
230
+ def construct_factory_without_args(factory)
231
+ instance = @singleton_factories_instances[factory] and return instance
232
+
233
+ if @phase_1_in_progress.include?(factory)
234
+ cycle = @phase_1_in_progress[@phase_1_in_progress.index(factory)..-1] + [factory]
235
+ raise CyclicDependencyError, "Cyclic constructor dependencies. Break the cycle by changing some into setter dependencies:\n#{cycle.map(&:inspect).join("\n")}"
236
+ end
237
+ @phase_1_in_progress.push(factory)
238
+ result = construct_with_constructor_dependencies(factory)
239
+ @phase_1_in_progress.pop
240
+
241
+ if @singleton_factories_instances.has_key?(factory)
242
+ @singleton_factories_instances[factory] = result
243
+ end
244
+
245
+ @queued_for_phase_2.push([factory, result])
246
+ result
247
+ end
248
+
249
+ def construct_factory_with_args(factory, *args, &block_arg)
250
+ result = construct_with_constructor_dependencies(factory, *args, &block_arg)
251
+ @queued_for_phase_2.push([factory, result])
252
+ result
253
+ end
254
+
255
+ def construct_with_constructor_dependencies(factory, *args, &block_arg)
256
+ deps = construct_dependencies(factory.constructor_dependencies)
257
+ begin
258
+ factory.new_from_dependencies(deps, *args, &block_arg)
259
+ rescue Wirer::Error
260
+ raise
261
+ rescue => e
262
+ wrapped = DependencyConstructionError.new("Unable to construct factory: #{factory.inspect}", e)
263
+ raise wrapped
264
+ end
265
+ end
266
+
267
+ def curry_factory_with_constructed_dependencies(factory)
268
+ deps = construct_dependencies(factory.constructor_dependencies)
269
+ factory.curry_with_dependencies(deps)
270
+ end
271
+
272
+ def construct_and_inject_setter_dependencies(factory, instance)
273
+ setter_deps = construct_dependencies(factory.setter_dependencies(instance))
274
+ setter_deps.each do |dep_name, dep|
275
+ factory.inject_dependency(instance, dep_name, dep)
276
+ end
277
+ end
278
+
279
+ def post_initialize(factories_instances)
280
+ factories_instances.each {|factory, instance| factory.post_initialize(instance)}
281
+ end
282
+ end
283
+ end