aquarium 0.4.0 → 0.4.1

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 (45) hide show
  1. data/CHANGES +26 -5
  2. data/README +8 -8
  3. data/RELEASE-PLAN +20 -2
  4. data/TODO.rb +26 -0
  5. data/UPGRADE +5 -5
  6. data/examples/aspect_design_example.rb +1 -1
  7. data/examples/aspect_design_example_spec.rb +1 -1
  8. data/examples/design_by_contract_example.rb +4 -9
  9. data/examples/design_by_contract_example_spec.rb +7 -9
  10. data/examples/exception_wrapping_example.rb +48 -0
  11. data/examples/exception_wrapping_example_spec.rb +49 -0
  12. data/examples/reusable_aspect_hack_example.rb +56 -0
  13. data/examples/reusable_aspect_hack_example_spec.rb +80 -0
  14. data/lib/aquarium.rb +1 -0
  15. data/lib/aquarium/aspects.rb +1 -1
  16. data/lib/aquarium/aspects/advice.rb +16 -13
  17. data/lib/aquarium/aspects/aspect.rb +81 -56
  18. data/lib/aquarium/aspects/join_point.rb +4 -4
  19. data/lib/aquarium/aspects/pointcut.rb +49 -73
  20. data/lib/aquarium/dsl.rb +2 -0
  21. data/lib/aquarium/dsl/aspect_dsl.rb +77 -0
  22. data/lib/aquarium/{aspects/dsl → dsl}/object_dsl.rb +2 -2
  23. data/lib/aquarium/extras/design_by_contract.rb +1 -1
  24. data/lib/aquarium/finders.rb +1 -1
  25. data/lib/aquarium/finders/method_finder.rb +26 -26
  26. data/lib/aquarium/finders/type_finder.rb +45 -39
  27. data/lib/aquarium/utils/array_utils.rb +6 -5
  28. data/lib/aquarium/utils/default_logger.rb +2 -1
  29. data/lib/aquarium/utils/options_utils.rb +178 -67
  30. data/lib/aquarium/utils/set_utils.rb +8 -3
  31. data/lib/aquarium/version.rb +1 -1
  32. data/spec/aquarium/aspects/aspect_invocation_spec.rb +111 -14
  33. data/spec/aquarium/aspects/aspect_spec.rb +91 -7
  34. data/spec/aquarium/aspects/pointcut_spec.rb +61 -0
  35. data/spec/aquarium/{aspects/dsl → dsl}/aspect_dsl_spec.rb +76 -32
  36. data/spec/aquarium/finders/method_finder_spec.rb +80 -80
  37. data/spec/aquarium/finders/type_finder_spec.rb +57 -52
  38. data/spec/aquarium/finders/type_finder_with_descendents_and_ancestors_spec.rb +12 -12
  39. data/spec/aquarium/spec_example_types.rb +4 -3
  40. data/spec/aquarium/utils/array_utils_spec.rb +9 -7
  41. data/spec/aquarium/utils/options_utils_spec.rb +106 -5
  42. data/spec/aquarium/utils/set_utils_spec.rb +14 -0
  43. metadata +12 -7
  44. data/lib/aquarium/aspects/dsl.rb +0 -2
  45. data/lib/aquarium/aspects/dsl/aspect_dsl.rb +0 -64
