aquarium 0.1.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 (80) hide show
  1. data/CHANGES +4 -0
  2. data/EXAMPLES.rd +4 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README +250 -0
  5. data/RELEASE-PLAN +1 -0
  6. data/Rakefile +236 -0
  7. data/UPGRADE +3 -0
  8. data/examples/aspect_design_example.rb +36 -0
  9. data/examples/design_by_contract_example.rb +88 -0
  10. data/examples/method_missing_example.rb +44 -0
  11. data/examples/method_tracing_example.rb +64 -0
  12. data/lib/aquarium.rb +7 -0
  13. data/lib/aquarium/aspects.rb +6 -0
  14. data/lib/aquarium/aspects/advice.rb +189 -0
  15. data/lib/aquarium/aspects/aspect.rb +577 -0
  16. data/lib/aquarium/aspects/default_object_handler.rb +27 -0
  17. data/lib/aquarium/aspects/dsl.rb +1 -0
  18. data/lib/aquarium/aspects/dsl/aspect_dsl.rb +61 -0
  19. data/lib/aquarium/aspects/join_point.rb +158 -0
  20. data/lib/aquarium/aspects/pointcut.rb +254 -0
  21. data/lib/aquarium/aspects/pointcut_composition.rb +36 -0
  22. data/lib/aquarium/extensions.rb +5 -0
  23. data/lib/aquarium/extensions/hash.rb +85 -0
  24. data/lib/aquarium/extensions/regexp.rb +20 -0
  25. data/lib/aquarium/extensions/set.rb +49 -0
  26. data/lib/aquarium/extensions/string.rb +13 -0
  27. data/lib/aquarium/extensions/symbol.rb +22 -0
  28. data/lib/aquarium/extras.rb +4 -0
  29. data/lib/aquarium/extras/design_by_contract.rb +64 -0
  30. data/lib/aquarium/finders.rb +4 -0
  31. data/lib/aquarium/finders/finder_result.rb +121 -0
  32. data/lib/aquarium/finders/method_finder.rb +228 -0
  33. data/lib/aquarium/finders/object_finder.rb +74 -0
  34. data/lib/aquarium/finders/type_finder.rb +127 -0
  35. data/lib/aquarium/utils.rb +9 -0
  36. data/lib/aquarium/utils/array_utils.rb +29 -0
  37. data/lib/aquarium/utils/hash_utils.rb +28 -0
  38. data/lib/aquarium/utils/html_escaper.rb +17 -0
  39. data/lib/aquarium/utils/invalid_options.rb +9 -0
  40. data/lib/aquarium/utils/method_utils.rb +18 -0
  41. data/lib/aquarium/utils/nil_object.rb +13 -0
  42. data/lib/aquarium/utils/set_utils.rb +32 -0
  43. data/lib/aquarium/version.rb +30 -0
  44. data/rake_tasks/examples.rake +7 -0
  45. data/rake_tasks/examples_specdoc.rake +8 -0
  46. data/rake_tasks/examples_with_rcov.rake +8 -0
  47. data/rake_tasks/verify_rcov.rake +7 -0
  48. data/spec/aquarium/aspects/advice_chain_node_spec.rb +34 -0
  49. data/spec/aquarium/aspects/advice_spec.rb +103 -0
  50. data/spec/aquarium/aspects/aspect_invocation_spec.rb +111 -0
  51. data/spec/aquarium/aspects/aspect_spec.rb +978 -0
  52. data/spec/aquarium/aspects/aspect_with_nested_types_spec.rb +129 -0
  53. data/spec/aquarium/aspects/concurrent_aspects_spec.rb +423 -0
  54. data/spec/aquarium/aspects/concurrent_aspects_with_objects_and_types_spec.rb +103 -0
  55. data/spec/aquarium/aspects/concurrently_accessed.rb +21 -0
  56. data/spec/aquarium/aspects/dsl/aspect_dsl_spec.rb +514 -0
  57. data/spec/aquarium/aspects/join_point_spec.rb +302 -0
  58. data/spec/aquarium/aspects/pointcut_and_composition_spec.rb +131 -0
  59. data/spec/aquarium/aspects/pointcut_or_composition_spec.rb +111 -0
  60. data/spec/aquarium/aspects/pointcut_spec.rb +800 -0
  61. data/spec/aquarium/extensions/hash_spec.rb +187 -0
  62. data/spec/aquarium/extensions/regex_spec.rb +40 -0
  63. data/spec/aquarium/extensions/set_spec.rb +105 -0
  64. data/spec/aquarium/extensions/string_spec.rb +25 -0
  65. data/spec/aquarium/extensions/symbol_spec.rb +37 -0
  66. data/spec/aquarium/extras/design_by_contract_spec.rb +68 -0
  67. data/spec/aquarium/finders/finder_result_spec.rb +359 -0
  68. data/spec/aquarium/finders/method_finder_spec.rb +878 -0
  69. data/spec/aquarium/finders/method_sorting_spec.rb +16 -0
  70. data/spec/aquarium/finders/object_finder_spec.rb +230 -0
  71. data/spec/aquarium/finders/type_finder_spec.rb +210 -0
  72. data/spec/aquarium/spec_example_classes.rb +117 -0
  73. data/spec/aquarium/spec_helper.rb +3 -0
  74. data/spec/aquarium/utils/array_utils_spec.rb +47 -0
  75. data/spec/aquarium/utils/hash_utils_spec.rb +48 -0
  76. data/spec/aquarium/utils/html_escaper_spec.rb +18 -0
  77. data/spec/aquarium/utils/method_utils_spec.rb +50 -0
  78. data/spec/aquarium/utils/nil_object_spec.rb +19 -0
  79. data/spec/aquarium/utils/set_utils_spec.rb +60 -0
  80. metadata +132 -0
