aquarium 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES CHANGED
@@ -1,3 +1,23 @@
1
+ == Version 0.1.8
2
+
3
+ V0.1.7 did not successfully "register" at rubyforge. This releases fixes that problem and also adds
4
+ several feature enhancements and refactorings. There are no known upgrade issues.
5
+
6
+ Bug fixes:
7
+ none
8
+
9
+ Enhancements:
10
+ 13399 Add :exclusion options for methods and types.
11
+ 14707 :exclude_ancestor_methods as synonym for :suppress_ancestor_methods
12
+
13
+ 13399 adds new :exclude_(pointcuts|join_points|types|objects|methods|attributes) options for
14
+ Aspect.new, and Pointcut.new that make it easier to specify a list or regular expression for
15
+ various "items" and then to exclude particular items, e.g.,
16
+ Aspect.new :around, :types => /nterestingType/, :exclude_types => UninterestingType ...
17
+
18
+ The :exclude_ancestor_methods option is now preferred over :suppress_ancestor_methods,
19
+ since the former is more consistent with the new :exclude_* options.
20
+
1
21
  == Version 0.1.7
2
22
 
3
23
  Bug fixes:
data/README CHANGED
@@ -194,6 +194,23 @@ You can specify a "Pointcut" that encapsulates one or more pre-defined Pointcuts
194
194
  around :pointcuts = [jp, ...], ... # for pre-defined join point list
195
195
  around :pointcut = {:type => T, :method => :m}, ... # same as around :type => T, :method => :m, ..
196
196
 