@@ -0,0 +1,80 @@
1
+ require File.dirname(__FILE__) + '/../spec/aquarium/spec_helper'
2
+ require 'aquarium'
3
+
4
+ # Example demonstrating a hack for defining a reusable aspect in a module
5
+ # so that the aspect only gets created when the module is included by another
6
+ # module or class.
7
+ # Hacking like this defies the spirit of Aquarium's goal of being "intuitive",
8
+ # so I created a feature request #19122 to address this problem.
9
+ #
10
+ # WARNING: put the "include ..." statement at the END of the class declaration,
11
+ # as shown below. If you put the include statement at the beginning, as you
12
+ # normally wouuld for including a module, it won't advice any join points,
13
+ # because no methods will have been defined at that point!!
14
+
15
+ module Aquarium
16
+ module Reusables
17
+ module TraceMethods
18
+ def self.advice_invoked?
19
+ @@advice_invoked
20
+ end
21
+ def self.reset_advice_invoked
22
+ @@advice_invoked = false
23
+ end
24
+
25
+ def self.append_features mod
26
+ Aquarium::Aspects::Aspect.new :around, :ignore_no_matching_join_points => true,
27
+ :type => mod, :methods => :all, :method_options => [:exclude_ancestor_methods] do |jp, object, *args|
28
+ @@advice_invoked = true
29
+ jp.proceed
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ class NotTraced1
37
+ def doit; end
38
+ end
39
+ class NotTraced2
40
+ include Aquarium::Reusables::TraceMethods
41
+ def doit; end
42
+ end
43
+ class Traced1
44
+ def doit; end
45
+ include Aquarium::Reusables::TraceMethods
46
+ end
47
+ class Traced2
48
+ def doit; end
49
+ end
50
+
51
+ describe "Reusable aspect defined in a module can be evaluated at 'include' time if append_features is used" do
52
+ before :each do
53
+ Aquarium::Reusables::TraceMethods.reset_advice_invoked
54
+ end
55
+
56
+ it "should not advise types that don't include the module with the aspect" do
57
+ NotTraced1.new.doit
58
+ Aquarium::Reusables::TraceMethods.advice_invoked?.should be_false
59
+ end
60
+
61
+ it "should not advise any methods if the module with the aspect is included before any methods are defined!" do
62
+ NotTraced2.new.doit
63
+ Aquarium::Reusables::TraceMethods.advice_invoked?.should be_false
64
+ end
65
+
66
+ it "should advise methods if the module with the aspect is included after the methods are defined" do
67
+ Traced1.new.doit
68
+ Aquarium::Reusables::TraceMethods.advice_invoked?.should be_true
69
+ end
70
+
71
+ it "should advise methods after the module with the aspect is included" do
72
+ Traced2.new.doit
73
+ Aquarium::Reusables::TraceMethods.advice_invoked?.should be_false
74
+ class Traced2
75
+ include Aquarium::Reusables::TraceMethods
76
+ end
77
+ Traced2.new.doit
78
+ Aquarium::Reusables::TraceMethods.advice_invoked?.should be_true
79
+ end
80
+ end
@@ -4,4 +4,5 @@ require 'aquarium/extensions'
4
4
  require 'aquarium/finders'
5
5
  require 'aquarium/aspects'
6
6
  require 'aquarium/version'
7
+ require 'aquarium/dsl'
7
8
 
@@ -3,4 +3,4 @@ require 'aquarium/aspects/aspect'
3
3
  require 'aquarium/aspects/join_point'
4
4
  require 'aquarium/aspects/pointcut'
5
5
  require 'aquarium/aspects/pointcut_composition'
