needle 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. data/doc/LICENSE-BSD +27 -0
  2. data/doc/LICENSE-GPL +280 -0
  3. data/doc/LICENSE-RUBY +56 -0
  4. data/doc/README +70 -0
  5. data/doc/manual/chapter.erb +18 -0
  6. data/doc/manual/index.erb +29 -0
  7. data/doc/manual/manual.css +192 -0
  8. data/doc/manual/manual.rb +240 -0
  9. data/doc/manual/manual.yml +48 -0
  10. data/doc/manual/page.erb +86 -0
  11. data/doc/manual/parts/01_license.txt +5 -0
  12. data/doc/manual/parts/01_support.txt +1 -0
  13. data/doc/manual/parts/01_use_cases.txt +141 -0
  14. data/doc/manual/parts/01_what_is_needle.txt +1 -0
  15. data/doc/manual/parts/02_creating.txt +9 -0
  16. data/doc/manual/parts/02_namespaces.txt +47 -0
  17. data/doc/manual/parts/02_overview.txt +3 -0
  18. data/doc/manual/parts/02_services.txt +44 -0
  19. data/doc/manual/tutorial.erb +30 -0
  20. data/doc/manual-html/chapter-1.html +354 -0
  21. data/doc/manual-html/chapter-2.html +310 -0
  22. data/doc/manual-html/chapter-3.html +154 -0
  23. data/doc/manual-html/chapter-4.html +154 -0
  24. data/doc/manual-html/chapter-5.html +154 -0
  25. data/doc/manual-html/chapter-6.html +154 -0
  26. data/doc/manual-html/chapter-7.html +154 -0
  27. data/doc/manual-html/index.html +177 -0
  28. data/doc/manual-html/manual.css +192 -0
  29. data/lib/needle/container.rb +318 -0
  30. data/lib/needle/errors.rb +32 -0
  31. data/lib/needle/include-exclude.rb +116 -0
  32. data/lib/needle/interceptor-chain.rb +162 -0
  33. data/lib/needle/interceptor.rb +189 -0
  34. data/lib/needle/log-factory.rb +207 -0
  35. data/lib/needle/logger.rb +161 -0
  36. data/lib/needle/logging-interceptor.rb +62 -0
  37. data/lib/needle/models/prototype-deferred.rb +41 -0
  38. data/lib/needle/models/prototype.rb +39 -0
  39. data/lib/needle/models/proxy.rb +84 -0
  40. data/lib/needle/models/singleton-deferred.rb +57 -0
  41. data/lib/needle/models/singleton.rb +56 -0
  42. data/lib/needle/models.rb +44 -0
  43. data/lib/needle/registry.rb +110 -0
  44. data/lib/needle/service-point.rb +109 -0
  45. data/lib/needle/version.rb +28 -0
  46. data/lib/needle.rb +54 -0
  47. data/test/ALL-TESTS.rb +21 -0
  48. data/test/models/tc_prototype.rb +53 -0
  49. data/test/models/tc_prototype_deferred.rb +54 -0
  50. data/test/models/tc_proxy.rb +51 -0
  51. data/test/models/tc_singleton.rb +53 -0
  52. data/test/models/tc_singleton_deferred.rb +54 -0
  53. data/test/tc_container.rb +246 -0
  54. data/test/tc_interceptor.rb +92 -0
  55. data/test/tc_interceptor_chain.rb +181 -0
  56. data/test/tc_logger.rb +181 -0
  57. data/test/tc_models.rb +44 -0
  58. data/test/tc_registry.rb +34 -0
  59. data/test/tc_service_point.rb +100 -0
  60. metadata +107 -0
