aquarium 0.4.1 → 0.4.2
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.
- 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
|