6
- require 'aquarium/aspects/dsl'
6
+ require 'aquarium/dsl'
@@ -37,6 +37,15 @@ module Aquarium
37
37
  attr_accessor(:#{key})
38
38
  EOF
39
39
  end
40
+
41
+ def call_advice jp, obj, *args
42
+ case advice.arity
43
+ when 0 then advice.call
44
+ when 1 then advice.call jp
45
+ when 2 then advice.call jp, obj
46
+ else advice.call jp, obj, *args
47
+ end
48
+ end
40
49
  end
41
50
 
42
51
  def call jp, obj, *args
@@ -106,15 +115,9 @@ module Aquarium
106
115
  def initialize options = {}
107
116
  super(options) { |jp, obj, *args|
108
117
  block_for_method = jp.context.block_for_method
109
- method = invoking_object(jp).method(@alias_method_name)
110
118
  block_for_method.nil? ?
111
119
  invoking_object(jp).send(@alias_method_name, *args) :
112
120
  invoking_object(jp).send(@alias_method_name, *args, &block_for_method)
113
- # Buggy!!
114
- # method = invoking_object.method(@alias_method_name)
115
- # block_for_method.nil? ?
116
- # method.call(*args) :
117
- # method.call(*args, &block_for_method)
118
121
  }
119
122
  end
120
123
  end
@@ -123,7 +126,7 @@ module Aquarium
123
126
  def initialize options = {}
124
127
  super(options) { |jp, obj, *args|
125
128
  before_jp = jp.make_current_context_join_point :advice_kind => :before, :current_advice_node => self
126
- advice.call(before_jp, obj, *args)
129
+ call_advice(before_jp, obj, *args)
127
130
  next_node.call(jp, obj, *args)
128
131
  }
129
132
  end
@@ -134,7 +137,7 @@ module Aquarium
134
137
  super(options) { |jp, obj, *args|
135
138
  returned_value = next_node.call(jp, obj, *args)
136
139
  next_jp = jp.make_current_context_join_point :advice_kind => :after_returning, :returned_value => returned_value, :current_advice_node => self
137
- advice.call(next_jp, obj, *args)
140
+ call_advice(next_jp, obj, *args)
138
141
  next_jp.context.returned_value # allow advice to modify the returned value
139
142
  }
140
143
  end
@@ -151,7 +154,7 @@ module Aquarium
151
154
  rescue Object => raised_exception
152
155
  if after_raising_exceptions_list_includes raised_exception
153
156
  next_jp = jp.make_current_context_join_point :advice_kind => :after_raising, :raised_exception => raised_exception, :current_advice_node => self
154
- advice.call(next_jp, obj, *args)
157
+ call_advice(next_jp, obj, *args)
155
158
  raised_exception = next_jp.context.raised_exception # allow advice to modify raised exception
156
159
  end
157
160
  raise raised_exception
@@ -173,16 +176,16 @@ module Aquarium
173
176
  class AfterAdviceChainNode < AdviceChainNode
174
177
  def initialize options = {}
175
178
  super(options) { |jp, obj, *args|
176
- # advice.call is invoked in each bloc, rather than once in an "ensure" clause, so the invocation in the rescue class
179
+ # call_advice is invoked in each bloc, rather than once in an "ensure" clause, so the invocation in the rescue class
177
180
  # can allow the advice to change the exception that will be raised.
178
181
  begin
179
182
  returned_value = next_node.call(jp, obj, *args)
180
183
  next_jp = jp.make_current_context_join_point :advice_kind => :after, :returned_value => returned_value, :current_advice_node => self
181
- advice.call(next_jp, obj, *args)
184
+ call_advice(next_jp, obj, *args)
182
185
  next_jp.context.returned_value # allow advice to modify the returned value
183
186
  rescue Object => raised_exception
184
187
  next_jp = jp.make_current_context_join_point :advice_kind => :after, :raised_exception => raised_exception, :current_advice_node => self
185
- advice.call(next_jp, obj, *args)
188
+ call_advice(next_jp, obj, *args)
186
189
  raise next_jp.context.raised_exception
187
190
  end
188
191
  }
@@ -193,7 +196,7 @@ module Aquarium
193
196
  def initialize options = {}
194
197
  super(options) { |jp, obj, *args|
195
198
  around_jp = jp.make_current_context_join_point :advice_kind => :around, :proceed_proc => next_node, :current_advice_node => self
196
- advice.call(around_jp, obj, *args)
199
+ call_advice(around_jp, obj, *args)
197
200
  }
198
201
  end
199
202
  end
@@ -32,29 +32,18 @@ module Aquarium
32
32
 
33
33
  attr_reader :specification, :pointcuts, :advice
34
34
 
35
- CANONICAL_OPTIONS = Pointcut::CANONICAL_OPTIONS.merge({
35
+ ASPECT_CANONICAL_OPTIONS = {
36
36
  "advice" => %w[action do_action use_advice advise_with invoke call],
37
- "pointcuts" => %w[pointcut within_pointcuts within_pointcut on_pointcuts on_pointcut],
38
- "exclude_pointcuts" => %w[exclude_pointcut exclude_on_pointcut exclude_on_pointcuts exclude_within_pointcut exclude_within_pointcuts],
37
+ "pointcuts" => %w[pointcut],
38
+ "exceptions" => %w[exception],
39
39
  "ignore_no_matching_join_points" => %[ignore_no_jps]
40
- })
40
+ }
41
+ add_prepositional_option_variants_for "pointcuts", ASPECT_CANONICAL_OPTIONS
42
+ add_exclude_options_for "pointcuts", ASPECT_CANONICAL_OPTIONS
43
+ CANONICAL_OPTIONS = Pointcut::CANONICAL_OPTIONS.merge ASPECT_CANONICAL_OPTIONS
41
44
 
42
- ALL_ALLOWED_OPTIONS = CANONICAL_OPTIONS.keys.inject([]) {|ary,i| ary << i << CANONICAL_OPTIONS[i]}.flatten +
43
- Pointcut::ATTRIBUTE_OPTIONS
45
+ canonical_options_given_methods CANONICAL_OPTIONS
44
46
 
45
- ALL_ALLOWED_OPTION_SYMBOLS = ALL_ALLOWED_OPTIONS.map {|o| o.intern} + Advice::kinds
46
-
47
- CANONICAL_OPTIONS.keys.each do |name|
48
- module_eval(<<-EOF, __FILE__, __LINE__)
49
- def #{name}_given
50
- @specification[:#{name}]
51
- end
52
-
53
- def #{name}_given?
54
- not (#{name}_given.nil? or #{name}_given.empty?)
55
- end
56
- EOF
57
- end
58
47
 
59
48
  # Aspect.new (:around | :before | :after | :after_returning | :after_raising ) \
60
49
  # (:pointcuts => [...]), | \
@@ -84,6 +73,8 @@ module Aquarium
84
73
  # Invoke the specified advice after the join point returns successfully.
85
74
  #
86
75
  # <tt>:after_raising [=> exception || [exception_list]]</tt>::
76
+ # <tt>:after_raising, :exceptions => (exception || [exception_list])</tt>::
77
+ # <tt>:after_raising, :exception => (exception || [exception_list])</tt>::
87
78
  # Invoke the specified advice after the join point raises one of the specified exceptions.
88
79
  # If no exceptions are specified, the advice is invoked after any exception is raised.
89
80
  #
@@ -115,7 +106,9 @@ module Aquarium
115
106
  def initialize *options, &block
116
107
  @first_option_that_was_method = []
117
108
  opts = rationalize options
118
- init_specification opts, CANONICAL_OPTIONS, &block
109
+ init_specification opts, CANONICAL_OPTIONS, (Pointcut::ATTRIBUTE_OPTIONS_VALUES + KINDS_IN_PRIORITY_ORDER) do
110
+ finish_specification_initialization &block
111
+ end
119
112
  init_pointcuts
120
113
  validate_specification
121
114
  return if noop
@@ -130,10 +123,6 @@ module Aquarium
130
123
  get_jps :join_points_not_matched
131
124
  end
132
125
 
133
- def all_allowed_option_symbols
134
- ALL_ALLOWED_OPTION_SYMBOLS + @first_option_that_was_method
135
- end
136
-
137
126
  def unadvise
138
127
  return if noop
139
128
  @pointcuts.each do |pointcut|
@@ -169,15 +158,38 @@ module Aquarium
169
158
  return (options.first.kind_of?(Hash) or options.first.kind_of?(Array)) ? options.first : options
170
159
  end
171
160
 
172
- def init_type_specific_specification original_options, options_hash, &block
161
+ def finish_specification_initialization &block
173
162
  Advice.kinds.each do |kind|
174
- @specification[kind] = Set.new(make_array(options_hash[kind])) if options_hash[kind]
163
+ found, value_array = contains_advice_kind kind
164
+ @specification[kind] = Set.new(value_array) if found
165
+ end
166
+ init_pointcut_specific_specification
167
+ options_to_ignore_when_validating = []
168
+ unless methods_given?
169
+ options_to_ignore_when_validating = use_first_nonadvice_symbol_as_method
175
170
  end
176
- @specification.merge! Pointcut.make_attribute_reading_writing_options(options_hash)
177
- use_default_objects_if_defined unless some_type_or_pc_option_given?
178
- use_first_nonadvice_symbol_as_method(original_options) unless methods_given?
179
171
  calculate_excluded_types
180
172
  @advice = determine_advice block
173
+ # Be careful to only add the exceptions if :after_raising was actually specified!
174
+ if (exceptions_given? and specified_advice_kinds.include?(:after_raising))
175
+ @specification[:after_raising] += exceptions_given
176
+ end
177
+ options_to_ignore_when_validating
178
+ end
179
+
180
+ def contains_advice_kind kind
181
+ keys = @original_options
182
+ hash = {}
183
+ if Array === @original_options
184
+ if Hash === @original_options.last
185
+ hash = @original_options.last
186
+ keys = @original_options[0...-1] + hash.keys
187
+ end
188
+ else Hash === @original_options
189
+ hash = @original_options
190
+ keys = @original_options.keys
191
+ end
192
+ keys.include?(kind) ? [true, make_array(hash[kind])] : [false, []]
181
193
  end
182
194
 
183
195
  def calculate_excluded_types
@@ -194,6 +206,19 @@ module Aquarium
194
206
  block || (@specification[:advice].to_a.first)
195
207
  end
196
208
 
209
+ def init_pointcut_specific_specification
210
+ options_hash = hash_in_original_options
211
+ @specification.merge! Pointcut.make_attribute_reading_writing_options(options_hash)
212
+ # Map the method options to their canonical values:
213
+ @specification[:method_options] = Aquarium::Finders::MethodFinder.init_method_options(@specification[:method_options])
214
+
215
+ Pointcut::validate_attribute_options @specification, options_hash
216
+ end
217
+
218
+ def hash_in_original_options
219
+ @original_options.kind_of?(Array) ? @original_options.last : @original_options
220
+ end
221
+
197
222
  def init_pointcuts
198
223
  pointcuts = []
199
224
  if pointcuts_given?
@@ -208,8 +233,9 @@ module Aquarium
208
233
  end
209
234
  else
210
235
  pc_options = {}
211
- Pointcut::ALL_ALLOWED_OPTION_SYMBOLS.each do |pc_option|
212
- pc_options[pc_option] = @specification[pc_option] unless @specification[pc_option].nil?
236
+ Pointcut::CANONICAL_OPTIONS.keys.each do |pc_option|
237
+ pco_sym = pc_option.intern
238
+ pc_options[pco_sym] = @specification[pco_sym] unless @specification[pco_sym].nil?
213
239
  end
214
240
  pointcuts << Pointcut.new(pc_options)
215
241
  end
@@ -351,7 +377,7 @@ module Aquarium
351
377
  advice_chain = #{type_being_advised_text}.send :class_variable_get, "#{advice_chain_attr_sym}"
352
378
  static_join_point = advice_chain.static_join_point
353
379
  advice_join_point = static_join_point.make_current_context_join_point(
354
- :advice_kind => :before,
380
+ :advice_kind => #{advice_kinds_given.inspect},
355
381
  :advised_object => #{target_self},
356
382
  :parameters => args,
357
383
  :block_for_method => block_for_method)
@@ -454,8 +480,8 @@ module Aquarium
454
480
  "_aspect_"
455
481
  end
456
482
 
457
- def some_type_or_pc_option_given?
458
- pointcuts_given? or some_type_option_given? or objects_given?
483
+ def some_type_object_join_point_or_pc_option_given?
484
+ pointcuts_given? or join_points_given? or some_type_option_given? or objects_given?
459
485
  end
460
486
 
461
487
  def some_type_option_given?
@@ -474,17 +500,22 @@ module Aquarium
474
500
  Advice.kinds & @specification.keys
475
501
  end
476
502
 
503
+ def options_given? option1, option2
504
+ @specification[option1] and @specification[option2]
505
+ end
506
+
477
507
  def validate_specification
478
508
  bad_options("One of #{Advice.kinds.inspect} is required.") unless advice_kinds_given?
479
- bad_options(":around can't be used with :before.") if around_given_with? :before
480
- bad_options(":around can't be used with :after.") if around_given_with? :after
481
- bad_options(":around can't be used with :after_returning.") if around_given_with? :after_returning
482
- bad_options(":around can't be used with :after_raising.") if around_given_with? :after_raising
483
- bad_options(":after can't be used with :after_returning.") if after_given_with? :after_returning
484
- bad_options(":after can't be used with :after_raising.") if after_given_with? :after_raising
485
- bad_options(":after_returning can't be used with :after_raising.") if after_returning_given_with? :after_raising
486
- unless some_type_or_pc_option_given?
487
- bad_options("At least one of :pointcut(s), :type(s), :type(s)_and_ancestors, :type(s)_and_descendents, :object(s) is required.")
509
+ %w[before after after_returning after_raising].each do |advice_kind|
510
+ bad_options(":around can't be used with :#{advice_kind}.") if options_given? :around, advice_kind.intern
511
+ end
512
+ %w[after_returning after_raising].each do |advice_kind|
513
+ bad_options(":after can't be used with :#{advice_kind}.") if options_given? :after, advice_kind.intern
514
+ end
515
+ bad_options(":after_returning can't be used with :after_raising.") if options_given? :after_returning, :after_raising
516
+ bad_options(":exceptions can't be specified except with :after_raising.") if exceptions_given? and not specified_advice_kinds.include?(:after_raising)
517
+ unless some_type_object_join_point_or_pc_option_given? or default_objects_given?
518
+ bad_options("At least one of :pointcut(s), :join_point(s), :type(s), :type(s)_and_ancestors, :type(s)_and_descendents, or :object(s) is required.")
488
519
  end
489
520
  if pointcuts_given? and (some_type_option_given? or objects_given?)
490
521
  bad_options("Can't specify both :pointcut(s) and one or more of :type(s), and/or :object(s).")
@@ -502,32 +533,26 @@ module Aquarium
502
533
  end
503
534
 
504
535
  def advice_kinds_given
505
- Advice.kinds.inject([]) {|ary, kind| ary << @specification[kind] if @specification[kind]; ary}
536
+ Advice.kinds.inject([]) {|ary, kind| ary << kind if @specification[kind]; ary}
506
537
  end
507
538
 
508
539
  def advice_kinds_given?
509
540
  not advice_kinds_given.empty?
510
541
  end
511
542
 
512
- %w[around after after_returning].each do |advice_kind|
513
- class_eval(<<-EOF, __FILE__, __LINE__)
514
- def #{advice_kind}_given_with? other_advice_kind_sym
515
- @specification[:#{advice_kind}] and @specification[other_advice_kind_sym]
516
- end
517
- EOF
518
- end
519
-
520
- def use_first_nonadvice_symbol_as_method options
543
+ def use_first_nonadvice_symbol_as_method
521
544
  2.times do |i|
522
- if options.size >= i+1
523
- sym = options[i]
545
+ if @original_options.size >= i+1
546
+ sym = @original_options[i]
524
547
  if sym.kind_of?(Symbol) && !Advice::kinds.include?(sym)
525
548
  @specification[:methods] = Set.new([sym])
549
+ @specification.delete sym
526
550
  @first_option_that_was_method << sym
527
- return
551
+ return [sym]
528
552
  end
529
553
  end
530
554
  end
555
+ []
531
556
  end
532
557
 
533
558
  def bad_options message
@@ -38,7 +38,7 @@ module Aquarium
38
38
 
39
39
  def update options
40
40
  options.each do |key, value|
41
- instance_variable_set "@#{key}".intern, value
41
+ instance_variable_set "@#{key}", value
42
42
  end
43
43
  end
44
44
 
@@ -125,7 +125,7 @@ module Aquarium
125
125
  type_or_object_sym = @target_type ? :type : :object
126
126
  results = Aquarium::Finders::MethodFinder.new.find type_or_object_sym => type_or_object,
127
127
  :method => method_name,
128
- :options => [visibility, instance_or_class_method]
128
+ :method_options => [visibility, instance_or_class_method]
129
129
  raise Aquarium::Utils::LogicError("MethodFinder returned more than one item! #{results.inspect}") if (results.matched.size + results.not_matched.size) != 1
130
130
  return results.matched.size == 1 ? true : false
131
131
  end
@@ -217,9 +217,9 @@ module Aquarium
217
217
  return type if type.kind_of? Module
218
218
  found = Aquarium::Finders::TypeFinder.new.find :type => type
219
219
  if found.matched.empty?
220
- bad_attributes("No type matched the string or regular expression: #{type}", options)
220
+ bad_attributes("No type matched the string or regular expression: #{type.inspect}", options)
221
221
  elsif found.matched.size > 1
222
- bad_attributes("More than one type matched the string or regular expression: #{type}", options)
222
+ bad_attributes("More than one type matched the string or regular expression: #{type.inspect}", options)
223
223
  end
224
224
  found.matched.keys.first
225
225
  end
@@ -160,7 +160,9 @@ module Aquarium
160
160
  #
161
161
  # Pointcut.new also accepts all the "universal" options documented in OptionsUtils.
162
162
  def initialize options = {}
163
- init_specification options, CANONICAL_OPTIONS
163
+ init_specification options, CANONICAL_OPTIONS, (ATTRIBUTE_OPTIONS_VALUES + Advice::KINDS_IN_PRIORITY_ORDER) do
164
+ finish_specification_initialization
165
+ end
164
166
  return if noop
165
167
  init_candidate_types
166
168
  init_candidate_objects
@@ -174,8 +176,9 @@ module Aquarium
174
176
  # the specifications are equal, and the candidate types and candidate objects are equal.
175
177
  # if you care only about the matched join points, then just compare #join_points_matched
176
178
  def eql? other
177
- object_id == other.object_id ||
178
- (specification == other.specification &&
179
+ object_id == other.object_id ||
180
+ (self.class === other &&
181
+ specification == other.specification &&
179
182
  candidate_types == other.candidate_types &&
180
183
  candidate_types_excluded == other.candidate_types_excluded &&
181
184
  candidate_objects == other.candidate_objects &&
@@ -195,111 +198,84 @@ module Aquarium
195
198
 
196
199
  alias to_s inspect
197
200
 
198
- CANONICAL_OPTIONS = {
199
- "types" => %w[type class classes module modules],
200
- "types_and_descendents" => %w[type_and_descendents class_and_descendents classes_and_descendents module_and_descendents modules_and_descendents],
201
- "types_and_ancestors" => %w[type_and_ancestors class_and_ancestors classes_and_ancestors module_and_ancestors modules_and_ancestors],
202
- "objects" => %w[object],
201
+ POINTCUT_CANONICAL_OPTIONS = {
202
+ "default_objects" => %w[default_object],
203
203
  "join_points" => %w[join_point],
204
- "methods" => %w[method within_method within_methods calling invoking calls_to invocations_of sending_message_to sending_messages_to],
204
+ "exclude_pointcuts" => %w[exclude_pointcut],
205
205
  "attributes" => %w[attribute accessing],
206
- "method_options" => %w[method_option restricting_methods_to],
207
206
  "attribute_options" => %w[attribute_option],
208
- "default_objects" => %w[default_object]
209
207
  }
210
- %w[types types_and_descendents types_and_ancestors objects join_points ].each do |thing|
211
- roots = CANONICAL_OPTIONS[thing].dup + [thing]
212
- CANONICAL_OPTIONS["exclude_#{thing}"] = roots.map {|x| "exclude_#{x}"}
213
- %w[for on in within].each do |prefix|
214
- roots.each do |root|
215
- CANONICAL_OPTIONS[thing] << "#{prefix}_#{root}"
216
- end
217
- end
218
- end
219
- CANONICAL_OPTIONS["methods"].dup.each do |synonym|
220
- CANONICAL_OPTIONS["methods"] << "#{synonym}_methods_matching"
208
+ add_prepositional_option_variants_for "join_points", POINTCUT_CANONICAL_OPTIONS
209
+ add_exclude_options_for "join_points", POINTCUT_CANONICAL_OPTIONS
210
+ Aquarium::Utils::OptionsUtils.universal_prepositions.each do |prefix|
211
+ POINTCUT_CANONICAL_OPTIONS["exclude_pointcuts"] += ["exclude_#{prefix}_pointcuts", "exclude_#{prefix}_pointcut"]
221
212
  end
222
- CANONICAL_OPTIONS["exclude_methods"] = []
223
- CANONICAL_OPTIONS["methods"].each do |synonym|
224
- CANONICAL_OPTIONS["exclude_methods"] << "exclude_#{synonym}"
225
- end
226
- CANONICAL_OPTIONS["exclude_pointcuts"] = ["exclude_pointcut"] +
227
- %w[for on in within].map {|prefix| ["exclude_#{prefix}_pointcuts", "exclude_#{prefix}_pointcut"]}.flatten
228
-
229
- ATTRIBUTE_OPTIONS = %w[reading writing changing]
230
-
231
- ALL_ALLOWED_OPTIONS = ATTRIBUTE_OPTIONS +
232
- CANONICAL_OPTIONS.keys.inject([]) {|ary,i| ary << i << CANONICAL_OPTIONS[i]}.flatten
213
+ CANONICAL_OPTIONS = Aquarium::Finders::TypeFinder::CANONICAL_OPTIONS.merge(
214
+ Aquarium::Finders::MethodFinder::METHOD_FINDER_CANONICAL_OPTIONS.merge(POINTCUT_CANONICAL_OPTIONS))
233
215
 
234
- ALL_ALLOWED_OPTION_SYMBOLS = ALL_ALLOWED_OPTIONS.map {|o| o.intern}
235
-
236
- def all_allowed_option_symbols
237
- ALL_ALLOWED_OPTION_SYMBOLS
238
- end
216
+ ATTRIBUTE_OPTIONS_VALUES = %w[reading writing changing]
239
217
 
240
- CANONICAL_OPTIONS.keys.each do |name|
241
- module_eval(<<-EOF, __FILE__, __LINE__)
242
- def #{name}_given
243
- @specification[:#{name}]
244
- end
245
-
246
- def #{name}_given?
247
- not (#{name}_given.nil? or #{name}_given.empty?)
248
- end
249
- EOF
250
- end
218
+ canonical_options_given_methods CANONICAL_OPTIONS
219
+ canonical_option_accessor CANONICAL_OPTIONS
251
220
 
252
221
  def self.make_attribute_reading_writing_options options_hash
253
222
  result = {}
254
223
  [:writing, :changing, :reading].each do |attr_key|
255
- unless options_hash[attr_key].nil? or options_hash[attr_key].empty?
256
- result[:attributes] ||= Set.new([])
257
- result[:attribute_options] ||= Set.new([])
258
- result[:attributes].merge(Aquarium::Utils::ArrayUtils.make_array(options_hash[attr_key]))
259
- attr_opt = attr_key == :reading ? :readers : :writers
260
- result[:attribute_options] << attr_opt
261
- end
224
+ next if options_hash[attr_key].nil? or options_hash[attr_key].empty?
225
+ result[:attributes] ||= Set.new([])
226
+ result[:attribute_options] ||= Set.new([])
227
+ result[:attributes].merge(Aquarium::Utils::ArrayUtils.make_array(options_hash[attr_key]))
228
+ attr_opt = attr_key == :reading ? :readers : :writers
229
+ result[:attribute_options] << attr_opt
262
230
  end
263
231
  result
264
232
  end
265
233
 
266
- protected
267
-
268
- attr_writer :join_points_matched, :join_points_not_matched, :specification, :candidate_types, :candidate_types_excluded, :candidate_objects, :candidate_join_points
269
-
270
- def init_type_specific_specification original_options, options_hash
271
- @specification.merge! Pointcut.make_attribute_reading_writing_options(options_hash)
234
+ # TODO remove duplication w/ aspect.rb
235
+ def finish_specification_initialization
236
+ @specification.merge! Pointcut.make_attribute_reading_writing_options(@original_options)
272
237
  # Map the method options to their canonical values:
273
238
  @specification[:method_options] = Aquarium::Finders::MethodFinder.init_method_options(@specification[:method_options])
274
- use_default_objects_if_defined unless (types_given? || objects_given?)
239
+ use_default_objects_if_defined unless any_type_related_options_given?
240
+ Pointcut::validate_attribute_options @specification, @original_options
241
+ init_methods_specification
242
+ end
243
+
244
+ def init_methods_specification
245
+ match_all_methods if ((no_methods_specified? and no_attributes_specified?) or all_methods_specified?)
246
+ end
275
247
 
276
- raise Aquarium::Utils::InvalidOptions.new(":all is not yet supported for :attributes.") if @specification[:attributes] == Set.new([:all])
248
+ def any_type_related_options_given?
249
+ objects_given? or join_points_given? or types_given? or types_and_descendents_given? or types_and_ancestors_given?
250
+ end
251
+
252
+ def self.validate_attribute_options spec_hash, options_hash
253
+ raise Aquarium::Utils::InvalidOptions.new(":all is not yet supported for :attributes.") if spec_hash[:attributes] == Set.new([:all])
277
254
  if options_hash[:reading] and (options_hash[:writing] or options_hash[:changing])
278
255
  unless options_hash[:reading].eql?(options_hash[:writing]) or options_hash[:reading].eql?(options_hash[:changing])
279
256
  raise Aquarium::Utils::InvalidOptions.new(":reading and :writing/:changing can only be used together if they refer to the same set of attributes.")
280
257
  end
281
258
  end
282
- init_methods_specification options_hash
283
- end
284
-
285
- def init_methods_specification options
286
- match_all_methods if ((no_methods_specified and no_attributes_specified) or all_methods_specified)
287
259
  end
260
+
261
+ protected
262
+
263
+ attr_writer :join_points_matched, :join_points_not_matched, :specification, :candidate_types, :candidate_types_excluded, :candidate_objects, :candidate_join_points
288
264
 
289
265
  def match_all_methods
290
266
  @specification[:methods] = Set.new([:all])
291
267
  end
292
268
 
293
- def no_methods_specified
269
+ def no_methods_specified?
294
270
  @specification[:methods].nil? or @specification[:methods].empty?
295
271
  end
296
272
 
297
- def all_methods_specified
273
+ def all_methods_specified?
298
274
  methods_spec = @specification[:methods].to_a
299
275
  methods_spec.include?(:all) or methods_spec.include?(:all_methods)
300
276
  end
301
277
 
302
- def no_attributes_specified
278
+ def no_attributes_specified?
303
279
  @specification[:attributes].nil? or @specification[:attributes].empty?
304
280
  end
305
281
 
@@ -387,7 +363,7 @@ module Aquarium
387
363
  Aquarium::Finders::MethodFinder.new.find type_or_object_sym => candidates.matched_keys,
388
364
  :methods => which_methods.to_a,
389
365
  :exclude_methods => @specification[:exclude_methods],
390
- :options => method_options
366
+ :method_options => method_options
391
367
  end
392
368
 
393
369
  def add_join_points search_results, type_or_object_sym