@@ -0,0 +1,318 @@
1
+ #--
2
+ # =============================================================================
3
+ # Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
4
+ # All rights reserved.
5
+ #
6
+ # This source file is distributed as part of the Needle dependency injection
7
+ # library for Ruby. This file (and the library as a whole) may be used only as
8
+ # allowed by either the BSD license, or the Ruby license (or, by association
9
+ # with the Ruby license, the GPL). See the "doc" subdirectory of the Needle
10
+ # distribution for the texts of these licenses.
11
+ # -----------------------------------------------------------------------------
12
+ # needle website : http://needle.rubyforge.org
13
+ # project website: http://rubyforge.org/projects/needle
14
+ # =============================================================================
15
+ #++
16
+
17
+ require 'needle/errors'
18
+ require 'needle/interceptor'
19
+ require 'needle/service-point'
20
+
21
+ module Needle
22
+
23
+ # The container is the heart of Needle's model. Every Container instance is
24
+ # a miniature registry, and is really a namespace separate from every other
25
+ # Container instance. Service lookups inside of a container always look in
26
+ # +self+ first, and if not found, they then look in their parent container,
27
+ # recursively.
28
+ #
29
+ # You will rarely need to instantiate a Container directly. Instead, use the
30
+ # Container#namespace method to create new containers.
31
+ class Container
32
+
33
+ # This class is used by the #register! and #namespace! methods to allow
34
+ # an +instance_eval+'d block to create new service points simply by
35
+ # invoking imaginary methods. It is basically an empty shell, with almost
36
+ # all of the builtin methods removed from it. (This allows services like
37
+ # "hash" and "print" to be defined, where they would normally conflict
38
+ # with the Kernel methods of the same name.)
39
+ class RegistrationContext
40
+ ( private_instance_methods +
41
+ protected_instance_methods +
42
+ public_instance_methods -
43
+ [ "instance_eval", "__id__", "__send__", "initialize", "remove_const",
44
+ "method_missing", "inspect" ]
45
+ ).
46
+ each { |m| undef_method m }
47
+
48
+ # Create a new RegistrationContext that wraps the given container. All
49
+ # operations performed on this context will be delegated to the
50
+ # container.
51
+ def initialize( container )
52
+ @container = container
53
+ end
54
+
55
+ # Delegate to Container#intercept.
56
+ def intercept( name )
57
+ @container.intercept( name )
58
+ end
59
+
60
+ # Delegate to Container#namespace!. Note that this is an exception to the
61
+ # general rule regarding bang methods, since this (non-bang) method
62
+ # delegates to a bang-method. However, because this is typically called
63
+ # within the context of a bang method (like Container#register!), it felt
64
+ # redundant to have the bang here as well. Disagree? Let me know.
65
+ def namespace( *parms, &block )
66
+ @container.namespace!( *parms, &block )
67
+ end
68
+
69
+ # Any method invocation with no block and no parameters is interpreted to
70
+ # be a service reference on the wrapped container, and delegates to
71
+ # Container#[]. If the block is not given but the args are not empty, a
72
+ # NoMethodError will be raised.
73
+ #
74
+ # If a block is given, this delegates to Container#register, leaving
75
+ # all parameters in place.
76
+ def method_missing( sym, *args, &block )
77
+ if block.nil?
78
+ if args.empty?
79
+ @container[sym]
80
+ else
81
+ super
82
+ end
83
+ else
84
+ @container.register( sym, *args, &block )
85
+ end
86
+ end
87
+ end
88
+
89
+ # The container that contains this container. This will be +nil+ for
90
+ # the root of a hierarchy (see Registry).
91
+ attr_reader :parent
92
+
93
+ # The name of this container. May be +nil+.
94
+ attr_reader :name
95
+
96
+ # Create a new empty container with the given parent and name.
97
+ def initialize( parent=nil, name=nil )
98
+ @root = nil
99
+
100
+ @name = name
101
+ @parent = parent
102
+ @service_points = Hash.new
103
+ end
104
+
105
+ # Returns the root of the current hierarchy. If the container is the
106
+ # root, returns self, otherwise calls Container#root on its parent.
107
+ # The value is cached for future reference.
108
+ def root
109
+ return @root if @root
110
+ return self if parent.nil?
111
+ @root = parent.root
112
+ end
113
+
114
+ # Return the fully qualified name of this container, which is the
115
+ # container's name and all parent's names up to the root container,
116
+ # catenated together with dot characters, i.e., "one.two.three".
117
+ def fullname
118
+ return @name.to_s unless @parent
119
+ "#{@parent.fullname}.#{@name}"
120
+ end
121
+
122
+ # Register the named service with the container. When the service is
123
+ # requested (with Container#[]), the associated callback will be used
124
+ # to construct it.
125
+ #
126
+ # Usage:
127
+ #
128
+ # container.register( :calc, :model=>:prototype ) do |c|
129
+ # Calc.new( c.operations )
130
+ # end
131
+ def register( name, opts={}, &callback )
132
+ raise ArgumentError, "expect block" unless callback
133
+ name = name.to_s.intern unless name.is_a?( Symbol )
134
+ @service_points[ name ] =
135
+ ServicePoint.new( self, name, opts, &callback )
136
+ end
137
+
138
+ # Create a new RegistrationContext around the container, and then evaluate
139
+ # the block within the new context instance (via +instance_eval+).
140
+ #
141
+ # Usage:
142
+ #
143
+ # container.register! do
144
+ # calc( :model => :prototype ) { Calc.new( operations ) }
145
+ # end
146
+ def register!( &block )
147
+ raise ArgumentError, "block expected" unless block
148
+ ctx = RegistrationContext.new( self )
149
+ ctx.instance_eval( &block )
150
+ self
151
+ end
152
+
153
+ # Create a new namespace within the container. Each parameter ought to
154
+ # be the name of a namespace. If more than one parameter is given,
155
+ # each one represents the name of a new namespace to create inside of
156
+ # the last-created namespace, unless that namespace already exists.
157
+ # This makes it work analogously to FileUtils#mkdir_p (creating new
158
+ # namespaces along a path of namespaces, as needed).
159
+ #
160
+ # If a block is given, the latest namespace created is yielded to the
161
+ # block.
162
+ #
163
+ # The last parameter may be a Hash, in which case it is used to specify
164
+ # options describing how the namespace should be created.
165
+ #
166
+ # For the curious, namespaces are simply services that are implemented
167
+ # by Container. The two statements are really identical:
168
+ #
169
+ # container.namespace( :calc )
170
+ # container.register( :calc ) { |c| Needle::Container.new( c, :calc ) }
171
+ #
172
+ # Usage:
173
+ #
174
+ # container.namespace( :calc, :operations ) do |op|
175
+ # op.register( :add ) { Adder.new }
176
+ # ...
177
+ # end
178
+ #
179
+ # adder = container.calc.operations.add
180
+ def namespace( *parms )
181
+ opts = {}
182
+ opts = parms.pop if parms.last.is_a?( Hash )
183
+
184
+ if parms.length < 1
185
+ raise ArgumentError, "you must specify at least one name"
186
+ end
187
+
188
+ container = self
189
+ parms.each do |parm|
190
+ unless container.has_key?( parm )
191
+ container.register( parm, opts ) { |c| Container.new( c, parm ) }
192
+ end
193
+
194
+ container = container[parm]
195
+ end
196
+
197
+ yield container if block_given?
198
+ end
199
+
200
+ # Create a new namespace within the container. Each parameter ought to
201
+ # be the name of a namespace. If more than one parameter is given,
202
+ # each one represents the name of a new namespace to create inside of
203
+ # the last-created namespace, unless that namespace already exists.
204
+ # This makes it work analogously to FileUtils#mkdir_p (creating new
205
+ # namespaces along a path of namespaces, as needed).
206
+ #
207
+ # The last parameter may be a Hash, in which case it is used to specify
208
+ # options describing how the namespace should be created.
209
+ #
210
+ # The block is passed to the Container#register! method of the last
211
+ # namespace created.
212
+ #
213
+ # For the curious, namespaces are simply services that are implemented
214
+ # by Container. The two statements are really identical:
215
+ #
216
+ # container.namespace( :calc )
217
+ # container.register( :calc ) { |c| Needle::Container.new( c, :calc ) }
218
+ #
219
+ # Usage:
220
+ #
221
+ # container.namespace!( :calc, :operations ) do
222
+ # add { Adder.new }
223
+ # ...
224
+ # end
225
+ #
226
+ # adder = container.calc.operations.add
227
+ def namespace!( *parms, &block )
228
+ raise ArgumentError, "block expected" unless block
229
+ namespace( *parms ) { |ns| ns.register!( &block ) }
230
+ end
231
+
232
+ # Describe a new interceptor to use that will intercept method calls
233
+ # on the named service. This method returns a new Interceptor instance,
234
+ # which can be used directly to configure the behavior of the interceptor.
235
+ #
236
+ # Usage:
237
+ #
238
+ # container.intercept( :calc ).with { |c| c.logging_interceptor }
239
+ def intercept( name )
240
+ point = find_definition( name )
241
+ raise ServiceNotFound, "#{fullname}.#{name}" unless point
242
+
243
+ interceptor = Interceptor.new
244
+ point.interceptor interceptor
245
+
246
+ interceptor
247
+ end
248
+
249
+ # Searches the current container and its ancestors for the named service.
250
+ # If found, the service point (the definition of that service) is returned,
251
+ # otherwise +nil+ is returned.
252
+ def find_definition( name )
253
+ point = @service_points[ name ]
254
+ point = @parent.find_definition( name ) if @parent unless point
255
+ point
256
+ end
257
+
258
+ # Retrieves the named service, if it exists. Ancestors are searched if the
259
+ # service is not defined by the current container (see #find_definition).
260
+ # If the named service does not exist, ServiceNotFound is raised.
261
+ #
262
+ # Note that this returns the instantiated service, not the service point.
263
+ def []( name )
264
+ point = find_definition( name )
265
+ raise ServiceNotFound, "#{fullname}.#{name}" unless point
266
+
267
+ point.instance
268
+ end
269
+
270
+ # Returns +true+ if this container includes a service point with the given
271
+ # name. Returns +false+ otherwise.
272
+ def has_key?( name )
273
+ @service_points.has_key?( name )
274
+ end
275
+
276
+ # Returns +true+ if this container <em>or any ancestor</em> includes a
277
+ # service point with the given name. Returns +false+ otherwise.
278
+ def knows_key?( name )
279
+ return true if has_key?( name )
280
+ return @parent.knows_key?( name ) if @parent
281
+ false
282
+ end
283
+
284
+ # Return an array of the names of all service points in this container.
285
+ def keys
286
+ @service_points.keys
287
+ end
288
+
289
+ # As a convenience for accessing services, this delegates any message
290
+ # sent to the container (which has no parameters and no block) to
291
+ # Container#[]. Note that this incurs slightly more overhead than simply
292
+ # calling Container#[] directly, so if performance is an issue, you should
293
+ # avoid this approach.
294
+ #
295
+ # Usage:
296
+ #
297
+ # container.register( :add ) { Adder.new }
298
+ # p container.add == container[:add] # => true
299
+ #
300
+ def method_missing( sym, *args, &block )
301
+ if block.nil? && args.empty? && knows_key?( sym )
302
+ self[sym]
303
+ else
304
+ super
305
+ end
306
+ end
307
+
308
+ # Returns true if this container responds to the given message, or if it
309
+ # explicitly contains a service with the given name (see #has_key?). In
310
+ # this case, #has_key? is used instead of #knows_key? so that subcontainers
311
+ # may be used as proper hashes by their parents.
312
+ def respond_to?( sym )
313
+ @service_points.has_key?( sym ) || super
314
+ end
315
+
316
+ end
317
+
318
+ end
@@ -0,0 +1,32 @@
1
+ #--
2
+ # =============================================================================
3
+ # Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
4
+ # All rights reserved.
5
+ #
6
+ # This source file is distributed as part of the Needle dependency injection
7
+ # library for Ruby. This file (and the library as a whole) may be used only as
8
+ # allowed by either the BSD license, or the Ruby license (or, by association
9
+ # with the Ruby license, the GPL). See the "doc" subdirectory of the Needle
10
+ # distribution for the texts of these licenses.
11
+ # -----------------------------------------------------------------------------
12
+ # needle website : http://needle.rubyforge.org
13
+ # project website: http://rubyforge.org/projects/needle
14
+ # =============================================================================
15
+ #++
16
+
17
+ module Needle
18
+
19
+ # The base class for all Needle-specific errors.
20
+ class NeedleError < StandardError; end
21
+
22
+ # Raised when a requested service could not be located.
23
+ class ServiceNotFound < NeedleError; end
24
+
25
+ # Raised when there was an error configuring an interceptor.
26
+ class InterceptorConfigurationError < NeedleError; end
27
+
28
+ # Raised to denote a condition that should never occur. If this gets
29
+ # raised, it is Needle's fault, not the consumer's.
30
+ class Bug < NeedleError; end
31
+
32
+ end
@@ -0,0 +1,116 @@
1
+ #--
2
+ # =============================================================================
3
+ # Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
4
+ # All rights reserved.
5
+ #
6
+ # This source file is distributed as part of the Needle dependency injection
7
+ # library for Ruby. This file (and the library as a whole) may be used only as
8
+ # allowed by either the BSD license, or the Ruby license (or, by association
9
+ # with the Ruby license, the GPL). See the "doc" subdirectory of the Needle
10
+ # distribution for the texts of these licenses.
11
+ # -----------------------------------------------------------------------------
12
+ # needle website : http://needle.rubyforge.org
13
+ # project website: http://rubyforge.org/projects/needle
14
+ # =============================================================================
15
+ #++
16
+
17
+ require 'needle/errors'
18
+
19
+ module Needle
20
+
21
+ # A simple structure for representing a single include/exclude pattern.
22
+ IncludeExcludePattern = Struct.new( :name, :comparitor, :arity )
23
+
24
+ # A module encapsulating the functionality of a service with include/exclude
25
+ # functionality. Such functionality involves a the ability to specify a
26
+ # pair of include and exclude arrays, each of which must be an array of
27
+ # method names that should be included or excluded from some kind of
28
+ # processing.
29
+ module IncludeExclude
30
+
31
+ # This is the regular expression for parsing elements in an include or
32
+ # exclude array.
33
+ PATTERN = /^
34
+ (.*?) (?# this matches the method name pattern)
35
+ (?: (?# begin optional arity section)
36
+ \( (?# begin parenthesized section)
37
+ ([<=>])? (?# optional comparator character)
38
+ (\d+) (?# arity specification)
39
+ \) (?# end parenthesized section)
40
+ )? (?# end optional arity section)
41
+ $/x
42
+
43
+ # This is a utility function for converting an array of strings
44
+ # representing method name patterns, into an array of
45
+ # IncludeExcludePattern instances.
46
+ def build_map( array )
47
+ ( array || [] ).map do |pattern|
48
+ unless pattern =~ PATTERN
49
+ raise InterceptorConfigurationError,
50
+ "invalid logging interceptor method pattern: #{pattern.inspect}"
51
+ end
52
+
53
+ name = $1
54
+ comparitor = $2
55
+ arity = ( $3 || -1 ).to_i
56
+
57
+ comparitor ||= ">" if arity < 0
58
+ comparitor ||= "="
59
+
60
+ IncludeExcludePattern.new( Regexp.new( "^" + name + "$" ),
61
+ comparitor,
62
+ arity )
63
+ end
64
+ end
65
+ private :build_map
66
+
67
+ # Returns +false+ if the given context object "matches" any of the
68
+ # exclude patterns without matching any of the include patterns.
69
+ # The context object must respond to the <tt>:sym</tt> and
70
+ # <tt>:args</tt> messages, where <tt>:sym</tt> is a symbol identifying
71
+ # the method being matched, and <tt>:args</tt> is an array of
72
+ # arguments that will be sent to that method.
73
+ def match( context )
74
+ match = true
75
+
76
+ @excludes.each do |pattern|
77
+ if match_pattern( context, pattern )
78
+ match = false
79
+ break
80
+ end
81
+ end
82
+
83
+ unless match
84
+ @includes.each do |pattern|
85
+ if match_pattern( context, pattern )
86
+ match = true
87
+ break
88
+ end
89
+ end
90
+ end
91
+
92
+ return match
93
+ end
94
+ private :match
95
+
96
+ # Returns +true+ if the given context matches the given pattern, and
97
+ # +false+ otherwise.
98
+ def match_pattern( context, pattern )
99
+ if context.sym.to_s =~ pattern.name
100
+ case pattern.comparitor
101
+ when "<"
102
+ return context.args.length < pattern.arity
103
+ when ">"
104
+ return context.args.length > pattern.arity
105
+ when "="
106
+ return context.args.length == pattern.arity
107
+ end
108
+ end
109
+
110
+ return false
111
+ end
112
+ private :match_pattern
113
+
114
+ end
115
+
116
+ end
@@ -0,0 +1,162 @@
1
+ #--
2
+ # =============================================================================
3
+ # Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
4
+ # All rights reserved.
5
+ #
6
+ # This source file is distributed as part of the Needle dependency injection
7
+ # library for Ruby. This file (and the library as a whole) may be used only as
8
+ # allowed by either the BSD license, or the Ruby license (or, by association
9
+ # with the Ruby license, the GPL). See the "doc" subdirectory of the Needle
10
+ # distribution for the texts of these licenses.
11
+ # -----------------------------------------------------------------------------
12
+ # needle website : http://needle.rubyforge.org
13
+ # project website: http://rubyforge.org/projects/needle
14
+ # =============================================================================
15
+ #++
16
+
17
+ require 'needle/errors'
18
+
19
+ module Needle
20
+
21
+ # This module encapsulates the functionality for building interceptor chains.
22
+ module InterceptorChainBuilder
23
+
24
+ # The context of a method invocation. This is used in an interceptor chain
25
+ # to encapsulate the elements of the current invocation.
26
+ # sym: the name of the method being invoked
27
+ # args: the argument list being passed to the method
28
+ # block: the reference to the block attached to the method invocation
29
+ # data: a hash that may be used by clients for storing arbitrary data in
30
+ # the context.
31
+ InvocationContext = Struct.new( :sym, :args, :block, :data )
32
+
33
+ # A single element in an interceptor chain. Each interceptor object is
34
+ # wrapped in an instance of one of these. Calling #process_next on a given
35
+ # chain element, invokes the #process method on the corresponding
36
+ # interceptor, with the next element in the chain being passed in.
37
+ class InterceptorChainElement
38
+
39
+ # Create a new InterceptorChainElement that wraps the given interceptor.
40
+ def initialize( interceptor )
41
+ @interceptor = interceptor
42
+ end
43
+
44
+ # Set the next element in the interceptor chain to the given object. This
45
+ # must be either an InterceptorChainElement instance of a
46
+ # ProxyObjectChainElement instance.
47
+ def next=( next_obj )
48
+ @next_obj = next_obj
49
+ end
50
+
51
+ # Invokes the #process method of the interceptor encapsulated by this
52
+ # object, with the _next_ element in the chain being passed to it.
53
+ def process_next( context )
54
+ if @next_obj.nil?
55
+ raise Bug,
56
+ "[BUG] interceptor chain should always terminate with proxy"
57
+ end
58
+ @interceptor.process( @next_obj, context )
59
+ end
60
+
61
+ end
62
+
63
+ # Encapsulates the end of an interceptor chain, which is the actual object
64
+ # being affected.
65
+ class ProxyObjectChainElement
66
+
67
+ # Create a new ProxyObjectChainElement that wraps the given object.
68
+ def initialize( obj )
69
+ @obj = obj
70
+ end
71
+
72
+ # Invoke the method represented by the context on the wrapped object.
73
+ def process_next( context )
74
+ @obj.__send__( context.sym, *context.args, &context.block )
75
+ end
76
+
77
+ end
78
+
79
+ # This is just a trivial proxy class that is used to wrap a service
80
+ # before the interceptors are applied to it. This additional level of
81
+ # abstraction prevents the need for mangling the names of the service's
82
+ # methods, and also offers those applications that need it the ability
83
+ # to invoke methods of the service without going through the interceptors.
84
+ #
85
+ # The proxy will be decorated with dynamically appended methods by the
86
+ # InterceptorChainBuilder#build method.
87
+ class InterceptedServiceProxy
88
+
89
+ # Create a new InterceptedServiceProxy that wraps the given interceptor
90
+ # chain.
91
+ def initialize( chain )
92
+ @chain = chain
93
+ end
94
+
95
+ end
96
+
97
+ # This will apply the given interceptors to the given service by first
98
+ # ordering the interceptors based on their relative priorities,
99
+ # and then dynamically modifying the service's methods so that the chain
100
+ # of interceptors sits in front of each of them.
101
+ #
102
+ # The modified service is returned.
103
+ def build( point, service, interceptors )
104
+ return service if interceptors.nil? || interceptors.empty?
105
+
106
+ ordered_list =
107
+ interceptors.sort { |a,b|
108
+ a.options[:priority] <=> b.options[:priority] }
109
+
110
+ chain = ProxyObjectChainElement.new( service )
111
+
112
+ ordered_list.reverse.each do |interceptor|
113
+ factory = interceptor.action.call( point.container )
114
+ instance = factory.new( point, interceptor.options )
115
+ element = InterceptorChainElement.new( instance )
116
+ element.next = chain
117
+ chain = element
118
+ end
119
+
120
+ # FIXME: should inherited methods of "Object" be interceptable?
121
+ methods_to_intercept = ( service.class.instance_methods( true ) -
122
+ Object.instance_methods +
123
+ service.class.instance_methods( false ) ).uniq
124
+
125
+ service = InterceptedServiceProxy.new( chain )
126
+ singleton = class << service; self; end
127
+
128
+ methods_to_intercept.each do |method|
129
+ next if method =~ /^__/
130
+
131
+ if singleton.instance_methods(false).include? method
132
+ singleton.send( :remove_method, method )
133
+ end
134
+
135
+ singleton.class_eval <<-EOF
136
+ def #{method}( *args, &block )
137
+ context = InvocationContext.new( :#{method}, args, block, Hash.new )
138
+ @chain.process_next( context )
139
+ end
140
+ EOF
141
+ end
142
+
143
+ # allow the interceptor to intercept methods not explicitly
144
+ # declared on the reciever.
145
+ if singleton.instance_methods(false).include? "method_missing"
146
+ singleton.send( :remove_method, :method_missing )
147
+ end
148
+
149
+ singleton.class_eval <<-EOF
150
+ def method_missing( sym, *args, &block )
151
+ context = InvocationContext.new( sym, args, block, Hash.new )
152
+ @chain.process_next( context )
153
+ end
154
+ EOF
155
+
156
+ return service
157
+ end
158
+ module_function :build
159
+
160
+ end
161
+
162
+ end