@@ -0,0 +1,577 @@
1
+ require 'aquarium/extensions'
2
+ require 'aquarium/utils'
3
+ require 'aquarium/aspects/advice'
4
+ require 'aquarium/aspects/join_point'
5
+ require 'aquarium/aspects/pointcut'
6
+ require 'aquarium/aspects/pointcut_composition'
7
+ require 'aquarium/aspects/default_object_handler'
8
+
9
+ module Aquarium
10
+ module Aspects
11
+
12
+ # == Aspect
13
+ # Aspects "advise" one or more method invocations for one or more types or objects
14
+ # (including class methods on types). The corresponding advice is a Proc that is
15
+ # invoked either before the join point, after it returns, after it raises an exception,
16
+ # after either event, or around the join point, meaning the advice runs and it decides
17
+ # when and if to invoke the advised method. (Hence, around advice can run code before
18
+ # and after the join point call and it can "veto" the actual join point call).
19
+ #
20
+ # See also Aquarium::Aspects::DSL::AspectDsl for more information.
21
+ class Aspect
22
+ include DefaultObjectHandler
23
+ include Aquarium::Utils::ArrayUtils
24
+ include Aquarium::Utils::HashUtils
25
+ include Aquarium::Utils::HtmlEscaper
26
+
27
+ attr_accessor :verbose, :log
28
+ attr_reader :specification, :pointcuts, :advice
29
+
30
+ # Aspect.new (:around | :before | :after | :after_returning | :after_raising ) \
31
+ # (:pointcuts => [...]), | \
32
+ # ((:types => [...] | :objects => [...]),
33
+ # :methods => [], :method_options => [...], \
34
+ # :attributes => [...], :attribute_options[...]), \
35
+ # (:advice = advice | do |join_point, *args| ...; end)
36
+ #
37
+ # where the parameters often have many synonyms (mostly to support a "humane
38
+ # interface") and they are interpreted as followed:
39
+ #
40
+ # <tt>:around</tt>::
41
+ # Invoke the specified advice "around" the join points. It is up to the advice
42
+ # itself to call <tt>join_point.proceed</tt> (where <tt>join_point</tt> is the
43
+ # first argument passed to the advice) if it wants the advised method to actually
44
+ # execute.
45
+ #
46
+ # <tt>:before</tt>::
47
+ # Invoke the specified advice as before the join point.
48
+ #
49
+ # <tt>:after</tt>::
50
+ # Invoke the specified advice as after the join point either returns successfully
51
+ # or raises an exception.
52
+ #
53
+ # <tt>:after_returning</tt>::
54
+ # Invoke the specified advice as after the join point returns successfully.
55
+ #
56
+ # <tt>:after_raising</tt>::
57
+ # Invoke the specified advice as after the join point raises an exception.
58
+ #
59
+ # <tt>:pointcuts => pointcut || [pointcut_list]</tt>::
60
+ # <tt>:pointcut => pointcut || [pointcut_list]</tt>::
61
+ # <tt>:within_pointcut => pointcut || [pointcut_list]</tt>::
62
+ # <tt>:within_pointcuts => pointcut || [pointcut_list]</tt>::
63
+ # One or an array of Pointcut objects. Mutually-exclusive with the :types, :objects,
64
+ # :methods, :attributes, :method_options, and :attribute_options parameters.
65
+ #
66
+ # <tt>:types => type || [type_list]</tt>::
67
+ # <tt>:type => type || [type_list]</tt>::
68
+ # <tt>:within_type => type || [type_list]</tt>::
69
+ # <tt>:within_types => type || [type_list]</tt>::
70
+ # One or an array of types, type names and/or type regular expessions to advise.
71
+ # All the :types, :objects, :methods, :attributes, :method_options, and :attribute_options
72
+ # are used to construct Pointcuts internally.
73
+ #
74
+ # <tt>:objects => object || [object_list]</tt>::
75
+ # <tt>:object => object || [object_list]</tt>::
76
+ # <tt>:within_object => object || [object_list]</tt>::
77
+ # <tt>:within_objects => object || [object_list]</tt>::
78
+ # One or an array of objects to advise.
79
+ #
80
+ # <tt>:default_object => object</tt>::
81
+ # An "internal" flag used by the methods that AspectDSL adds to Object. When no object
82
+ # or type is specified, the value of :default_object will be used, if defined. The
83
+ # AspectDSL methods set the value to self, so that the user doesn't have to in the
84
+ # appropriate contexts. This flag is subject to change, so don't use it explicitly!
85
+ #
86
+ # <tt>:methods => method || [method_list]</tt>::
87
+ # <tt>:method => method || [method_list]</tt>::
88
+ # <tt>:within_method => method || [method_list]</tt>::
89
+ # <tt>:within_methods => method || [method_list]</tt>::
90
+ # One or an array of methods, method names and/or method regular expessions to match.
91
+ # By default, unless :attributes are specified, searches for public instance methods
92
+ # with the method option :suppress_ancestor_methods implied, unless explicit method
93
+ # options are given.
94
+ #
95
+ # <tt>:method_options => [options]</tt>::
96
+ # One or more options supported by Aquarium::Finders::MethodFinder. Defaults to :suppress_ancestor_methods
97
+ #
98
+ # <tt>:attributes => attribute || [attribute_list]</tt>::
99
+ # <tt>:attribute => attribute || [attribute_list]</tt>::
100
+ # <tt>:within_attribute => attribute || [attribute_list]</tt>::
101
+ # <tt>:within_attributes => attribute || [attribute_list]</tt>::
102
+ # One or an array of attribute names and/or regular expessions to match.
103
+ # This is syntactic sugar for the corresponding attribute readers and/or writers
104
+ # methods, as specified using the <tt>:attrbute_options. Any matches will be
105
+ # joined with the matched :methods.</tt>.
106
+ #
107
+ # <tt>:attribute_options => [options]</tt>::
108
+ # One or more of <tt>:readers</tt>, <tt>:reader</tt> (synonymous),
109
+ # <tt>:writers</tt>, and/or <tt>:writer</tt> (synonymous). By default, both
110
+ # readers and writers are matched.
111
+ def initialize *options, &block
112
+ process_input *options, &block
113
+ init_pointcuts
114
+ return if specification[:noop]
115
+ advise_join_points
116
+ end
117
+
118
+ def join_points_matched
119
+ matched_jps = Set.new
120
+ @pointcuts.each do |pointcut|
121
+ matched_jps = matched_jps.union pointcut.join_points_matched
122
+ end
123
+ matched_jps
124
+ end
125
+
126
+ def unadvise
127
+ return if @specification[:noop]
128
+ @pointcuts.each do |pointcut|
129
+ interesting_join_points(pointcut).each do |join_point|
130
+ remove_advice_for_aspect_at join_point
131
+ end
132
+ end
133
+ end
134
+
135
+ alias :unadvise_join_points :unadvise
136
+
137
+ def inspect
138
+ "Aspect: {specification: #{specification.inspect}, pointcuts: #{pointcuts.inspect}, advice: #{advice.inspect}}"
139
+ end
140
+
141
+ alias :to_s :inspect
142
+
143
+ # We have to ignore advice in the comparison. As recently discussed in ruby-users, there are very few situations.
144
+ # where Proc#eql? returns true.
145
+ def eql? other
146
+ self.object_id == other.object_id ||
147
+ (self.class.eql?(other.class) && specification == other.specification && pointcuts == other.pointcuts)
148
+ end
149
+
150
+ alias :== :eql?
151
+
152
+ protected
153
+
154
+ def process_input *options, &block
155
+ @original_options = *options
156
+ make_specification *options, &block
157
+ @verbose = @specification[:verbose] || false
158
+ @log = @specification[:log] || ""
159
+ validate_specification
160
+ end
161
+
162
+ def make_specification *options, &block
163
+ opts = options.dup
164
+ rationalize_parameters opts
165
+ @specification = Aquarium::Utils::MethodUtils.method_args_to_hash(*opts) {|option| ""} # set other hash values to an empty string
166
+ use_default_object_if_defined unless (types_given? || objects_given? || pointcuts_given?)
167
+ use_first_nonadvice_symbol_as_method(opts) unless methods_given?
168
+ @advice = block || @specification[:advice]
169
+ if @advice.nil? && @specification[:noop].nil?
170
+ raise Aquarium::Utils::InvalidOptions.new("No advice block or argument specified.")
171
+ end
172
+ end
173
+
174
+ def init_pointcuts
175
+ pointcuts = []
176
+ if pointcuts_given?
177
+ pointcuts_given.each do |pointcut|
178
+ if pointcut.kind_of?(Aquarium::Aspects::Pointcut)
179
+ pointcuts << pointcut
180
+ else
181
+ pointcuts << Aquarium::Aspects::Pointcut.new(pointcut)
182
+ end
183
+ end
184
+ else
185
+ pc_options = {}
186
+ pc_options[:types] = types_given.to_a if types_given?
187
+ pc_options[:objects] = objects_given.to_a if objects_given?
188
+ pc_options[:methods] = methods_given.to_a if methods_given?
189
+ pc_options[:method_options] = method_options_given.to_a if method_options_given?
190
+ pointcuts << Aquarium::Aspects::Pointcut.new(pc_options)
191
+ end
192
+ @pointcuts = Set.new(pointcuts)
193
+ end
194
+
195
+ def advise_join_points
196
+ advice = @advice.to_proc
197
+ @pointcuts.each do |pointcut|
198
+ interesting_join_points(pointcut).each do |join_point|
199
+ type_or_object = Aspect.type_or_object_string join_point.type_or_object
200
+ add_advice_framework join_point
201
+ Aquarium::Aspects::Advice.sort_by_priority_order(specified_advice_kinds).reverse.each do |advice_kind|
202
+ advice_chain = Aspect.get_advice_chain join_point.type_or_object, join_point.method_name
203
+ add_advice_to_chain join_point, advice_chain, advice_kind, advice
204
+ end
205
+ end
206
+ end
207
+ end
208
+
209
+ # Ignore any inserted methods that are part of the aspect implementation,
210
+ # i.e., those that match the Aspect..aspect_method_prefix.
211
+ def interesting_join_points pointcut
212
+ pointcut.join_points_matched.reject do |join_point|
213
+ join_point.method_name.to_s =~ /^#{Aspect.aspect_method_prefix}/
214
+ end
215
+ end
216
+
217
+ def add_advice_to_chain join_point, advice_chain, advice_kind, advice
218
+ options = @specification.merge({
219
+ :aspect => self,
220
+ :advice_kind => advice_kind,
221
+ :advice => advice,
222
+ :next_node => advice_chain,
223
+ :static_join_point => join_point})
224
+ # The returned node becomes the lead node in the chain.
225
+ advice_chain = Aspect.get_advice_chain join_point.type_or_object, join_point.method_name
226
+ Aspect.set_advice_chain(join_point.type_or_object, join_point.method_name, Aquarium::Aspects::AdviceChainNodeFactory.make_node(options))
227
+ advice_chain = Aspect.get_advice_chain join_point.type_or_object, join_point.method_name
228
+ end
229
+
230
+ # Useful for debugging...
231
+ def self.advice_chain_inspect advice_chain
232
+ return "[nil]" if advice_chain.nil?
233
+ "<br/>"+advice_chain.inspect do |advice_chain|
234
+ "#{advice_chain.class.name}:#{advice_chain.object_id}: aspect = #{advice_chain.aspect.object_id}, next_node = #{advice_chain_inspect advice_chain.next_node}"
235
+ end.gsub(/\</,"&lt;").gsub(/\>/,"&gt;")+"<br/>"
236
+ end
237
+
238
+ def add_advice_framework join_point
239
+ type_to_advise = join_point.type || (class << join_point.object; self; end)
240
+ alias_method_name = (saved_method_name join_point).intern
241
+ return if private_method_defined? join_point.type_or_object, alias_method_name
242
+ type_to_advise.class_eval(<<-EVAL_WRAPPER, __FILE__, __LINE__)
243
+ #{static_method_prefix join_point.is_instance_method?}
244
+ #{alias_original_method_text alias_method_name, join_point}
245
+ #{static_method_suffix join_point.is_instance_method?}
246
+ EVAL_WRAPPER
247
+ Aspect.set_advice_chain join_point.type_or_object, join_point.method_name, Aquarium::Aspects::AdviceChainNodeFactory.make_node(
248
+ :aspect => nil, # Belongs to all aspects that might advise this join point!
249
+ :advice_kind => :none,
250
+ :alias_method_name => alias_method_name,
251
+ :static_join_point => join_point)
252
+ advice_chain = Aspect.get_advice_chain join_point.type_or_object, join_point.method_name
253
+ end
254
+
255
+ def static_method_prefix is_instance_method
256
+ return "" if is_instance_method
257
+ <<-EOF
258
+ class << self
259
+ EOF
260
+ end
261
+
262
+ def static_method_suffix is_instance_method
263
+ return "" if is_instance_method
264
+ <<-EOF
265
+ end
266
+ EOF
267
+ end
268
+
269
+ # Note that we make the alias for the original method private, so it doesn't pollute the "interface"
270
+ # of the advised classes. This also means that we have to use the Class.method(name).call()
271
+ # idiom when invoking it.
272
+ def alias_original_method_text alias_method_name, join_point
273
+ self_name = join_point.type.nil? ? "self" : join_point.type.name
274
+ target_self = join_point.is_instance_method? ? "self" : join_point.type.name
275
+ <<-EOF
276
+ alias_method :#{alias_method_name}, :#{join_point.method_name}
277
+ def #{join_point.method_name} *args, &block_for_method
278
+ advice_chain = Aspect.get_advice_chain #{self_name}, :#{join_point.method_name}
279
+ static_join_point = advice_chain.static_join_point
280
+ advice_join_point = Aspect.make_advice_join_point static_join_point, #{target_self}, args, block_for_method
281
+ advice_chain.call advice_join_point, *args
282
+ end
283
+ private :#{alias_method_name}
284
+ EOF
285
+ end
286
+
287
+ def unalias_original_method_text alias_method_name, join_point
288
+ self_name = join_point.type.nil? ? "self" : join_point.type.name
289
+ <<-EOF
290
+ alias_method :#{join_point.method_name}, :#{alias_method_name}
291
+ public :#{join_point.method_name}
292
+ undef_method :#{alias_method_name}
293
+ EOF
294
+ end
295
+
296
+ def remove_advice_chain_class_variable_text alias_method_name, join_point
297
+ self_name = join_point.type.nil? ? "self" : join_point.type.name
298
+ <<-EOF
299
+ advice_chain_name = :@@#{Aspect.advice_chain_attr_name join_point.type_or_object, join_point.method_name}
300
+ remove_class_variable advice_chain_name
301
+ EOF
302
+ end
303
+
304
+ def self.make_advice_join_point static_join_point, object, method_parameters, block_for_method
305
+ static_join_point.make_current_context_join_point(
306
+ :advice_kind => :before,
307
+ :advised_object => object,
308
+ :parameters => method_parameters,
309
+ :block_for_method => block_for_method)
310
+ end
311
+
312
+ def remove_advice_for_aspect_at join_point
313
+ advice_chain = Aspect.get_advice_chain join_point.type_or_object, join_point.method_name
314
+ advice_chain = prune_nodes_in advice_chain
315
+ if advice_chain.empty?
316
+ remove_advice_framework_for join_point
317
+ else
318
+ Aspect.set_advice_chain join_point.type_or_object, join_point.method_name, advice_chain
319
+ end
320
+ end
321
+
322
+ def prune_nodes_in advice_chain
323
+ # Use equal? for the aspects to compare object id only,
324
+ while advice_chain.empty? == false && advice_chain.aspect.equal?(self)
325
+ advice_chain = advice_chain.next_node
326
+ end
327
+ keeper_node = node = advice_chain
328
+ while node.empty? == false
329
+ while node.next_node.aspect.equal?(self)
330
+ node.next_node = node.next_node.next_node
331
+ end
332
+ node = node.next_node
333
+ end
334
+ advice_chain
335
+ end
336
+
337
+ def remove_advice_framework_for join_point
338
+ if Aspect.is_type?(join_point.type_or_object)
339
+ restore_type_method join_point
340
+ else
341
+ restore_object_method join_point
342
+ end
343
+ end
344
+
345
+ def restore_type_method join_point
346
+ alias_method_name = (saved_method_name join_point).intern
347
+ join_point.type.class_eval(<<-EVAL_WRAPPER, __FILE__, __LINE__)
348
+ #{static_method_prefix join_point.is_instance_method?}
349
+ #{unalias_original_method_text alias_method_name, join_point}
350
+ #{static_method_suffix join_point.is_instance_method?}
351
+ #{remove_advice_chain_class_variable_text alias_method_name, join_point}
352
+ EVAL_WRAPPER
353
+ end
354
+
355
+ def do_restore_type_method join_point
356
+ end
357
+
358
+ def restore_object_method join_point
359
+ saved = saved_method_name join_point
360
+ singleton = class << join_point.object; self; end
361
+ singleton.class_eval do
362
+ alias_method join_point.method_name, saved
363
+ public join_point.method_name
364
+ undef_method saved.intern
365
+ end
366
+ advice_chain_name = "@#{Aspect.advice_chain_attr_name join_point.type_or_object, join_point.method_name}".intern
367
+ join_point.object.method(:remove_instance_variable).call advice_chain_name
368
+ end
369
+
370
+ def self.set_advice_chain type_or_object, method_name, advice_chain
371
+ self.is_type?(type_or_object) ?
372
+ self.set_type_advice_chain(type_or_object, method_name, advice_chain) :
373
+ self.set_object_advice_chain(type_or_object, method_name, advice_chain)
374
+ end
375
+
376
+ def self.get_advice_chain type_or_object, method_name
377
+ self.is_type?(type_or_object) ?
378
+ self.get_type_advice_chain(type_or_object, method_name) :
379
+ self.get_object_advice_chain(type_or_object, method_name)
380
+ end
381
+
382
+ def self.set_type_advice_chain type_or_object, method_name, advice_chain
383
+ chain_class_var = ("@@" + self.advice_chain_attr_name(type_or_object, method_name)).intern
384
+ type_or_object.class_eval do
385
+ class_variable_set chain_class_var, advice_chain
386
+ end
387
+ end
388
+
389
+ def self.set_object_advice_chain type_or_object, method_name, advice_chain
390
+ chain_class_var = ("@" + self.advice_chain_attr_name(type_or_object, method_name)).intern
391
+ type_or_object.instance_eval do
392
+ instance_variable_set chain_class_var, advice_chain
393
+ end
394
+ end
395
+
396
+
397
+ def self.get_type_advice_chain type_or_object, method_name
398
+ chain_class_var = ("@@" + self.advice_chain_attr_name(type_or_object, method_name)).intern
399
+ type_or_object.class_eval do
400
+ class_variable_get chain_class_var
401
+ end
402
+ end
403
+
404
+ def self.get_object_advice_chain type_or_object, method_name
405
+ chain_class_var = ("@" + self.advice_chain_attr_name(type_or_object, method_name)).intern
406
+ type_or_object.instance_eval do
407
+ instance_variable_get chain_class_var
408
+ end
409
+ end
410
+
411
+ def private_method_defined? type_or_object, method_name
412
+ if Aspect.is_type? type_or_object
413
+ type_or_object.private_instance_methods.include? method_name.to_s
414
+ else
415
+ type_or_object.private_methods.include? method_name.to_s
416
+ end
417
+ end
418
+
419
+ def self.advice_chain_attr_name type_or_object, method_name
420
+ type_or_object_key = self.make_type_or_object_key(type_or_object)
421
+ class_or_object_prefix = is_type?(type_or_object) ? "class_" : ""
422
+ valid_name = self.make_valid_attr_name_from_method_name method_name
423
+ "#{self.aspect_method_prefix}#{class_or_object_prefix}advice_chain_#{type_or_object_key}_#{valid_name}"
424
+ end
425
+
426
+ def self.aspect_method_prefix
427
+ "_aspect_"
428
+ end
429
+
430
+ def saved_method_name join_point
431
+ to_key = Aspect.make_type_or_object_key(join_point.type_or_object)
432
+ "#{Aspect.aspect_method_prefix}saved_#{to_key}_#{join_point.method_name}"
433
+ end
434
+
435
+ def self.make_type_or_object_key type_or_object
436
+ if is_type?(type_or_object)
437
+ make_valid_type_name type_or_object.name
438
+ else
439
+ "#{make_valid_type_name(type_or_object.class.name)}_#{type_or_object.object_id}"
440
+ end
441
+ end
442
+
443
+ def self.make_valid_type_name type_name
444
+ type_name.gsub(/:/, '_')
445
+ end
446
+
447
+ def self.make_valid_attr_name_from_method_name method_name
448
+ method_name.to_s.gsub("=","equalsign").gsub("?", "questionmark").gsub("!", "exclamation").gsub("~", "tilde").intern
449
+ end
450
+
451
+ def specified_advice_kinds
452
+ @specification.keys.select do |key|
453
+ Aquarium::Aspects::Advice.kinds.include? key
454
+ end
455
+ end
456
+
457
+ def rationalize_parameters opts
458
+ return unless opts.last.kind_of?(Hash)
459
+ option_synonyms = {
460
+ :type => :types,
461
+ :within_type => :types,
462
+ :within_types => :types,
463
+ :object => :objects,
464
+ :within_object => :objects,
465
+ :within_objects => :objects,
466
+ :method => :methods,
467
+ :within_method => :methods,
468
+ :within_methods => :methods,
469
+ :pointcut => :pointcuts,
470
+ :within_pointcut => :pointcuts,
471
+ :within_pointcuts => :pointcuts
472
+ }
473
+ hash = opts.pop.dup
474
+ opts.push hash
475
+ option_synonyms.each do |syn, actual|
476
+ if hash.has_key? syn
477
+ hash[actual] = make_array(hash[actual], hash[syn])
478
+ hash.delete syn
479
+ end
480
+ end
481
+ # Only one advice argument allowed.
482
+ unless hash.has_key?(:advice)
483
+ advice_synonyms = {
484
+ :call => :advice,
485
+ :invoke => :advice,
486
+ :advise_with => :advice
487
+ }
488
+ advice_synonyms.each do |syn, actual|
489
+ if hash.has_key? syn
490
+ hash[actual] = hash[syn]
491
+ hash.delete syn
492
+ end
493
+ end
494
+ end
495
+ [:types, :objects, :methods, :method_options, :pointcuts, :default_object].each do |opt|
496
+ hash[opt] = Set.new(make_array(hash[opt]))
497
+ end
498
+ opts
499
+ end
500
+
501
+ def validate_specification
502
+ bad_options("One of #{Aquarium::Aspects::Advice.kinds.inspect} is required.") unless advice_kinds_given?
503
+ bad_options(":around can't be used with :before.") if around_given_with? :before
504
+ bad_options(":around can't be used with :after.") if around_given_with? :after
505
+ bad_options(":around can't be used with :after_returning.") if around_given_with? :after_returning
506
+ bad_options(":around can't be used with :after_raising.") if around_given_with? :after_raising
507
+ bad_options(":after can't be used with :after_returning.") if after_given_with? :after_returning
508
+ bad_options(":after can't be used with :after_raising.") if after_given_with? :after_raising
509
+ bad_options(":after_returning can't be used with :after_raising.") if after_returning_given_with? :after_raising
510
+ unless pointcuts_given? or types_given? or objects_given? #or objects_given_excluding_default?
511
+ bad_options("At least one of :pointcut(s), :type(s), :object(s) is required.")
512
+ end
513
+ if pointcuts_given? and (types_given? or objects_given?)
514
+ bad_options("Can't specify both :pointcut(s) and one or more of :type(s), and/or :object(s).")
515
+ end
516
+ # unless methods_given? or pointcuts_given? or types_given? #or objects_given_excluding_default?
517
+ # specification_too_short
518
+ # end
519
+ end
520
+
521
+ def advice_kinds_given
522
+ Aquarium::Aspects::Advice.kinds.inject([]) {|ary, kind| ary << @specification[kind] if @specification[kind]; ary}
523
+ end
524
+
525
+ def advice_kinds_given?
526
+ not advice_kinds_given.empty?
527
+ end
528
+
529
+ def around_given_with? other_advice_kind_sym
530
+ @specification[:around] and @specification[other_advice_kind_sym]
531
+ end
532
+
533
+ def after_given_with? other_advice_kind_sym
534
+ @specification[:after] and @specification[other_advice_kind_sym]
535
+ end
536
+ def after_returning_given_with? other_advice_kind_sym
537
+ @specification[:after_returning] and @specification[other_advice_kind_sym]
538
+ end
539
+
540
+ %w[pointcuts types objects methods attributes method_options attribute_options].each do |name|
541
+ class_eval(<<-EOF, __FILE__, __LINE__)
542
+ def #{name}_given
543
+ @specification[:#{name}]
544
+ end
545
+
546
+ def #{name}_given?
547
+ not (#{name}_given.nil? or #{name}_given.empty?)
548
+ end
549
+ EOF
550
+ end
551
+
552
+ def use_first_nonadvice_symbol_as_method options
553
+ 2.times do |i|
554
+ if options.size >= i+1
555
+ sym = options[i]
556
+ if sym.kind_of?(Symbol) && !Aquarium::Aspects::Advice::kinds.include?(sym)
557
+ @specification[:methods] = Set.new([sym])
558
+ return
559
+ end
560
+ end
561
+ end
562
+ end
563
+
564
+ def self.is_type? type_or_object
565
+ type_or_object.kind_of?(Class) or type_or_object.kind_of?(Module)
566
+ end
567
+
568
+ def self.type_or_object_string type_or_object
569
+ is_type?(type_or_object) ? "type" : "object"
570
+ end
571
+
572
+ def bad_options message
573
+ raise Aquarium::Utils::InvalidOptions.new("Invalid options given. " + message + " (options: #{@original_options.inspect})")
574
+ end
575
+ end
576
+ end
577
+ end