197
+ With release V0.1.8, you can specifically exclude particular pointcuts, join points, types, objects, methods, or attributes. This is useful when you specify a list or regular expression of "items" to match and you want to exclude some of the items.
198
+ Note that there is an open bug (#15202) that appears to affect advising types, unadvising the types, then advising objects
199
+ of the same types. (This is not likely to happen a lot in real applications, but it shows up when running specs.)
200
+
201
+ around ..., :exclude_pointcut = pc, ...
202
+ around ..., :exclude_pointcuts = [pc, ...]
203
+ around ..., :exclude_join_point = jp, ...
204
+ around ..., :exclude_join_points = [jp, ...]
205
+ around ..., :exclude_type = t, ...
206
+ around ..., :exclude_types = [t, ...]
207
+ around ..., :exclude_object = o, ...
208
+ around ..., :exclude_objects = [o, ...]
209
+ around ..., :exclude_method = m, ...
210
+ around ..., :exclude_methods = [m, ...]
211
+ around ..., :exclude_attribute = a, ...
212
+ around ..., :exclude_attributes = [a, ...]
213
+
197
214
  You can advice methods before execution:
198
215
 
199
216
  before :types => ...
data/UPGRADE CHANGED
@@ -1,3 +1,8 @@
1
+ == Upgrading to Aquarium-0.1.8
2
+
3
+ V0.1.7 did not successfully "register" at rubyforge. This releases fixes that problem and also adds
4
+ several feature enhancements and refactorings. There are no known upgrade issues.
5
+
1
6
  == Upgrading to Aquarium-0.1.7
2
7
 
3
8
  This is primarily a bug-fix release, so there should be no upgrading or incompatibility issues.
@@ -7,15 +7,14 @@ require 'aquarium/utils/nil_object'
7
7
  module Aquarium
8
8
  module Aspects
9
9
  module Advice
10
- def self.kinds_in_priority_order
11
- [:around, :before, :after, :after_returning, :after_raising]
12
- end
13
-
14
- def self.kinds; self.kinds_in_priority_order; end
10
+
11
+ KINDS_IN_PRIORITY_ORDER = [:around, :before, :after, :after_returning, :after_raising]
12
+
13
+ def self.kinds; KINDS_IN_PRIORITY_ORDER; end
15
14
 
16
15
  def self.sort_by_priority_order advice_kinds
17
16
  advice_kinds.sort do |x,y|
18
- self.kinds_in_priority_order.index(x.to_sym) <=> self.kinds_in_priority_order.index(y.to_sym)
17
+ KINDS_IN_PRIORITY_ORDER.index(x.to_sym) <=> KINDS_IN_PRIORITY_ORDER.index(y.to_sym)
19
18
  end.map {|x| x.to_sym}
20
19
  end
21
20
  end
@@ -28,10 +27,12 @@ module Aquarium
28
27
  include Enumerable
29
28
  def initialize options = {}, &proc_block
30
29
  @proc = Proc.new &proc_block
31
- options[:next_node] ||= nil # assign so the attribute is always created
30
+ # assign :next_node and :static_join_point so the attributes are always created
31
+ options[:next_node] ||= nil
32
+ options[:static_join_point] ||= nil
32
33
  options.each do |key, value|
33
34
  instance_variable_set "@#{key}".intern, value
34
- (class << self; self; end).class_eval <<-EOF
35
+ (class << self; self; end).class_eval(<<-EOF, __FILE__, __LINE__)
35
36
  attr_accessor(:#{key})
36
37
  EOF
37
38
  end
@@ -44,7 +45,7 @@ module Aquarium
44
45
  class_or_instance_method_separater = jp.instance_method? ? "#" : "."
45
46
  context_message = "Exception raised while executing \"#{jp.context.advice_kind}\" advice for \"#{jp.type_or_object.inspect}#{class_or_instance_method_separater}#{jp.method_name}\": "
46
47
  backtrace = e.backtrace
47
- e2 = e.exception(context_message + e.message)
48
+ e2 = e.exception(context_message + e.message + " (join_point = #{jp.inspect})")
48
49
  e2.set_backtrace backtrace
49
50
  raise e2
50
51
  end
@@ -1,6 +1,7 @@
1
1
  require 'aquarium/extensions'
2
2
  require 'aquarium/utils'
3
3
  require 'aquarium/aspects/advice'
4
+ require 'aquarium/aspects/exclusion_handler'
4
5
  require 'aquarium/aspects/join_point'
5
6
  require 'aquarium/aspects/pointcut'
6
7
  require 'aquarium/aspects/pointcut_composition'
@@ -19,6 +20,8 @@ module Aquarium
19
20
  #
20
21
  # See also Aquarium::Aspects::DSL::AspectDsl for more information.
21
22
  class Aspect
23
+ include Advice
24
+ include ExclusionHandler
22
25
  include DefaultObjectHandler
23
26
  include Aquarium::Utils::ArrayUtils
24
27
  include Aquarium::Utils::HashUtils
@@ -27,6 +30,41 @@ module Aquarium
27
30
  attr_accessor :verbose, :log
28
31
  attr_reader :specification, :pointcuts, :advice
29
32
 
33
+ ALLOWED_OPTIONS_SINGULAR = %w[advice type object method attribute method_option attribute_option pointcut default_object
34
+ exclude_type exclude_object exclude_pointcut exclude_join_point exclude_method exclude_attribute noop].map {|o| o.intern}
35
+
36
+ ALLOWED_OPTIONS_PLURAL = ALLOWED_OPTIONS_SINGULAR.map {|o| "#{o}s".intern}
37
+
38
+ ALLOWED_OPTIONS = ALLOWED_OPTIONS_SINGULAR + ALLOWED_OPTIONS_PLURAL
39
+
40
+ ADVICE_OPTIONS_SYNONYMS_MAP = {
41
+ :call => :advice,
42
+ :invoke => :advice,
43
+ :advise_with => :advice
44
+ }
45
+
46
+ ALLOWED_OPTIONS_SYNONYMS_MAP = {
47
+ :type => :types,
48
+ :within_type => :types,
49
+ :within_types => :types,
50
+ :object => :objects,
51
+ :within_object => :objects,
52
+ :within_objects => :objects,
53
+ :method => :methods,
54
+ :within_method => :methods,
55
+ :within_methods => :methods,
56
+ :attribute => :attributes,
57
+ :pointcut => :pointcuts,
58
+ :within_pointcut => :pointcuts,
59
+ :within_pointcuts => :pointcuts,
60
+ :exclude_type => :exclude_types,
61
+ :exclude_object => :exclude_objects,
62
+ :exclude_pointcut => :exclude_pointcuts,
63
+ :exclude_join_point => :exclude_join_points,
64
+ :exclude_method => :exclude_methods,
65
+ :exclude_attribute => :exclude_attributes,
66
+ }
67
+
30
68
  # Aspect.new (:around | :before | :after | :after_returning | :after_raising ) \
31
69
  # (:pointcuts => [...]), | \
32
70
  # ((:types => [...] | :objects => [...]),
@@ -77,11 +115,13 @@ module Aquarium
77
115
  # <tt>:within_objects => object || [object_list]</tt>::
78
116
  # One or an array of objects to advise.
79
117
  #
80
- # <tt>:default_object => object</tt>::
118
+ # <tt>:default_objects => object || [object_list]</tt>::
119
+ # <tt>:default_object => object || [object_list]</tt>::
81
120
  # 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
121
+ # or type is specified, the value of :default_objects will be used, if defined. The
83
122
  # 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!
123
+ # appropriate contexts, for convenience. This flag is subject to change, so don't
124
+ # use it explicitly!
85
125
  #
86
126
  # <tt>:methods => method || [method_list]</tt>::
87
127
  # <tt>:method => method || [method_list]</tt>::
@@ -109,7 +149,21 @@ module Aquarium
109
149
  # <tt>:writers</tt>, and/or <tt>:writer</tt> (synonymous). By default, both
110
150
  # readers and writers are matched.
111
151
  #
112
- # See also the project README for extensive examples of these options.
152
+ # <tt>:exclude_pointcuts => pc || [pc_list]</tt>::
153
+ # <tt>:exclude_pointcut => pc || [pc_list]</tt>::
154
+ # <tt>:exclude_join_points => jp || [jp_list]</tt>::
155
+ # <tt>:exclude_join_point => jp || [jp_list]</tt>::
156
+ # <tt>:exclude_types => type || [type_list]</tt>::
157
+ # <tt>:exclude_types => type || [type_list]</tt>::
158
+ # <tt>:exclude_type => type || [type_list]</tt>::
159
+ # <tt>:exclude_objects => object || [object_list]</tt>::
160
+ # <tt>:exclude_object => object || [object_list]</tt>::
161
+ # <tt>:exclude_methods => method || [method_list]</tt>::
162
+ # <tt>:exclude_method => method || [method_list]</tt>::
163
+ # <tt>:exclude_attributes => attribute || [attribute_list]</tt>::
164
+ # <tt>:exclude_attribute => attribute || [attribute_list]</tt>::
165
+ # Exclude the specified "things" from the matched join points.
166
+ #
113
167
  def initialize *options, &block
114
168
  process_input options, &block
115
169
  init_pointcuts
@@ -154,25 +208,30 @@ module Aquarium
154
208
  protected
155
209
 
156
210
  def process_input options, &block
157
- @original_options = options
211
+ @original_options = options.flatten
158
212
  make_specification options, &block
159
213
  @verbose = @specification[:verbose] || false
160
214
  @log = @specification[:log] || ""
161
- validate_specification
162
215
  end
163
216
 
164
217
  def make_specification options, &block
165
- opts = options.flatten.dup
166
- rationalize_parameters opts
167
- @specification = Aquarium::Utils::MethodUtils.method_args_to_hash(*opts) {|option| ""} # set other hash values to an empty string
218
+ opts = rationalize_parameters options.flatten.dup
219
+ # For non-hash inputs, use an empty string for the value
220
+ @specification = Aquarium::Utils::MethodUtils.method_args_to_hash(*opts) {|option| ""}
168
221
  use_default_object_if_defined unless (types_given? || objects_given? || pointcuts_given?)
169
222
  use_first_nonadvice_symbol_as_method(opts) unless methods_given?
170
- @advice = block || @specification[:advice]
223
+ @advice = determine_advice block
171
224
  if @advice.nil? && @specification[:noop].nil?
172
- raise Aquarium::Utils::InvalidOptions.new("No advice block or argument specified.")
225
+ bad_options "No advice block nor :advice argument was given."
173
226
  end
227
+ validate_specification
174
228
  end
175
229
 
230
+ def determine_advice block
231
+ # There can be only one advice; take the last one...
232
+ block || (@specification[:advice].kind_of?(Array) ? @specification[:advice].last : @specification[:advice])
233
+ end
234
+
176
235
  def init_pointcuts
177
236
  pointcuts = []
178
237
  if pointcuts_given?
@@ -181,61 +240,81 @@ module Aquarium
181
240
  pointcuts << pointcut
182
241
  elsif pointcut.kind_of?(Aquarium::Aspects::JoinPoint)
183
242
  pointcuts << Aquarium::Aspects::Pointcut.new(:join_point => pointcut)
184
- else
243
+ else # a hash of Pointcut.new options?
185
244
  pointcuts << Aquarium::Aspects::Pointcut.new(pointcut)
186
245
  end
187
246
  end
188
247
  else
189
248
  pc_options = {}
190
- pc_options[:types] = types_given.to_a if types_given?
191
- pc_options[:objects] = objects_given.to_a if objects_given?
192
- pc_options[:methods] = methods_given.to_a if methods_given?
193
- pc_options[:method_options] = method_options_given.to_a if method_options_given?
194
- pc_options[:attributes] = attributes_given.to_a if attributes_given?
195
- pc_options[:attribute_options] = attribute_options_given.to_a if attribute_options_given?
249
+ ALLOWED_OPTIONS_PLURAL.each do |option|
250
+ next if pointcut_new_doesnt_accept? option
251
+ self.instance_eval(<<-EOF, __FILE__, __LINE__)
252
+ pc_options[:#{option}] = #{option}_given if #{option}_given?
253
+ EOF
254
+ end
196
255
  pointcuts << Aquarium::Aspects::Pointcut.new(pc_options)
197
256
  end
198
- @pointcuts = Set.new(pointcuts)
257
+ @pointcuts = Set.new(remove_excluded_join_points_and_empty_pointcuts(pointcuts))
199
258
  end
200
259
 
260
+ def pointcut_new_doesnt_accept? option
261
+ case option
262
+ when :advices: true
263
+ when :pointcuts: true
264
+ when :default_objects: true
265
+ when :noops: true
266
+ else false
267
+ end
268
+ end
269
+
270
+ def remove_excluded_join_points_and_empty_pointcuts pointcuts
271
+ pointcuts.reject do |pc|
272
+ pc.join_points_matched.delete_if do |jp|
273
+ join_point_excluded? jp
274
+ end
275
+ pc.empty?
276
+ end
277
+ end
278
+
201
279
  def advise_join_points
202
280
  advice = @advice.to_proc
203
281
  @pointcuts.each do |pointcut|
204
282
  interesting_join_points(pointcut).each do |join_point|
205
- add_advice_framework join_point
283
+ attr_name = Aspect.make_advice_chain_attr_sym(join_point)
284
+ add_advice_framework(join_point) if need_advice_framework?(join_point)
206
285
  Aquarium::Aspects::Advice.sort_by_priority_order(specified_advice_kinds).reverse.each do |advice_kind|
207
- advice_chain = Aspect.get_advice_chain join_point.type_or_object, join_point.method_name
208
- add_advice_to_chain join_point, advice_chain, advice_kind, advice
286
+ add_advice_to_chain join_point, advice_kind, advice
209
287
  end
210
288
  end
211
289
  end
212
290
  end
213
291
 
214
- # Ignore any inserted methods that are part of the aspect implementation,
215
- # i.e., those that match the prefix returned by Aspect.aspect_method_prefix.
216
292
  def interesting_join_points pointcut
217
293
  pointcut.join_points_matched.reject do |join_point|
218
- join_point.method_name.to_s =~ /^#{Aspect.aspect_method_prefix}/
294
+ join_point_for_aspect_implementation_method? join_point
219
295
  end
220
296
  end
221
297
 
222
- def add_advice_to_chain join_point, advice_chain, advice_kind, advice
298
+ def join_point_for_aspect_implementation_method? join_point
299
+ join_point.method_name.to_s.index("#{Aspect.aspect_method_prefix}") == 0
300
+ end
301
+
302
+ def add_advice_to_chain join_point, advice_kind, advice
303
+ start_of_advice_chain = Aspect.get_advice_chain join_point
223
304
  options = @specification.merge({
224
305
  :aspect => self,
225
306
  :advice_kind => advice_kind,
226
307
  :advice => advice,
227
- :next_node => advice_chain,
308
+ :next_node => start_of_advice_chain,
228
309
  :static_join_point => join_point})
229
- # The returned node becomes the lead node in the chain.
230
- advice_chain = Aspect.get_advice_chain join_point.type_or_object, join_point.method_name
231
- Aspect.set_advice_chain(join_point.type_or_object, join_point.method_name, Aquarium::Aspects::AdviceChainNodeFactory.make_node(options))
232
- advice_chain = Aspect.get_advice_chain join_point.type_or_object, join_point.method_name
310
+ # New node is new start of chain.
311
+ Aspect.set_advice_chain(join_point, Aquarium::Aspects::AdviceChainNodeFactory.make_node(options))
233
312
  end
234
313
 
235
- def get_jps message
314
+ def get_jps which_jps
236
315
  jps = Set.new
237
316
  @pointcuts.each do |pointcut|
238
- jps = jps.union(pointcut.send(message))
317
+ jps = jps.union(pointcut.send(which_jps))
239
318
  end
240
319
  jps
241
320
  end
@@ -244,62 +323,93 @@ module Aquarium
244
323
  def self.advice_chain_inspect advice_chain
245
324
  return "[nil]" if advice_chain.nil?
246
325
  "<br/>"+advice_chain.inspect do |advice_chain|
247
- "#{advice_chain.class.name}:#{advice_chain.object_id}: aspect = #{advice_chain.aspect.object_id}, next_node = #{advice_chain_inspect advice_chain.next_node}"
326
+ "#{advice_chain.class.name}:#{advice_chain.object_id}: join_point = #{advice_chain.static_join_point}: aspect = #{advice_chain.aspect.object_id}, next_node = #{advice_chain_inspect advice_chain.next_node}"
248
327
  end.gsub(/\</,"&lt;").gsub(/\>/,"&gt;")+"<br/>"
249
328
  end
250
-
329
+
330
+ def need_advice_framework? join_point
331
+ alias_method_name = (Aspect.make_saved_method_name join_point).intern
332
+ private_method_defined?(join_point, alias_method_name) == false
333
+ end
334
+
251
335
  def add_advice_framework join_point
252
- type_to_advise = join_point.target_type || (class << join_point.target_object; self; end)
253
- alias_method_name = (saved_method_name join_point).intern
254
- return if private_method_defined? join_point.type_or_object, alias_method_name
255
- type_to_advise.class_eval(<<-EVAL_WRAPPER, __FILE__, __LINE__)
256
- #{static_method_prefix join_point.instance_method?}
257
- #{alias_original_method_text alias_method_name, join_point}
258
- #{static_method_suffix join_point.instance_method?}
259
- EVAL_WRAPPER
260
- Aspect.set_advice_chain join_point.type_or_object, join_point.method_name, Aquarium::Aspects::AdviceChainNodeFactory.make_node(
336
+ alias_method_name = (Aspect.make_saved_method_name join_point).intern
337
+ type_to_advise = Aspect.type_to_advise_for join_point
338
+ # Note: Must set advice chain, a class variable on the type we're advising, FIRST.
339
+ # Otherwise the class_eval that follows will assume the @@ advice chain belongs to Aspect!
340
+ Aspect.set_advice_chain join_point, Aquarium::Aspects::AdviceChainNodeFactory.make_node(
261
341
  :aspect => nil, # Belongs to all aspects that might advise this join point!
262
342
  :advice_kind => :none,
263
343
  :alias_method_name => alias_method_name,
264
344
  :static_join_point => join_point)
265
- advice_chain = Aspect.get_advice_chain join_point.type_or_object, join_point.method_name
345
+ type_being_advised_text = join_point.instance_method? ? "self.class" : "self"
346
+ unless Aspect.is_type_join_point?(join_point)
347
+ type_being_advised_text = "(class << self; self; end)"
348
+ end
349
+ type_to_advise2 = join_point.instance_method? ? type_to_advise : (class << type_to_advise; self; end)
350
+ type_to_advise2.class_eval(<<-EOF, __FILE__, __LINE__)
351
+ #{def_eigenclass_method_text join_point}
352
+ #{alias_original_method_text alias_method_name, join_point, type_being_advised_text}
353
+ EOF
266
354
  end
267
355
 
268
- def static_method_prefix instance_method
269
- return "" if instance_method
270
- <<-EOF
271
- class << self
272
- EOF
356
+ def static_method_prefix join_point
357
+ if join_point.instance_method?
358
+ "@@type_being_advised = self"
359
+ else
360
+ "@@type_being_advised = self"
361
+ "class << self"
362
+ end
273
363
  end
274
364
 
275
- def static_method_suffix instance_method
276
- return "" if instance_method
277
- <<-EOF
278
- end
279
- EOF
365
+ def static_method_suffix join_point
366
+ join_point.instance_method? ? "" : "end"
280
367
  end
281
-
282
- # Note that we make the alias for the original method private, so it doesn't pollute the "interface"
283
- # of the advised classes. This also means that we have to use the Class.method(name).call()
284
- # idiom when invoking it.
285
- def alias_original_method_text alias_method_name, join_point
286
- self_name = join_point.target_type.nil? ? "self" : join_point.target_type.name
368
+
369
+ # When advising an instance, create an override method that gets advised instead of the types method.
370
+ # Otherwise, all objects will be advised!
371
+ # Note: this also solves bug #15202.
372
+ def def_eigenclass_method_text join_point
373
+ Aspect.is_type_join_point?(join_point) ? "" : "def #{join_point.method_name} *args; super; end"
374
+ end
375
+
376
+ # For the temporary eigenclass method wrapper, alias it to a temporary name then undefine it, so it
377
+ # completely disappears. Next, remove_method on the method name so the object starts responding again
378
+ # to the original definition.
379
+ def undef_eigenclass_method_text join_point
380
+ Aspect.is_type_join_point?(join_point) ? "" : "remove_method :#{join_point.method_name}"
381
+ end
382
+
383
+ # TODO Move to JoinPoint
384
+ def self.is_type_join_point? join_point
385
+ Aquarium::Utils::TypeUtils.is_type? join_point.type_or_object
386
+ end
387
+
388
+ def self.type_to_advise_for join_point
389
+ join_point.target_type ? join_point.target_type : (class << join_point.target_object; self; end)
390
+ end
391
+
392
+ def alias_original_method_text alias_method_name, join_point, type_being_advised_text
287
393
  target_self = join_point.instance_method? ? "self" : join_point.target_type.name
394
+ advice_chain_attr_sym = Aspect.make_advice_chain_attr_sym join_point
288
395
  <<-EOF
289
396
  alias_method :#{alias_method_name}, :#{join_point.method_name}
290
397
  def #{join_point.method_name} *args, &block_for_method
291
- advice_chain = Aspect.get_advice_chain #{self_name}, :#{join_point.method_name}
398
+ advice_chain = #{type_being_advised_text}.send :class_variable_get, "#{advice_chain_attr_sym}"
292
399
  static_join_point = advice_chain.static_join_point
293
- advice_join_point = Aspect.make_advice_join_point static_join_point, #{target_self}, args, block_for_method
400
+ advice_join_point = static_join_point.make_current_context_join_point(
401
+ :advice_kind => :before,
402
+ :advised_object => #{target_self},
403
+ :parameters => args,
404
+ :block_for_method => block_for_method)
294
405
  advice_chain.call advice_join_point, *args
295
406
  end
296
407
  #{join_point.visibility.to_s} :#{join_point.method_name}
297
408
  private :#{alias_method_name}
298
409
  EOF
299
410
  end
300
-
411
+
301
412
  def unalias_original_method_text alias_method_name, join_point
302
- self_name = join_point.target_type.nil? ? "self" : join_point.target_type.name
303
413
  <<-EOF
304
414
  alias_method :#{join_point.method_name}, :#{alias_method_name}
305
415
  #{join_point.visibility.to_s} :#{join_point.method_name}
@@ -307,134 +417,98 @@ module Aquarium
307
417
  EOF
308
418
  end
309
419
 
310
- def remove_advice_chain_class_variable_text alias_method_name, join_point
311
- self_name = join_point.target_type.nil? ? "self" : join_point.target_type.name
312
- <<-EOF
313
- advice_chain_name = :@@#{Aspect.advice_chain_attr_name join_point.type_or_object, join_point.method_name}
314
- remove_class_variable advice_chain_name
315
- EOF
316
- end
317
-
318
- def self.make_advice_join_point static_join_point, object, method_parameters, block_for_method
319
- static_join_point.make_current_context_join_point(
320
- :advice_kind => :before,
321
- :advised_object => object,
322
- :parameters => method_parameters,
323
- :block_for_method => block_for_method)
324
- end
325
-
326
420
  def remove_advice_for_aspect_at join_point
327
- advice_chain = Aspect.get_advice_chain join_point.type_or_object, join_point.method_name
328
- advice_chain = prune_nodes_in advice_chain
329
- if advice_chain.empty?
330
- remove_advice_framework_for join_point
331
- else
332
- Aspect.set_advice_chain join_point.type_or_object, join_point.method_name, advice_chain
333
- end
421
+ prune_nodes_in_advice_chain_for join_point
422
+ advice_chain = Aspect.get_advice_chain join_point
423
+ remove_advice_framework_for(join_point) if advice_chain.empty?
334
424
  end
335
425
 
336
- def prune_nodes_in advice_chain
426
+ def prune_nodes_in_advice_chain_for join_point
427
+ advice_chain = Aspect.get_advice_chain join_point
337
428
  # Use equal? for the aspects to compare object id only,
338
429
  while advice_chain.empty? == false && advice_chain.aspect.equal?(self)
339
430
  advice_chain = advice_chain.next_node
340
431
  end
341
- keeper_node = node = advice_chain
432
+ node = advice_chain
342
433
  while node.empty? == false
343
434
  while node.next_node.aspect.equal?(self)
344
435
  node.next_node = node.next_node.next_node
345
436
  end
346
437
  node = node.next_node
347
438
  end
348
- advice_chain
439
+ Aspect.set_advice_chain join_point, advice_chain
349
440
  end
350
441
 
351
442
  def remove_advice_framework_for join_point
352
- if Aquarium::Utils::TypeUtils.is_type?(join_point.type_or_object)
353
- restore_type_method join_point
354
- else
355
- restore_object_method join_point
356
- end
443
+ type_to_advise = Aspect.type_to_advise_for join_point
444
+ type_to_advise.class_eval(<<-EVAL_WRAPPER, __FILE__, __LINE__)
445
+ #{restore_original_method_text join_point}
446
+ EVAL_WRAPPER
447
+ Aspect.remove_advice_chain join_point
357
448
  end
358
449
 
359
- def restore_type_method join_point
360
- alias_method_name = (saved_method_name join_point).intern
361
- join_point.target_type.class_eval(<<-EVAL_WRAPPER, __FILE__, __LINE__)
362
- #{static_method_prefix join_point.instance_method?}
450
+ def restore_original_method_text join_point
451
+ alias_method_name = (Aspect.make_saved_method_name join_point).intern
452
+ <<-EOF
453
+ #{static_method_prefix join_point}
363
454
  #{unalias_original_method_text alias_method_name, join_point}
364
- #{static_method_suffix join_point.instance_method?}
365
- #{remove_advice_chain_class_variable_text alias_method_name, join_point}
366
- EVAL_WRAPPER
455
+ #{undef_eigenclass_method_text join_point}
456
+ #{static_method_suffix join_point}
457
+ EOF
367
458
  end
368
459
 
369
- def restore_object_method join_point
370
- saved = saved_method_name join_point
371
- singleton = class << join_point.target_object; self; end
372
- singleton.class_eval do
373
- alias_method join_point.method_name, saved
374
- send join_point.visibility, join_point.method_name
375
- undef_method saved.intern
376
- end
377
- advice_chain_name = "@#{Aspect.advice_chain_attr_name join_point.type_or_object, join_point.method_name}".intern
378
- join_point.target_object.method(:remove_instance_variable).call advice_chain_name
460
+ # TODO optimize calls to these *_advice_chain methods from other private methods.
461
+ def self.set_advice_chain join_point, advice_chain
462
+ advice_chain_attr_sym = self.make_advice_chain_attr_sym join_point
463
+ type_to_advise_for(join_point).send :class_variable_set, advice_chain_attr_sym, advice_chain
379
464
  end
380
465
 
381
- def self.set_advice_chain type_or_object, method_name, advice_chain
382
- advice_chain_attr_sym = self.make_advice_chain_attr_sym type_or_object, method_name
383
- if Aquarium::Utils::TypeUtils.is_type?(type_or_object)
384
- type_or_object.class_eval do
385
- class_variable_set advice_chain_attr_sym, advice_chain
386
- end
387
- else
388
- type_or_object.instance_eval do
389
- instance_variable_set advice_chain_attr_sym, advice_chain
390
- end
391
- end
466
+ def self.get_advice_chain join_point
467
+ advice_chain_attr_sym = self.make_advice_chain_attr_sym join_point
468
+ type_to_advise_for(join_point).send :class_variable_get, advice_chain_attr_sym
392
469
  end
393
-
394
- def self.get_advice_chain type_or_object, method_name
395
- advice_chain_attr_sym = self.make_advice_chain_attr_sym type_or_object, method_name
396
- if Aquarium::Utils::TypeUtils.is_type?(type_or_object)
397
- type_or_object.class_eval do
398
- class_variable_get advice_chain_attr_sym
399
- end
400
- else
401
- type_or_object.instance_eval do
402
- instance_variable_get advice_chain_attr_sym
403
- end
404
- end
470
+
471
+ def self.remove_advice_chain join_point
472
+ advice_chain_attr_sym = self.make_advice_chain_attr_sym join_point
473
+ type_to_advise_for(join_point).send :remove_class_variable, advice_chain_attr_sym
474
+ end
475
+
476
+ def private_method_defined? join_point, alias_method_name
477
+ type_to_advise = Aspect.type_to_advise_for join_point
478
+ type_to_advise.send(:private_instance_methods).include? alias_method_name.to_s
405
479
  end
406
480
 
407
- def self.make_advice_chain_attr_sym type_or_object, method_name
408
- ats = Aquarium::Utils::TypeUtils.is_type?(type_or_object) ? "@@" : "@"
409
- chain_class_var = (ats + self.advice_chain_attr_name(type_or_object, method_name)).intern
481
+ def self.make_advice_chain_attr_sym join_point
482
+ class_or_object_prefix = is_type_join_point?(join_point) ? "class_" : ""
483
+ type_or_object_key = make_type_or_object_key join_point
484
+ valid_name = Aquarium::Utils::NameUtils.make_valid_attr_name_from_method_name join_point.method_name
485
+ "@@#{Aspect.aspect_method_prefix}#{class_or_object_prefix}advice_chain_#{type_or_object_key}_#{valid_name}".intern
410
486
  end
411
487
 
412
-
413
- def private_method_defined? type_or_object, method_name
414
- if Aquarium::Utils::TypeUtils.is_type? type_or_object
415
- type_or_object.private_instance_methods.include? method_name.to_s
416
- else
417
- type_or_object.private_methods.include? method_name.to_s
418
- end
419
- end
420
-
421
- def self.advice_chain_attr_name type_or_object, method_name
422
- type_or_object_key = Aquarium::Utils::NameUtils.make_type_or_object_key(type_or_object)
423
- class_or_object_prefix = Aquarium::Utils::TypeUtils.is_type?(type_or_object) ? "class_" : ""
424
- valid_name = Aquarium::Utils::NameUtils.make_valid_attr_name_from_method_name method_name
425
- "#{self.aspect_method_prefix}#{class_or_object_prefix}advice_chain_#{type_or_object_key}_#{valid_name}"
488
+ def self.make_saved_method_name join_point
489
+ type_or_object_key = make_type_or_object_key join_point
490
+ valid_name = Aquarium::Utils::NameUtils.make_valid_attr_name_from_method_name join_point.method_name
491
+ "#{Aspect.aspect_method_prefix}saved_#{type_or_object_key}_#{valid_name}"
426
492
  end
427
493
 
428
494
  def self.aspect_method_prefix
429
495
  "_aspect_"
430
496
  end
431
497
 
432
- def saved_method_name join_point
433
- to_key = Aquarium::Utils::NameUtils.make_type_or_object_key(join_point.type_or_object)
434
- valid_name = Aquarium::Utils::NameUtils.make_valid_attr_name_from_method_name join_point.method_name
435
- "#{Aspect.aspect_method_prefix}saved_#{to_key}_#{valid_name}"
498
+ def self.determine_type_or_object join_point
499
+ join_point.type_or_object
500
+ # type_or_object = join_point.type_or_object
501
+ # method_type = join_point.instance_or_class_method
502
+ # if is_type_join_point? join_point
503
+ # type_or_object = Aquarium::Utils::MethodUtils.definer type_or_object, join_point.method_name, "#{method_type}_method_only".intern
504
+ # end
505
+ # type_or_object
436
506
  end
437
-
507
+
508
+ def self.make_type_or_object_key join_point
509
+ Aquarium::Utils::NameUtils.make_type_or_object_key determine_type_or_object(join_point)
510
+ end
511
+
438
512
  def specified_advice_kinds
439
513
  @specification.keys.select do |key|
440
514
  Aquarium::Aspects::Advice.kinds.include? key
@@ -442,25 +516,10 @@ module Aquarium
442
516
  end
443
517
 
444
518
  def rationalize_parameters opts
445
- return unless opts.last.kind_of?(Hash)
446
- option_synonyms = {
447
- :type => :types,
448
- :within_type => :types,
449
- :within_types => :types,
450
- :object => :objects,
451
- :within_object => :objects,
452
- :within_objects => :objects,
453
- :method => :methods,
454
- :within_method => :methods,
455
- :within_methods => :methods,
456
- :attribute => :attributes,
457
- :pointcut => :pointcuts,
458
- :within_pointcut => :pointcuts,
459
- :within_pointcuts => :pointcuts
460
- }
519
+ return opts unless opts.last.kind_of?(Hash)
461
520
  hash = opts.pop.dup
462
521
  opts.push hash
463
- option_synonyms.each do |syn, actual|
522
+ ALLOWED_OPTIONS_SYNONYMS_MAP.each do |syn, actual|
464
523
  if hash.has_key? syn
465
524
  hash[actual] = make_array(hash[actual], hash[syn])
466
525
  hash.delete syn
@@ -468,19 +527,18 @@ module Aquarium
468
527
  end
469
528
  # Only one advice argument allowed.
470
529
  unless hash.has_key?(:advice)
471
- advice_synonyms = {
472
- :call => :advice,
473
- :invoke => :advice,
474
- :advise_with => :advice
475
- }
476
- advice_synonyms.each do |syn, actual|
530
+ ADVICE_OPTIONS_SYNONYMS_MAP.each do |syn, actual|
477
531
  if hash.has_key? syn
478
532
  hash[actual] = hash[syn]
479
533
  hash.delete syn
480
534
  end
481
535
  end
482
536
  end
483
- [:types, :objects, :methods, :method_options, :pointcuts, :default_object].each do |opt|
537
+ ALLOWED_OPTIONS_PLURAL.each do |opt|
538
+ case opt
539
+ when :advices: next
540
+ when :noops: next
541
+ end
484
542
  hash[opt] = Set.new(make_array(hash[opt]))
485
543
  end
486
544
  opts
@@ -495,17 +553,31 @@ module Aquarium
495
553
  bad_options(":after can't be used with :after_returning.") if after_given_with? :after_returning
496
554
  bad_options(":after can't be used with :after_raising.") if after_given_with? :after_raising
497
555
  bad_options(":after_returning can't be used with :after_raising.") if after_returning_given_with? :after_raising
498
- unless pointcuts_given? or types_given? or objects_given? #or objects_given_excluding_default?
556
+ unless pointcuts_given? or types_given? or objects_given? or default_objects_given?
499
557
  bad_options("At least one of :pointcut(s), :type(s), :object(s) is required.")
500
558
  end
501
559
  if pointcuts_given? and (types_given? or objects_given?)
502
560
  bad_options("Can't specify both :pointcut(s) and one or more of :type(s), and/or :object(s).")
503
561
  end
504
- # unless methods_given? or pointcuts_given? or types_given? #or objects_given_excluding_default?
505
- # specification_too_short
506
- # end
562
+ @specification.each_key do |parameter|
563
+ check_parameter parameter
564
+ end
507
565
  end
508
566
 
567
+ def check_parameter parameter
568
+ bad_options("Unrecognized parameter: :#{parameter}") unless is_valid_parameter(parameter)
569
+ end
570
+
571
+ def is_valid_parameter key
572
+ ALLOWED_OPTIONS.include?(key) || ALLOWED_OPTIONS_SYNONYMS_MAP.keys.include?(key) ||
573
+ ADVICE_OPTIONS_SYNONYMS_MAP.keys.include?(key) || KINDS_IN_PRIORITY_ORDER.include?(key) ||
574
+ parameter_is_a_method_name?(key) # i.e., use_first_nonadvice_symbol_as_method
575
+ end
576
+
577
+ def parameter_is_a_method_name? name
578
+ @specification[:methods].include? name
579
+ end
580
+
509
581
  def advice_kinds_given
510
582
  Aquarium::Aspects::Advice.kinds.inject([]) {|ary, kind| ary << @specification[kind] if @specification[kind]; ary}
511
583
  end
@@ -514,21 +586,23 @@ module Aquarium
514
586
  not advice_kinds_given.empty?
515
587
  end
516
588
 
517
- def around_given_with? other_advice_kind_sym
518
- @specification[:around] and @specification[other_advice_kind_sym]
519
- end
520
-
521
- def after_given_with? other_advice_kind_sym
522
- @specification[:after] and @specification[other_advice_kind_sym]
523
- end
524
- def after_returning_given_with? other_advice_kind_sym
525
- @specification[:after_returning] and @specification[other_advice_kind_sym]
589
+ %w[around after after_returning].each do |advice_kind|
590
+ class_eval(<<-EOF, __FILE__, __LINE__)
591
+ def #{advice_kind}_given_with? other_advice_kind_sym
592
+ @specification[:#{advice_kind}] and @specification[other_advice_kind_sym]
593
+ end
594
+ EOF
526
595
  end
527
-
528
- %w[pointcuts types objects methods attributes method_options attribute_options].each do |name|
596
+
597
+ ALLOWED_OPTIONS_PLURAL.each do |name|
598
+ case name
599
+ when :advices : next
600
+ when :default_objects : next
601
+ when :noops : next
602
+ end
529
603
  class_eval(<<-EOF, __FILE__, __LINE__)
530
604
  def #{name}_given
531
- @specification[:#{name}]
605
+ make_array(@specification[:#{name}])
532
606
  end
533
607
 
534
608
  def #{name}_given?
@@ -550,7 +624,8 @@ module Aquarium
550
624
  end
551
625
 
552
626
  def bad_options message
553
- raise Aquarium::Utils::InvalidOptions.new("Invalid options given. " + message + " (options: #{@original_options.inspect})")
627
+ raise Aquarium::Utils::InvalidOptions.new("Invalid options given. " + message +
628
+ " (options: #{@original_options.inspect}, mapped to specification: #{@specification.inspect})")
554
629
  end
555
630
  end
556
631
  end