aquarium 0.4.1 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Aquarium-IDEA.ipr +252 -0
- data/Aquarium-IDEA.iws +493 -0
- data/Aquarium.ipr +1 -1
- data/Aquarium.iws +133 -138
- data/CHANGES +63 -0
- data/ParseTreePlay.rb +25 -0
- data/README +55 -3
- data/RELEASE-PLAN +9 -1
- data/TODO.rb +175 -15
- data/examples/aspect_design_example.rb +13 -1
- data/examples/aspect_design_example_spec.rb +20 -2
- data/examples/introductions_example.rb +35 -0
- data/examples/introductions_example_spec.rb +37 -0
- data/examples/method_missing_example.rb +2 -1
- data/lib/aquarium/aspects/advice.rb +127 -74
- data/lib/aquarium/aspects/aspect.rb +139 -72
- data/lib/aquarium/aspects/default_objects_handler.rb +6 -4
- data/lib/aquarium/aspects/exclusion_handler.rb +15 -3
- data/lib/aquarium/aspects/join_point.rb +60 -55
- data/lib/aquarium/aspects/pointcut.rb +153 -124
- data/lib/aquarium/aspects/pointcut_composition.rb +1 -1
- data/lib/aquarium/dsl/aspect_dsl.rb +13 -5
- data/lib/aquarium/dsl/object_dsl.rb +4 -2
- data/lib/aquarium/extras/design_by_contract.rb +9 -5
- data/lib/aquarium/finders.rb +1 -0
- data/lib/aquarium/finders/finder_result.rb +13 -5
- data/lib/aquarium/finders/method_finder.rb +75 -70
- data/lib/aquarium/finders/pointcut_finder.rb +166 -0
- data/lib/aquarium/finders/type_finder.rb +104 -62
- data/lib/aquarium/utils/array_utils.rb +1 -1
- data/lib/aquarium/utils/invalid_options.rb +2 -0
- data/lib/aquarium/utils/name_utils.rb +3 -2
- data/lib/aquarium/utils/nil_object.rb +7 -3
- data/lib/aquarium/utils/options_utils.rb +38 -27
- data/lib/aquarium/utils/set_utils.rb +2 -2
- data/lib/aquarium/utils/type_utils.rb +11 -0
- data/lib/aquarium/version.rb +1 -1
- data/spec/aquarium/aspects/advice_spec.rb +147 -32
- data/spec/aquarium/aspects/aspect_invocation_spec.rb +252 -43
- data/spec/aquarium/aspects/aspect_spec.rb +148 -88
- data/spec/aquarium/aspects/aspect_with_nested_types_spec.rb +40 -34
- data/spec/aquarium/aspects/aspect_with_subtypes_spec.rb +39 -3
- data/spec/aquarium/aspects/join_point_spec.rb +190 -227
- data/spec/aquarium/aspects/pointcut_spec.rb +24 -1
- data/spec/aquarium/dsl/aspect_dsl_spec.rb +17 -17
- data/spec/aquarium/finders/method_finder_spec.rb +8 -2
- data/spec/aquarium/finders/pointcut_finder_spec.rb +193 -0
- data/spec/aquarium/finders/pointcut_finder_spec_test_classes.rb +90 -0
- data/spec/aquarium/finders/type_finder_spec.rb +17 -0
- data/spec/aquarium/finders/type_finder_with_descendents_and_ancestors_spec.rb +4 -4
- data/spec/aquarium/finders/type_finder_with_nested_types.rb +47 -0
- data/spec/aquarium/utils/nil_object_spec.rb +21 -0
- data/spec/aquarium/utils/type_utils_sample_nested_types.rb +51 -0
- data/spec/aquarium/utils/type_utils_spec.rb +18 -1
- metadata +13 -3
@@ -31,10 +31,28 @@ end
|
|
31
31
|
|
32
32
|
include Aquarium::Aspects
|
33
33
|
|
34
|
-
|
34
|
+
# Two ways of referencing the pointcut are shown. The first assumes you know the particular
|
35
|
+
# pointcuts you care about. The second is more general; it uses the recently-introduced
|
36
|
+
# :named_pointcut feature to search for all pointcuts matching a name in a set of types.
|
37
|
+
describe "An example of an aspect referencing a particular class-defined pointcut." do
|
35
38
|
it "should observe state changes in the class." do
|
36
39
|
@new_state = nil
|
37
|
-
observer = Aspect.new :after,
|
40
|
+
observer = Aspect.new :after,
|
41
|
+
:pointcut => Aquarium::ClassWithStateAndBehavior::STATE_CHANGE do |jp, obj, *args|
|
42
|
+
@new_state = obj.state
|
43
|
+
@new_state.should be_eql(*args)
|
44
|
+
end
|
45
|
+
object = Aquarium::ClassWithStateAndBehavior.new(:a1, :a2, :a3)
|
46
|
+
object.state = [:b1, :b2]
|
47
|
+
@new_state.should == [:b1, :b2]
|
48
|
+
observer.unadvise
|
49
|
+
end
|
50
|
+
end
|
51
|
+
describe "An example of an aspect searching for class-defined pointcuts." do
|
52
|
+
it "should observe state changes in the class." do
|
53
|
+
@new_state = nil
|
54
|
+
observer = Aspect.new :after,
|
55
|
+
:named_pointcuts => {:matching => /CHANGE/, :within_types => Aquarium::ClassWithStateAndBehavior} do |jp, obj, *args|
|
38
56
|
@new_state = obj.state
|
39
57
|
@new_state.should be_eql(*args)
|
40
58
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
2
|
+
require 'aquarium'
|
3
|
+
|
4
|
+
module Aquarium
|
5
|
+
module TypeFinderIntroductionExampleTargetModule1
|
6
|
+
end
|
7
|
+
module TypeFinderIntroductionExampleTargetModule2
|
8
|
+
end
|
9
|
+
class TypeFinderIntroductionExampleTargetClass1
|
10
|
+
end
|
11
|
+
class TypeFinderIntroductionExampleTargetClass2
|
12
|
+
end
|
13
|
+
module TypeFinderIntroductionExampleModule
|
14
|
+
def introduced_method; end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
include Aquarium::Finders
|
19
|
+
|
20
|
+
# First, find the types
|
21
|
+
|
22
|
+
found = TypeFinder.new.find :types => /Aquarium::TypeFinderIntroductionExampleTarget/
|
23
|
+
|
24
|
+
# Now, iterate through them and "extend" them with the module defining the new behavior.
|
25
|
+
|
26
|
+
found.each {|t| t.extend Aquarium::TypeFinderIntroductionExampleModule }
|
27
|
+
|
28
|
+
# See if the "introduced" modules's method is there.
|
29
|
+
|
30
|
+
[Aquarium::TypeFinderIntroductionExampleTargetModule1,
|
31
|
+
Aquarium::TypeFinderIntroductionExampleTargetModule2,
|
32
|
+
Aquarium::TypeFinderIntroductionExampleTargetClass1,
|
33
|
+
Aquarium::TypeFinderIntroductionExampleTargetClass2].each do |t|
|
34
|
+
p "type #{t}, method there? #{t.methods.include?("introduced_method")}"
|
35
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec/aquarium/spec_helper'
|
2
|
+
require 'aquarium'
|
3
|
+
|
4
|
+
# Example demonstrating how to use the TypeFinder class to convenient "introduce" new
|
5
|
+
# methods and attributes in a set of types, like you might do with AspectJ in Java.
|
6
|
+
# Of course, in Ruby, you can simply use Object#extend(module). However, if you want to
|
7
|
+
# do this in a cross-cutting way, TypeFinder. is convenient.
|
8
|
+
|
9
|
+
module Aquarium
|
10
|
+
module TypeFinderIntroductionExampleTargetModule1
|
11
|
+
end
|
12
|
+
module TypeFinderIntroductionExampleTargetModule2
|
13
|
+
end
|
14
|
+
class TypeFinderIntroductionExampleTargetClass1
|
15
|
+
end
|
16
|
+
class TypeFinderIntroductionExampleTargetClass2
|
17
|
+
end
|
18
|
+
module TypeFinderIntroductionExampleModule
|
19
|
+
def introduced_method; end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# include Aquarium::Finders
|
24
|
+
|
25
|
+
describe "Using TypeFinder to introduce modules in a set of other types" do
|
26
|
+
it "should extend each found type with the specified module if you use the finder result #each method" do
|
27
|
+
found = Aquarium::Finders::TypeFinder.new.find :types => /Aquarium::TypeFinderIntroductionExampleTarget/
|
28
|
+
found.each {|t| t.extend Aquarium::TypeFinderIntroductionExampleModule }
|
29
|
+
[Aquarium::TypeFinderIntroductionExampleTargetModule1,
|
30
|
+
Aquarium::TypeFinderIntroductionExampleTargetModule2,
|
31
|
+
Aquarium::TypeFinderIntroductionExampleTargetClass1,
|
32
|
+
Aquarium::TypeFinderIntroductionExampleTargetClass2].each do |t|
|
33
|
+
t.methods.should include('introduced_method')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -31,7 +31,8 @@ echo1.say "hello", "world!"
|
|
31
31
|
echo1.log "something", "interesting..."
|
32
32
|
echo1.shout "theater", "in", "a", "crowded", "firehouse!"
|
33
33
|
|
34
|
-
Aquarium::Aspects::Aspect.new :around,
|
34
|
+
Aquarium::Aspects::Aspect.new :around,
|
35
|
+
:calls_to => :method_missing, :for_type => Aquarium::Echo, do |join_point, obj, sym, *args|
|
35
36
|
if sym == :log
|
36
37
|
p "--- Sending to log: #{args.join(" ")}"
|
37
38
|
else
|
@@ -8,6 +8,8 @@ module Aquarium
|
|
8
8
|
module Aspects
|
9
9
|
module Advice
|
10
10
|
|
11
|
+
UNKNOWN_ADVICE_KIND = "unknown"
|
12
|
+
|
11
13
|
KINDS_IN_PRIORITY_ORDER = [:around, :before, :after, :after_returning, :after_raising]
|
12
14
|
|
13
15
|
def self.kinds; KINDS_IN_PRIORITY_ORDER; end
|
@@ -17,6 +19,19 @@ module Aquarium
|
|
17
19
|
KINDS_IN_PRIORITY_ORDER.index(x.to_sym) <=> KINDS_IN_PRIORITY_ORDER.index(y.to_sym)
|
18
20
|
end.map {|x| x.to_sym}
|
19
21
|
end
|
22
|
+
|
23
|
+
def self.compare_advice_kinds kind1, kind2
|
24
|
+
if kind1.nil?
|
25
|
+
return kind2.nil? ? 0 : -1
|
26
|
+
end
|
27
|
+
return 1 if kind2.nil?
|
28
|
+
if kind1.eql?(UNKNOWN_ADVICE_KIND)
|
29
|
+
return kind2.eql?(UNKNOWN_ADVICE_KIND) ? 0 : -1
|
30
|
+
else
|
31
|
+
return kind2.eql?(UNKNOWN_ADVICE_KIND) ? 1 : KINDS_IN_PRIORITY_ORDER.index(kind1) <=> KINDS_IN_PRIORITY_ORDER.index(kind2)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
20
35
|
end
|
21
36
|
|
22
37
|
# Supports Enumerable, but not the sorting methods, as this class is a linked list structure.
|
@@ -25,9 +40,7 @@ module Aquarium
|
|
25
40
|
# in the case of around advice!
|
26
41
|
class AdviceChainNode
|
27
42
|
include Enumerable
|
28
|
-
def initialize options = {}
|
29
|
-
raise Aquarium::Utils::InvalidOptions.new("You must specify an advice block or Proc") if proc_block.nil?
|
30
|
-
@proc = Proc.new &proc_block
|
43
|
+
def initialize options = {}
|
31
44
|
# assign :next_node and :static_join_point so the attributes are always created
|
32
45
|
options[:next_node] ||= nil
|
33
46
|
options[:static_join_point] ||= nil
|
@@ -37,38 +50,32 @@ module Aquarium
|
|
37
50
|
attr_accessor(:#{key})
|
38
51
|
EOF
|
39
52
|
end
|
53
|
+
end
|
40
54
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
55
|
+
# Bug #19262 workaround: need to only pass jp argument if arity is 1.
|
56
|
+
def call_advice jp
|
57
|
+
if advice.arity == 1
|
58
|
+
advice.call jp
|
59
|
+
else
|
60
|
+
advice.call jp, jp.context.advised_object, *jp.context.parameters
|
48
61
|
end
|
49
62
|
end
|
50
63
|
|
51
|
-
def call jp
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
64
|
+
def call jp
|
65
|
+
begin
|
66
|
+
advice_wrapper jp
|
67
|
+
rescue Exception => e
|
68
|
+
handle_call_rescue e, "", jp
|
69
|
+
end
|
57
70
|
end
|
58
71
|
|
59
|
-
def
|
72
|
+
def invoke_original_join_point current_jp
|
60
73
|
begin
|
61
|
-
|
62
|
-
rescue => e
|
63
|
-
|
64
|
-
context_message = error_message_prefix + "Exception raised while executing \"#{jp.context.advice_kind}\" advice for \"#{jp.type_or_object.inspect}#{class_or_instance_method_separater}#{jp.method_name}\": "
|
65
|
-
backtrace = e.backtrace
|
66
|
-
e2 = e.exception(context_message + e.message + " (join_point = #{jp.inspect})")
|
67
|
-
e2.set_backtrace backtrace
|
68
|
-
raise e2
|
74
|
+
last.advice_wrapper current_jp
|
75
|
+
rescue Exception => e
|
76
|
+
handle_call_rescue e, "While executing the original join_point: ", current_jp
|
69
77
|
end
|
70
78
|
end
|
71
|
-
protected :do_call
|
72
79
|
|
73
80
|
# Supports Enumerable
|
74
81
|
def each
|
@@ -101,8 +108,30 @@ module Aquarium
|
|
101
108
|
|
102
109
|
protected
|
103
110
|
|
104
|
-
|
105
|
-
|
111
|
+
#--
|
112
|
+
# For performance reasons, we don't clone the context.
|
113
|
+
# TODO: There are potential concurrency issues!
|
114
|
+
#++
|
115
|
+
def update_current_context jp
|
116
|
+
return if advice.arity == 0
|
117
|
+
@last_advice_kind = jp.context.advice_kind
|
118
|
+
@last_advice_node = jp.context.current_advice_node
|
119
|
+
jp.context.current_advice_node = self
|
120
|
+
end
|
121
|
+
|
122
|
+
def reset_current_context jp
|
123
|
+
return if advice.arity == 0
|
124
|
+
jp.context.advice_kind = @last_advice_kind
|
125
|
+
jp.context.current_advice_node = @last_advice_node
|
126
|
+
end
|
127
|
+
|
128
|
+
def handle_call_rescue ex, error_message_prefix, jp
|
129
|
+
class_or_instance_method_separater = jp.instance_method? ? "#" : "."
|
130
|
+
context_message = error_message_prefix + "Exception raised while executing \"#{jp.context.advice_kind}\" advice for \"#{jp.type_or_object.inspect}#{class_or_instance_method_separater}#{jp.method_name}\": "
|
131
|
+
backtrace = ex.backtrace
|
132
|
+
e2 = ex.exception(context_message + ex.message + " (join_point = #{jp.inspect})")
|
133
|
+
e2.set_backtrace backtrace
|
134
|
+
raise e2
|
106
135
|
end
|
107
136
|
end
|
108
137
|
|
@@ -113,33 +142,39 @@ module Aquarium
|
|
113
142
|
# Note that we extract the block passed to the original method call, if any,
|
114
143
|
# from the context and pass it to method invocation.
|
115
144
|
def initialize options = {}
|
116
|
-
super
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
invoking_object(jp).send(@alias_method_name, *args, &block_for_method)
|
121
|
-
}
|
145
|
+
super options
|
146
|
+
end
|
147
|
+
def advice_wrapper jp
|
148
|
+
jp.context.advised_object.send @alias_method_name, *jp.context.parameters, &jp.context.block_for_method
|
122
149
|
end
|
123
150
|
end
|
124
151
|
|
125
152
|
class BeforeAdviceChainNode < AdviceChainNode
|
126
153
|
def initialize options = {}
|
127
|
-
super
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
154
|
+
super options
|
155
|
+
end
|
156
|
+
def advice_wrapper jp
|
157
|
+
update_current_context jp
|
158
|
+
jp.context.advice_kind = :before
|
159
|
+
call_advice jp
|
160
|
+
reset_current_context jp
|
161
|
+
next_node.call jp
|
132
162
|
end
|
133
163
|
end
|
134
164
|
|
135
165
|
class AfterReturningAdviceChainNode < AdviceChainNode
|
136
166
|
def initialize options = {}
|
137
|
-
super
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
167
|
+
super options
|
168
|
+
end
|
169
|
+
def advice_wrapper jp
|
170
|
+
returned_value = next_node.call jp
|
171
|
+
update_current_context jp
|
172
|
+
jp.context.advice_kind = :after_returning
|
173
|
+
jp.context.returned_value = returned_value
|
174
|
+
call_advice jp
|
175
|
+
result = jp.context.returned_value # allow advice to modify the returned value
|
176
|
+
reset_current_context jp
|
177
|
+
result
|
143
178
|
end
|
144
179
|
end
|
145
180
|
|
@@ -148,18 +183,22 @@ module Aquarium
|
|
148
183
|
class AfterRaisingAdviceChainNode < AdviceChainNode
|
149
184
|
include Aquarium::Utils::ArrayUtils
|
150
185
|
def initialize options = {}
|
151
|
-
super
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
186
|
+
super options
|
187
|
+
end
|
188
|
+
def advice_wrapper jp
|
189
|
+
begin
|
190
|
+
next_node.call jp
|
191
|
+
rescue Object => raised_exception
|
192
|
+
if after_raising_exceptions_list_includes raised_exception
|
193
|
+
update_current_context jp
|
194
|
+
jp.context.advice_kind = :after_raising
|
195
|
+
jp.context.raised_exception = raised_exception
|
196
|
+
call_advice jp
|
197
|
+
raised_exception = jp.context.raised_exception # allow advice to modify the raised exception
|
198
|
+
reset_current_context jp
|
161
199
|
end
|
162
|
-
|
200
|
+
raise raised_exception
|
201
|
+
end
|
163
202
|
end
|
164
203
|
|
165
204
|
private
|
@@ -175,29 +214,43 @@ module Aquarium
|
|
175
214
|
|
176
215
|
class AfterAdviceChainNode < AdviceChainNode
|
177
216
|
def initialize options = {}
|
178
|
-
super
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
217
|
+
super options
|
218
|
+
end
|
219
|
+
def advice_wrapper jp
|
220
|
+
# call_advice is invoked in each bloc, rather than once in an "ensure" clause, so the invocation in
|
221
|
+
# the rescue clause can allow the advice to change the exception that will be raised.
|
222
|
+
begin
|
223
|
+
returned_value = next_node.call jp
|
224
|
+
update_current_context jp
|
225
|
+
jp.context.advice_kind = :after
|
226
|
+
jp.context.returned_value = returned_value
|
227
|
+
call_advice jp
|
228
|
+
result = jp.context.returned_value # allow advice to modify the returned value
|
229
|
+
reset_current_context jp
|
230
|
+
result
|
231
|
+
rescue Object => raised_exception
|
232
|
+
update_current_context jp
|
233
|
+
jp.context.advice_kind = :after
|
234
|
+
jp.context.raised_exception = raised_exception
|
235
|
+
call_advice jp
|
236
|
+
raised_exception = jp.context.raised_exception # allow advice to modify the raised exception
|
237
|
+
reset_current_context jp
|
238
|
+
raise raised_exception
|
239
|
+
end
|
192
240
|
end
|
193
241
|
end
|
194
242
|
|
195
243
|
class AroundAdviceChainNode < AdviceChainNode
|
196
244
|
def initialize options = {}
|
197
|
-
super
|
198
|
-
|
199
|
-
|
200
|
-
|
245
|
+
super options
|
246
|
+
end
|
247
|
+
def advice_wrapper jp
|
248
|
+
update_current_context jp
|
249
|
+
jp.context.advice_kind = :around
|
250
|
+
jp.context.proceed_proc = next_node
|
251
|
+
result = call_advice jp
|
252
|
+
reset_current_context jp
|
253
|
+
result
|
201
254
|
end
|
202
255
|
end
|
203
256
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'aquarium/extensions'
|
2
|
-
require 'aquarium/finders
|
2
|
+
require 'aquarium/finders'
|
3
3
|
require 'aquarium/utils'
|
4
4
|
require 'aquarium/aspects/advice'
|
5
5
|
require 'aquarium/aspects/exclusion_handler'
|
@@ -12,14 +12,14 @@ module Aquarium
|
|
12
12
|
module Aspects
|
13
13
|
|
14
14
|
# == Aspect
|
15
|
-
#
|
15
|
+
# Aspect "advises" one or more method invocations for one or more types or objects
|
16
16
|
# (including class methods on types). The corresponding advice is a Proc that is
|
17
17
|
# invoked either before the join point, after it returns, after it raises an exception,
|
18
18
|
# after either event, or around the join point, meaning the advice runs and it decides
|
19
19
|
# when and if to invoke the advised method. (Hence, around advice can run code before
|
20
20
|
# and after the join point call and it can "veto" the actual join point call).
|
21
21
|
#
|
22
|
-
# See also Aquarium::
|
22
|
+
# See also Aquarium::DSL for more information.
|
23
23
|
class Aspect
|
24
24
|
include Advice
|
25
25
|
include ExclusionHandler
|
@@ -35,27 +35,31 @@ module Aquarium
|
|
35
35
|
ASPECT_CANONICAL_OPTIONS = {
|
36
36
|
"advice" => %w[action do_action use_advice advise_with invoke call],
|
37
37
|
"pointcuts" => %w[pointcut],
|
38
|
+
"named_pointcuts" => %w[named_pointcut],
|
38
39
|
"exceptions" => %w[exception],
|
39
40
|
"ignore_no_matching_join_points" => %[ignore_no_jps]
|
40
41
|
}
|
41
|
-
|
42
|
-
|
42
|
+
["pointcuts", "named_pointcuts"].each do |pc_option|
|
43
|
+
add_prepositional_option_variants_for pc_option, ASPECT_CANONICAL_OPTIONS
|
44
|
+
add_exclude_options_for pc_option, ASPECT_CANONICAL_OPTIONS
|
45
|
+
end
|
43
46
|
CANONICAL_OPTIONS = Pointcut::CANONICAL_OPTIONS.merge ASPECT_CANONICAL_OPTIONS
|
44
47
|
|
45
48
|
canonical_options_given_methods CANONICAL_OPTIONS
|
46
49
|
|
47
50
|
|
48
|
-
# Aspect.new (:around | :before | :after | :after_returning | :after_raising )
|
49
|
-
# (:pointcuts => [...]), |
|
50
|
-
# ((:types => [...] | :types_and_ancestors => [...] | :types_and_descendents => [...]
|
51
|
-
# :objects => [...]),
|
52
|
-
# :methods => [], :method_options => [...],
|
53
|
-
# :attributes => [...], :attribute_options[...]),
|
51
|
+
# Aspect.new (:around | :before | :after | :after_returning | :after_raising )
|
52
|
+
# (:pointcuts => [...]), :named_pointcuts => [...] |
|
53
|
+
# ((:types => [...] | :types_and_ancestors => [...] | :types_and_descendents => [...]
|
54
|
+
# | :types_and_nested_types | :objects => [...]),
|
55
|
+
# :methods => [], :method_options => [...],
|
56
|
+
# :attributes => [...], :attribute_options[...]),
|
54
57
|
# (:advice = advice | do |join_point, obj, *args| ...; end)
|
55
58
|
#
|
56
59
|
# where the parameters often have many synonyms (mostly to support a "humane
|
57
60
|
# interface") and they are interpreted as followed:
|
58
61
|
#
|
62
|
+
# ==== Type of Advice
|
59
63
|
# <tt>:around</tt>::
|
60
64
|
# Invoke the specified advice "around" the join points. It is up to the advice
|
61
65
|
# itself to call <tt>join_point.proceed</tt> (where <tt>join_point</tt> is the
|
@@ -73,36 +77,64 @@ module Aquarium
|
|
73
77
|
# Invoke the specified advice after the join point returns successfully.
|
74
78
|
#
|
75
79
|
# <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>::
|
78
80
|
# Invoke the specified advice after the join point raises one of the specified exceptions.
|
79
|
-
# If no exceptions are specified, the advice is invoked after any exception is raised.
|
81
|
+
# If no exceptions are specified, the advice is invoked after any exception is raised. An
|
82
|
+
# alternative syntax is <tt>:after_raising, :exception[s] => (exception || [exception_list])</tt>.
|
83
|
+
#
|
84
|
+
# ==== Advice
|
85
|
+
# The advice to invoke before, after, or around the join points. Only one advice may be specified.
|
86
|
+
# If a block is specified, the following options are ignored.
|
87
|
+
# * <tt>:advice => proc</tt>
|
88
|
+
# * <tt>:action => proc</tt>
|
89
|
+
# * <tt>:do_action => proc</tt>
|
90
|
+
# * <tt>:use_advice => proc</tt>
|
91
|
+
# * <tt>:advise_with => proc</tt>
|
92
|
+
# * <tt>:invoke => proc</tt>
|
93
|
+
# * <tt>:call => proc</tt>
|
94
|
+
#
|
95
|
+
# ==== Pointcuts
|
96
|
+
# A Pointcut, JoinPoint, or array of the same (Mixed is allowed.). Specifying pointcuts
|
97
|
+
# is mutually-exclusive with specifying them "indirectly" through :types, :objects,
|
98
|
+
# :methods, :attributes, :method_options, and :attribute_options parameters.
|
99
|
+
# * <tt>:pointcuts => pointcut || [pointcut_list]</tt>
|
100
|
+
# * <tt>:pointcut => pointcut || [pointcut_list]</tt>
|
101
|
+
# * <tt>:on_pointcut => pointcut || [pointcut_list]</tt>
|
102
|
+
# * <tt>:on_pointcuts => pointcut || [pointcut_list]</tt>
|
103
|
+
# * <tt>:in_pointcut => pointcut || [pointcut_list]</tt>
|
104
|
+
# * <tt>:in_pointcuts => pointcut || [pointcut_list]</tt>
|
105
|
+
# * <tt>:within_pointcut => pointcut || [pointcut_list]</tt>
|
106
|
+
# * <tt>:within_pointcuts => pointcut || [pointcut_list]</tt>
|
80
107
|
#
|
81
|
-
#
|
82
|
-
#
|
83
|
-
# <tt
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
87
|
-
# <tt>:
|
88
|
-
#
|
89
|
-
#
|
108
|
+
# ==== Named Pointcuts
|
109
|
+
# Specify search criteria to locate Pointcuts defined as class constants and/or class variables.
|
110
|
+
# The options for the <tt>named_pointcuts</tt> parameter must form a hash and satisfy the
|
111
|
+
# requirements documented for Aquarium::Finders::PointcutFinder#find.
|
112
|
+
# Specifying named pointcuts is also mutually-exclusive with specifying Pointcuts "indirectly"
|
113
|
+
# through :types, :objects, :methods, :attributes, :method_options, and :attribute_options parameters.
|
114
|
+
# * <tt>:named_pointcuts => {PointcutFinder options}</tt>
|
115
|
+
# * <tt>:named_pointcut => {PointcutFinder options}</tt>
|
116
|
+
# * <tt>:on_named_pointcuts => {PointcutFinder options}</tt>
|
117
|
+
# * <tt>:on_named_pointcut => {PointcutFinder options}</tt>
|
118
|
+
# * <tt>:in_named_pointcuts => {PointcutFinder options}</tt>
|
119
|
+
# * <tt>:in_named_pointcut => {PointcutFinder options}</tt>
|
120
|
+
# * <tt>:within_named_pointcuts => {PointcutFinder options}</tt>
|
121
|
+
# * <tt>:within_named_pointcut => {PointcutFinder options}</tt>
|
90
122
|
#
|
91
|
-
#
|
92
|
-
# <tt
|
93
|
-
# <tt>:
|
94
|
-
# <tt>:
|
95
|
-
#
|
96
|
-
# :methods, :attributes, :method_options, and :attribute_options parameters.
|
123
|
+
# ==== Exclude Pointcuts
|
124
|
+
# Exclude the specified pointcuts. The <tt>exclude_</tt> prefix can be used with any of the
|
125
|
+
# <tt>:pointcuts</tt> and <tt>:named_pointcuts</tt> synonyms.
|
126
|
+
# * <tt>:exclude_pointcuts => pointcut || [pointcut_list]</tt>
|
127
|
+
# * <tt>:exclude_named_pointcuts => {PointcutFinder options}</tt>
|
97
128
|
#
|
98
|
-
#
|
99
|
-
# <tt
|
129
|
+
# ==== Miscellaneous Options
|
130
|
+
# <tt>:ignore_no_matching_join_points => true | false</tt>::
|
100
131
|
# Do not issue a warning if no join points are actually matched by the aspect. By default, the value
|
101
132
|
# is false, meaning that a WARN-level message will be written to the log. It is usually very helpful
|
102
|
-
# to be warned when no matches occurred, for
|
133
|
+
# to be warned when no matches occurred, for debugging purposes! A synonym for this option is
|
134
|
+
# <tt>ignore_no_jps => true | false</tt>.
|
103
135
|
#
|
104
|
-
#
|
105
|
-
#
|
136
|
+
# For other options <i>e.g.,</i> <tt>:types</tt>, <tt>:methods</tt>, Aspect#new accepts all the options
|
137
|
+
# that Pointcut#new accepts. It also accepts the "universal" options documented in Aquarium::Utils::OptionsUtils.
|
106
138
|
def initialize *options, &block
|
107
139
|
@first_option_that_was_method = []
|
108
140
|
opts = rationalize options
|
@@ -149,7 +181,7 @@ module Aquarium
|
|
149
181
|
|
150
182
|
alias :== :eql?
|
151
183
|
|
152
|
-
|
184
|
+
private
|
153
185
|
|
154
186
|
def rationalize options
|
155
187
|
return {} if options.nil? or options.empty?
|
@@ -194,7 +226,7 @@ module Aquarium
|
|
194
226
|
|
195
227
|
def calculate_excluded_types
|
196
228
|
type_finder_options = {}
|
197
|
-
%w[types types_and_ancestors types_and_descendents].each do |opt|
|
229
|
+
%w[types types_and_ancestors types_and_descendents types_and_nested_types].each do |opt|
|
198
230
|
type_finder_options[opt.intern] = @specification["exclude_#{opt}".intern] if @specification["exclude_#{opt}".intern]
|
199
231
|
end
|
200
232
|
excluded_types = Aquarium::Finders::TypeFinder.new.find type_finder_options
|
@@ -220,18 +252,10 @@ module Aquarium
|
|
220
252
|
end
|
221
253
|
|
222
254
|
def init_pointcuts
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
pointcuts << pointcut
|
228
|
-
elsif pointcut.kind_of? JoinPoint
|
229
|
-
pointcuts << Pointcut.new(:join_point => pointcut)
|
230
|
-
else # a hash of Pointcut.new options?
|
231
|
-
pointcuts << Pointcut.new(pointcut)
|
232
|
-
end
|
233
|
-
end
|
234
|
-
else
|
255
|
+
set_calculated_excluded_pointcuts determine_excluded_pointcuts
|
256
|
+
pointcuts = determine_specified_pointcuts
|
257
|
+
pointcuts += determine_named_pointcuts
|
258
|
+
if pointcuts.empty? # If no PCs specified, then the user must have specified :types, ...
|
235
259
|
pc_options = {}
|
236
260
|
Pointcut::CANONICAL_OPTIONS.keys.each do |pc_option|
|
237
261
|
pco_sym = pc_option.intern
|
@@ -239,10 +263,38 @@ module Aquarium
|
|
239
263
|
end
|
240
264
|
pointcuts << Pointcut.new(pc_options)
|
241
265
|
end
|
242
|
-
@pointcuts = Set.new(
|
266
|
+
@pointcuts = Set.new(remove_excluded_join_points_and_pointcuts(pointcuts))
|
243
267
|
warn_if_no_join_points_matched
|
244
268
|
end
|
245
269
|
|
270
|
+
def determine_specified_pointcuts
|
271
|
+
pointcuts_given.inject([]) do |pointcuts, pointcut|
|
272
|
+
if pointcut.kind_of? Pointcut
|
273
|
+
pointcuts << pointcut
|
274
|
+
elsif pointcut.kind_of? JoinPoint
|
275
|
+
pointcuts << Pointcut.new(:join_point => pointcut)
|
276
|
+
else # a hash of Pointcut.new options?
|
277
|
+
pointcuts << Pointcut.new(pointcut)
|
278
|
+
end
|
279
|
+
pointcuts
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def determine_named_pointcuts
|
284
|
+
named_pointcuts_given.inject([]) do |pointcuts, pointcut_spec|
|
285
|
+
found_pointcuts_results = Aquarium::Finders::PointcutFinder.new.find(pointcut_spec)
|
286
|
+
pointcuts += found_pointcuts_results.found_pointcuts
|
287
|
+
pointcuts
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def determine_excluded_pointcuts
|
292
|
+
exclude_named_pointcuts_given.inject([]) do |excluded_pointcuts, pointcut_spec|
|
293
|
+
found_pointcuts_results = Aquarium::Finders::PointcutFinder.new.find(pointcut_spec)
|
294
|
+
excluded_pointcuts += found_pointcuts_results.found_pointcuts
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
246
298
|
def warn_if_no_join_points_matched
|
247
299
|
return unless should_warn_if_no_matching_join_points
|
248
300
|
@pointcuts.each do |pc|
|
@@ -261,7 +313,7 @@ module Aquarium
|
|
261
313
|
@specification[:ignore_no_matching_join_points].to_a.first == false
|
262
314
|
end
|
263
315
|
|
264
|
-
def
|
316
|
+
def remove_excluded_join_points_and_pointcuts pointcuts
|
265
317
|
pointcuts.reject do |pc|
|
266
318
|
pc.join_points_matched.delete_if do |jp|
|
267
319
|
join_point_excluded? jp
|
@@ -293,13 +345,15 @@ module Aquarium
|
|
293
345
|
end
|
294
346
|
|
295
347
|
def add_advice_to_chain join_point, advice_kind, advice
|
348
|
+
# Note that we use the same static join point object throughout the chain. It is
|
349
|
+
# equal to the passed-in join_point, except for additional context information.
|
296
350
|
start_of_advice_chain = Aspect.get_advice_chain join_point
|
297
351
|
options = @specification.merge({
|
298
352
|
:aspect => self,
|
299
353
|
:advice_kind => advice_kind,
|
300
354
|
:advice => advice,
|
301
355
|
:next_node => start_of_advice_chain,
|
302
|
-
:static_join_point =>
|
356
|
+
:static_join_point => start_of_advice_chain.static_join_point})
|
303
357
|
# New node is new start of chain.
|
304
358
|
Aspect.set_advice_chain(join_point, AdviceChainNodeFactory.make_node(options))
|
305
359
|
end
|
@@ -330,22 +384,31 @@ module Aquarium
|
|
330
384
|
type_to_advise = Aspect.type_to_advise_for join_point
|
331
385
|
# Note: Must set advice chain, a class variable on the type we're advising, FIRST.
|
332
386
|
# Otherwise the class_eval that follows will assume the @@ advice chain belongs to Aspect!
|
333
|
-
|
387
|
+
static_join_point = make_static_join_point join_point
|
388
|
+
advice_chain = AdviceChainNodeFactory.make_node(
|
334
389
|
:aspect => nil, # Belongs to all aspects that might advise this join point!
|
335
390
|
:advice_kind => :none,
|
336
391
|
:alias_method_name => alias_method_name,
|
337
|
-
:static_join_point =>
|
338
|
-
|
339
|
-
|
392
|
+
:static_join_point => static_join_point)
|
393
|
+
advice_chain_attr_sym = Aspect.make_advice_chain_attr_sym join_point
|
394
|
+
Aspect.set_advice_chain static_join_point, advice_chain
|
395
|
+
type_being_advised_text = static_join_point.instance_method? ? "self.class" : "self"
|
396
|
+
unless Aspect.is_type_join_point?(static_join_point)
|
340
397
|
type_being_advised_text = "(class << self; self; end)"
|
341
398
|
end
|
342
|
-
|
343
|
-
|
344
|
-
#{def_eigenclass_method_text
|
345
|
-
#{alias_original_method_text alias_method_name,
|
399
|
+
metatype_to_advise = static_join_point.instance_method? ? type_to_advise : (class << type_to_advise; self; end)
|
400
|
+
metatype_to_advise.class_eval(<<-EOF, __FILE__, __LINE__)
|
401
|
+
#{def_eigenclass_method_text static_join_point}
|
402
|
+
#{alias_original_method_text alias_method_name, static_join_point, type_being_advised_text}
|
346
403
|
EOF
|
347
404
|
end
|
348
405
|
|
406
|
+
def make_static_join_point join_point
|
407
|
+
static_jp = join_point.dup
|
408
|
+
static_jp.context.advice_kind = advice_kinds_given
|
409
|
+
static_jp
|
410
|
+
end
|
411
|
+
|
349
412
|
# When advising an instance, create an override method that gets advised instead of the types method.
|
350
413
|
# Otherwise, all objects will be advised!
|
351
414
|
# Note: this also solves bug #15202.
|
@@ -368,6 +431,13 @@ module Aquarium
|
|
368
431
|
join_point.target_type ? join_point.target_type : (class << join_point.target_object; self; end)
|
369
432
|
end
|
370
433
|
|
434
|
+
#--
|
435
|
+
# By NOT dup'ing the join_point, we save about 25% on the overhead! However, we
|
436
|
+
# compromise thread safety, primarily because the join_point's context object will be changed.
|
437
|
+
# TODO Refactor context out of static join point part.
|
438
|
+
# Note that we have to assign the parameters and block to the context object in case
|
439
|
+
# the advice calls "proceed" or "invoke_original_join_point" without arguments.
|
440
|
+
#++
|
371
441
|
def alias_original_method_text alias_method_name, join_point, type_being_advised_text
|
372
442
|
target_self = join_point.instance_method? ? "self" : join_point.target_type.name
|
373
443
|
advice_chain_attr_sym = Aspect.make_advice_chain_attr_sym join_point
|
@@ -375,13 +445,11 @@ module Aquarium
|
|
375
445
|
alias_method :#{alias_method_name}, :#{join_point.method_name}
|
376
446
|
def #{join_point.method_name} *args, &block_for_method
|
377
447
|
advice_chain = #{type_being_advised_text}.send :class_variable_get, "#{advice_chain_attr_sym}"
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
:block_for_method => block_for_method)
|
384
|
-
advice_chain.call advice_join_point, #{target_self}, *args
|
448
|
+
join_point = advice_chain.static_join_point
|
449
|
+
join_point.context.parameters = args
|
450
|
+
join_point.context.block_for_method = block_for_method
|
451
|
+
join_point.context.advised_object = #{target_self}
|
452
|
+
advice_chain.call join_point
|
385
453
|
end
|
386
454
|
#{join_point.visibility.to_s} :#{join_point.method_name}
|
387
455
|
private :#{alias_method_name}
|
@@ -437,7 +505,6 @@ module Aquarium
|
|
437
505
|
EOF
|
438
506
|
end
|
439
507
|
|
440
|
-
# TODO optimize calls to these *_advice_chain methods from other private methods.
|
441
508
|
def self.advice_chain_exists? join_point
|
442
509
|
advice_chain_attr_sym = self.make_advice_chain_attr_sym join_point
|
443
510
|
type_to_advise_for(join_point).class_variable_defined? advice_chain_attr_sym
|
@@ -481,11 +548,11 @@ module Aquarium
|
|
481
548
|
end
|
482
549
|
|
483
550
|
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?
|
551
|
+
pointcuts_given? or named_pointcuts_given? or join_points_given? or some_type_option_given? or objects_given?
|
485
552
|
end
|
486
553
|
|
487
554
|
def some_type_option_given?
|
488
|
-
types_given? or types_and_ancestors_given? or types_and_descendents_given?
|
555
|
+
types_given? or types_and_ancestors_given? or types_and_descendents_given? or types_and_nested_types_given?
|
489
556
|
end
|
490
557
|
|
491
558
|
def self.determine_type_or_object join_point
|
@@ -515,10 +582,10 @@ module Aquarium
|
|
515
582
|
bad_options(":after_returning can't be used with :after_raising.") if options_given? :after_returning, :after_raising
|
516
583
|
bad_options(":exceptions can't be specified except with :after_raising.") if exceptions_given? and not specified_advice_kinds.include?(:after_raising)
|
517
584
|
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.")
|
585
|
+
bad_options("At least one of :pointcut(s), :named_pointcut(s), :join_point(s), :type(s), :type(s)_and_ancestors, :type(s)_and_descendents, :type(s)_and_nested_types, or :object(s) is required.")
|
519
586
|
end
|
520
|
-
if pointcuts_given? and (some_type_option_given? or objects_given?)
|
521
|
-
bad_options("Can't specify both :pointcut(s) and one or more of :type(s), and/or :object(s).")
|
587
|
+
if (pointcuts_given? or named_pointcuts_given?) and (some_type_option_given? or objects_given?)
|
588
|
+
bad_options("Can't specify both :pointcut(s) or :named_pointcut(s) and one or more of :type(s), and/or :object(s).")
|
522
589
|
end
|
523
590
|
unless noop
|
524
591
|
if (not @specification[:advice].nil?) && @specification[:advice].size > 1
|