aquarium 0.1.6 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +18 -0
- data/README +68 -39
- data/RELEASE-PLAN +25 -1
- data/UPGRADE +4 -0
- data/examples/aspect_design_example.rb +9 -3
- data/examples/aspect_design_example_spec.rb +7 -2
- data/examples/method_tracing_example.rb +1 -1
- data/examples/method_tracing_example_spec.rb +2 -2
- data/lib/aquarium/aspects/aspect.rb +53 -60
- data/lib/aquarium/aspects/dsl/aspect_dsl.rb +0 -1
- data/lib/aquarium/aspects/pointcut.rb +72 -17
- data/lib/aquarium/aspects/pointcut_composition.rb +4 -1
- data/lib/aquarium/extensions/hash.rb +65 -28
- data/lib/aquarium/extensions/set.rb +2 -0
- data/lib/aquarium/finders/finder_result.rb +13 -2
- data/lib/aquarium/finders/method_finder.rb +54 -28
- data/lib/aquarium/finders/type_finder.rb +36 -19
- data/lib/aquarium/utils/method_utils.rb +3 -12
- data/lib/aquarium/utils/name_utils.rb +27 -1
- data/lib/aquarium/version.rb +1 -1
- data/spec/aquarium/aspects/aspect_invocation_spec.rb +182 -51
- data/spec/aquarium/aspects/aspect_spec.rb +43 -8
- data/spec/aquarium/aspects/pointcut_and_composition_spec.rb +32 -3
- data/spec/aquarium/aspects/pointcut_or_composition_spec.rb +36 -5
- data/spec/aquarium/aspects/pointcut_spec.rb +373 -99
- data/spec/aquarium/extensions/hash_spec.rb +129 -38
- data/spec/aquarium/finders/finder_result_spec.rb +73 -15
- data/spec/aquarium/finders/method_finder_spec.rb +156 -72
- data/spec/aquarium/finders/object_finder_spec.rb +1 -0
- data/spec/aquarium/finders/type_finder_spec.rb +43 -0
- data/spec/aquarium/utils/name_utils_spec.rb +79 -4
- metadata +3 -3
data/CHANGES
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
== Version 0.1.7
|
2
|
+
|
3
|
+
Bug fixes:
|
4
|
+
14946 Advice fails when instrumenting methods containing special characters
|
5
|
+
15038 Spec for pointcut example variation #1
|
6
|
+
15039 Spec for pointcut example variation #2
|
7
|
+
15085 Specifying just :attributes for aspects also matches all methods, as if :methods => :all specified
|
8
|
+
|
9
|
+
Enhancements:
|
10
|
+
13396 Unify internal handling of types vs. objects
|
11
|
+
|
12
|
+
15038 and 15039 were bugs in one of the examples (actually in the comments). However,
|
13
|
+
experimenting with them also revealed the nasty 15085 bug!
|
14
|
+
|
15
|
+
I previously handled some special characters in method names, but not all the possible
|
16
|
+
ones, hence 14946. Aquarium should now properly handle any valid Ruby method name.
|
17
|
+
|
18
|
+
|
1
19
|
== Version 0.1.6
|
2
20
|
|
3
21
|
Bug fixes:
|
data/README
CHANGED
@@ -10,18 +10,20 @@ Aquarium is a toolkit for Aspect-Oriented Programming (AOP) whose goals include:
|
|
10
10
|
|
11
11
|
Ruby's metaprogramming facilities already provide some of the capabilities for which static-language AOP toolkits like AspectJ are typically used. With Ruby, you can easily add new methods and attributes to existing classes and objects. You can alias and redefine existing methods, which provides the method interception and "wrapping" needed to extend or modify existing behavior.
|
12
12
|
|
13
|
-
However, what is missing in Ruby is an expressive language for describing systemic modifications, a so-called "pointcut language". If you have simple needs for method interception and wrapping, then Aquarium will be overkill. However, if you have system-wide concerns that cross the boundaries of many objects, then an AOP tookit like Aquarium can help you implement these concerns in a modular way.
|
13
|
+
However, what is missing in Ruby is an expressive language for describing systemic modifications, a so-called "pointcut language". If you have simple needs for method interception and wrapping, then Aquarium will be overkill. However, if you have system-wide concerns that cross the boundaries of many objects, then an AOP tookit like Aquarium can help you implement these concerns in a more modular way.
|
14
|
+
|
15
|
+
So, if you are designing with aspects, wouldn't you like to write your code using the same "language"? Without AOP support, you have to map your aspect designs to metaprogramming idioms, which will often be slower to implement and harder to maintain. Imagine writing objects without native support for OOP!
|
14
16
|
|
15
17
|
=== Terminology
|
16
18
|
|
17
19
|
Several terms are used in the AOP community.
|
18
20
|
|
19
|
-
* Join Point - A point of execution in a program where "advice" might invoked.
|
20
|
-
* Pointcut - (yes, one word...) A set of join points, like a query over all join points in the system.
|
21
|
+
* Join Point - A point of execution in a program where "advice" might be invoked.
|
22
|
+
* Pointcut - (yes, one word...) A set of join points of particular interest, like a query over all join points in the system.
|
21
23
|
* Advice - The behavior invoked at a join point. There are several kinds of advice:
|
22
24
|
* Before advice - Advice invoked before the actual join point is invoked.
|
23
25
|
* After returning advice - Advice invoked after the join point executes successfully.
|
24
|
-
* After raising advice - Advice invoked only
|
26
|
+
* After raising advice - Advice invoked only after the join point raises an exception.
|
25
27
|
* After advice - Advice invoked after the join point executes successfully or raises an exception.
|
26
28
|
* Around advice - Advice invoked instead of the join point. The around advice must choose whether or not to invoke the join point by calling a special "proceed" method. Otherwise, the join point is NOT executed.
|
27
29
|
|
@@ -30,49 +32,49 @@ Only around advice can prevent execution of the join point, except for the speci
|
|
30
32
|
=== Known Limitations
|
31
33
|
|
32
34
|
* You cannot advice "String", "Symbol" or instances there of, because trying to specify either one will be confused with naming a type.
|
33
|
-
* Concurrent type
|
34
|
-
* See also the comparison with AspectJ behavior next.
|
35
|
-
* The API and wrapper DSL will probably evolve until the 1.0.0 release. Backwards compatibility will be maintained between releases as much as possible
|
35
|
+
* Concurrent advice on type AND advice on objects of the type can't be removed cleanly.
|
36
|
+
* The pointcut language is still limited, compared to AspectJ's. See also the comparison with AspectJ behavior next.
|
37
|
+
* The API and wrapper DSL will probably evolve until the 1.0.0 release. Backwards compatibility will be maintained between releases as much as possible. When it is necessary to break backwards compatibility, translation tools will be provided, if possible.
|
36
38
|
|
37
39
|
=== Differences With Other Ruby AOP Toolkits
|
38
40
|
|
39
41
|
There are several other AOP toolkits for Ruby that precede Aquarium. The most notable are AspectR and the aspect capabilities in the Facets toolkit. There are also Ruby 2.0 proposals to add method wrappers for "before", "after" and "wrap" behavior.
|
40
42
|
|
41
|
-
The goal of Aquarium is to provide a superset of the functionality provided by these other toolkits. Aquarium is suitable for non-trivial and large-scale aspect-oriented components in systems. Aquarium will be most valuable for systems where aspects might be added and removed dynamically at runtime and systems where nontrivial pointcut descriptions are needed, requiring a full-featured pointcut language (as discussed
|
43
|
+
The goal of Aquarium is to provide a superset of the functionality provided by these other toolkits. Aquarium is suitable for non-trivial and large-scale aspect-oriented components in systems. Aquarium will be most valuable for systems where aspects might be added and removed dynamically at runtime and systems where nontrivial pointcut descriptions are needed, requiring a full-featured pointcut language (as discussed above...). For less demanding needs, the alternatives are lighter weight and hence may be more appropriate.
|
42
44
|
|
43
45
|
=== Differences With AspectJ Behavior
|
44
46
|
|
45
47
|
Many of AspectJ's behaviors that aren't currently supported are planned for future releases.
|
46
48
|
|
47
|
-
* Attribute reading and writing join points are not supported. The :
|
49
|
+
* Attribute reading and writing join points are not supported. The :attributes and :attributes_options parameters for Aspect.new are "syntactic sugar" for the corresponding accessor methods.
|
48
50
|
* At this time, the pointcut language supported by Aquarium is not nearly as feature-rich as AspectJ's language. For example, there are no runtime pointcut designators supported, such as "if" conditions and "cflow" (context flow). Most of AspectJ pointcut language features are planned, however.
|
49
51
|
* While AspectJ provides "intertype declaration" capabilities (i.e., adding state and behavior to existing classes), Ruby's native metaprogramming support satisfies this need. There may be convenience hooks added in a future release, however.
|
50
|
-
* User defined advice precedence is not supported. However, advice precedence is unambiguous; the last aspects created
|
51
|
-
* Unlike AspectJ, Aquarium can advise individual objects, can remove advice, and it
|
52
|
+
* User defined advice precedence is not supported. However, advice precedence is unambiguous; the last aspects created while modules are loaded at runtime have higher precedence than earlier aspects. Ensuring a particular order is not always easy, of course.
|
53
|
+
* Unlike AspectJ, Aquarium can advise individual objects, can remove advice, and it supports named advice that can be defined separately from the aspects that use the advice.
|
52
54
|
|
53
55
|
=== Examples
|
54
56
|
|
55
57
|
Several complete examples are provided in the "examples" directory.
|
56
58
|
|
57
|
-
In most cases, you can either declare the appropriate classes or use the optional DSL, which adds methods to classes, objects, or even Object itself.
|
59
|
+
In most cases, you can either declare the appropriate classes or use the optional DSL, which adds convenience methods to classes, objects, or even Object itself.
|
58
60
|
|
59
|
-
Here is an example that traces invocations of all public instance methods of the classes or modules Foo and Bar.
|
61
|
+
Here is an example that traces invocations of all public instance methods (included inherited ones) of the classes or modules Foo and Bar.
|
60
62
|
|
61
63
|
require 'aquarium'
|
62
|
-
Aspect.new :around, :types => [Foo, Bar], :methods => :all do |
|
63
|
-
p "Entering: #{
|
64
|
-
result =
|
65
|
-
p "Leaving: #{
|
64
|
+
Aspect.new :around, :types => [Foo, Bar], :methods => :all do |join_point, *args|
|
65
|
+
p "Entering: #{join_point.target_type.name}##{join_point.method_name}"
|
66
|
+
result = join_point.proceed
|
67
|
+
p "Leaving: #{join_point.target_type.name}##{join_point.method_name}"
|
66
68
|
result # block needs to return the result of the "proceed"!
|
67
69
|
end
|
68
70
|
|
69
|
-
The advice to execute at each join point is the block. The pointcut is the set of all public instance methods in Foo and Bar. (There are additional options available for specifying class methods, protected methods, etc.) Here is the same example using the convenience DSL that adds aspect methods to Object (available only if you require aquarium/aspects/dsl/object_dsl', since other toolkits, like Rails, define similar methods on Object!).
|
71
|
+
The advice to execute at each join point is the block. The pointcut is the set of all public instance methods in Foo and Bar. (There are additional options available for specifying class methods, protected methods, excluding inherited (ancestor) methods, etc.) Here is the same example using the convenience DSL that adds aspect methods to Object (available only if you require aquarium/aspects/dsl/object_dsl', since other toolkits, like Rails, define similar methods on Object!).
|
70
72
|
|
71
73
|
require 'aquarium/aspects/dsl/object_dsl'
|
72
|
-
around :types => [Foo, Bar], :methods => :all do |
|
73
|
-
p "Entering: #{
|
74
|
-
result =
|
75
|
-
p "Leaving: #{
|
74
|
+
around :types => [Foo, Bar], :methods => :all do |join_point, *args|
|
75
|
+
p "Entering: #{join_point.target_type.name}##{join_point.method_name}"
|
76
|
+
result = join_point.proceed
|
77
|
+
p "Leaving: #{join_point.target_type.name}##{join_point.method_name}"
|
76
78
|
result # block needs to return the result of the "proceed"!
|
77
79
|
end
|
78
80
|
|
@@ -80,7 +82,7 @@ See "examples/method_tracing_example.rb" for a more detailed version of this exa
|
|
80
82
|
|
81
83
|
If you don't want to add these methods to Object, you can also add them individually to modules, classes, or objects:
|
82
84
|
|
83
|
-
require 'aquarium/aspects/dsl/aspect_dsl'
|
85
|
+
require 'aquarium/aspects/dsl/aspect_dsl' # NOT object_dsl
|
84
86
|
...
|
85
87
|
module MyModule
|
86
88
|
include Aquarium::Aspects::DSL::AspectDSL
|
@@ -104,14 +106,18 @@ If you use the DSL inside a class and omit the :type(s) and :object(s) options,
|
|
104
106
|
end
|
105
107
|
...
|
106
108
|
class Foo
|
107
|
-
around :critical_operation do |
|
109
|
+
around :critical_operation do |join_point, *args|
|
108
110
|
p "Entering: Foo#critical_operation"
|
109
|
-
result =
|
111
|
+
result = join_point.proceed
|
110
112
|
p "Leaving: Foo#critical_operation"
|
111
113
|
result
|
112
114
|
end
|
113
115
|
end
|
114
116
|
|
117
|
+
It is important to note that aspect "instances" usually behave like class (static) variables, in terms of the lifetime of their effects. In the example shown, class Foo is permanently modified to do the print statements shown for all "critical methods", unless you save the result of calling "around" to a variable, e.g., critical_operation_logging, and you explicitly call "critical_operation_logging.unadvise" at some future time. Put another way, the effects scope just like changes made when you reopen a class or module.
|
118
|
+
|
119
|
+
A common mistake is to create an aspect in an initialize method and assign it to an attribute. This usually means that you are creating long-lived, redundant aspects every time an instance is created. The aspect modifications remain in effect even when the instances themselves are garbage collected!
|
120
|
+
|
115
121
|
Here are some more succinct examples, illustrating the API (using the DSL methods).
|
116
122
|
|
117
123
|
You can pass in pointcuts defined elsewhere:
|
@@ -120,21 +126,39 @@ You can pass in pointcuts defined elsewhere:
|
|
120
126
|
around :pointcuts => my_pointcut do |jp, *args| ... # Pass in a pointcut
|
121
127
|
around :pointcuts => [my_pointcut, ...] do |jp, *args| ... # Pass in a pointcut array
|
122
128
|
|
123
|
-
|
129
|
+
As a convenience, since a JoinPoint is like a Pointcut with one element, you can pass a JoinPoint object where Pointcut objects are expected:
|
130
|
+
|
131
|
+
my_join_point1 = JoinPoint.new :type => Foo::Bar, :method => do_this
|
132
|
+
my_join_point2 = JoinPoint.new :type => Foo::Bar, :method => do_that
|
133
|
+
around :pointcuts => my_join_point1 do |jp, *args| ...
|
134
|
+
around :pointcuts => [my_join_point1, my_join_point2, ...] do |jp, *args| ...
|
135
|
+
|
136
|
+
You can specify a single type, a type name, a type regular expression, or an array of the same. Note that :type and :types are synonymous "sugar".
|
124
137
|
|
125
138
|
around :type = A, ...
|
139
|
+
around :type = "A", ...
|
126
140
|
around :types => [A, B, ...], ...
|
141
|
+
around :types => %w[A, B, ...], ...
|
127
142
|
around :types => /A::.*Helper$/, ...
|
128
143
|
around :types => [/A::.*Helper$/, /B::Foo.*/], ...
|
129
144
|
|
130
|
-
You can specify a single object or an array of objects. Note that :object and :objects are synonymous.
|
145
|
+
You can specify a single object or an array of objects. Note that :object and :objects are synonymous.
|
131
146
|
|
132
147
|
a1 = A.new
|
133
148
|
a2 = A.new
|
134
149
|
around :object = a1, ...
|
135
150
|
around :objects => [a1, a2], ...
|
136
151
|
|
137
|
-
|
152
|
+
If no types or objects are specified, the object defaults to "self". However, this default is only supported when using the DSL to create an aspect!
|
153
|
+
|
154
|
+
class MyClass
|
155
|
+
include Aquarium::Aspects::DSL::AspectDSL
|
156
|
+
def doit; ...; end
|
157
|
+
|
158
|
+
around :method => doit, ... # Implicit :object => self, i.e., MyClass
|
159
|
+
end
|
160
|
+
|
161
|
+
You can specify a single method symbol (name), a regular expression, or an array of the same. Note that :method and :methods are synonymous. The special keyword :all means match all methods, subject to the :method_options discussed next.
|
138
162
|
|
139
163
|
around :method = :all, ...
|
140
164
|
around :method = :foo, ...
|
@@ -142,13 +166,15 @@ You can specify a single method symbol (name), a regular expression, or an array
|
|
142
166
|
around :methods = /^foo/, ...
|
143
167
|
around :methods = [/^foo/, /bar$/], ...
|
144
168
|
|
145
|
-
You can specify
|
169
|
+
You can specify method options. By default, public instance methods only are matched. Note that :methods => :all with no method options matches all public instance methods, including ancestor (inherited and included module) methods.
|
146
170
|
|
147
171
|
around :methods = /foo/, :method_options => [:instance], ... # match instance methods (default)
|
148
172
|
around :methods = /foo/, :method_options => [:class], ... # match class methods
|
149
173
|
around :methods = /foo/, :method_options => [:public, :protected, :private], ...
|
150
174
|
# match public, protected, and private instance methods
|
151
175
|
around :methods = /foo/, :method_options => [:singleton], ... # match singleton methods
|
176
|
+
around :methods = /foo/, :method_options => [:exclude_ancestor_methods], ...
|
177
|
+
# ignore methods defined in ancestors, inherited classes and included modules
|
152
178
|
|
153
179
|
You can specify attributes, which are actually convenience methods for the attribute accessors. They work
|
154
180
|
very much like the :method options. Note that :all is NOT supported in this case and :attribute and :attributes are synonymous.
|
@@ -164,6 +190,8 @@ You can specify a "Pointcut" that encapsulates one or more pre-defined Pointcuts
|
|
164
190
|
|
165
191
|
around :pointcut = pc, ... # for pre-defined pointcut "pc"
|
166
192
|
around :pointcuts = [pc, ...], ... # for pre-defined pointcut list
|
193
|
+
around :pointcut = jp, ... # for pre-defined join point "jp"
|
194
|
+
around :pointcuts = [jp, ...], ... # for pre-defined join point list
|
167
195
|
around :pointcut = {:type => T, :method => :m}, ... # same as around :type => T, :method => :m, ..
|
168
196
|
|
169
197
|
You can advice methods before execution:
|
@@ -173,12 +201,12 @@ You can advice methods before execution:
|
|
173
201
|
You can advice methods after returning successfully (i.e., no exceptions were raised):
|
174
202
|
|
175
203
|
after_returning :types => ...
|
176
|
-
after_returning_from :types => ...
|
204
|
+
after_returning_from :types => ... # synonym
|
177
205
|
|
178
206
|
You can advice methods after raising exceptions:
|
179
207
|
|
180
208
|
after_raising :types => ... # After any exception is thrown
|
181
|
-
after_raising_within :types => ...
|
209
|
+
after_raising_within :types => ... # synonym
|
182
210
|
after_raising => MyError, :types => ... # Only invoke advice if "MyError" is raised.
|
183
211
|
after_raising => [MyError1, MyError2], :types => ...
|
184
212
|
# Only invoke advice if "MyError1" or "MyError2" is raised.
|
@@ -187,19 +215,18 @@ You can advice methods after returning successfully or raising exceptions. (You
|
|
187
215
|
a set of exceptions in this case.):
|
188
216
|
|
189
217
|
after :types => ...
|
190
|
-
after_raising_within_or_returning_from : types =>
|
218
|
+
after_raising_within_or_returning_from : types => # synonym
|
191
219
|
|
192
|
-
You can advice methods both before after. This is different from around advice, where the advice has to explicitly invoke the join point (using JoinPoint#proceed).
|
193
|
-
around the creation of before advice and the corresponding after advice.
|
220
|
+
You can advice methods both before after. This is different from around advice, where the around advice has to explicitly invoke the join point (using JoinPoint#proceed). Instead, the before-and-after methods are convenience wrappers around the creation of separate before advice and the corresponding after advice.
|
194
221
|
|
195
222
|
before_and_after :types =>, ...
|
196
223
|
before_and_after_returning :types =>, ...
|
197
|
-
before_and_after_returning_from :types =>, ...
|
224
|
+
before_and_after_returning_from :types =>, ... # synonym
|
198
225
|
before_and_after_raising :types =>, ...
|
199
|
-
before_and_after_raising_within :types =>, ...
|
200
|
-
before_and_after_raising_within_or_returning_from :types =>, ...
|
226
|
+
before_and_after_raising_within :types =>, ... # synonym
|
227
|
+
before_and_after_raising_within_or_returning_from :types =>, ... # synonym
|
201
228
|
|
202
|
-
You can pass a block as the advice. Notice that all advice blocks and Procs (see below) are required to accept two arguments, the JoinPoint, which will contain useful context information, and "*args", which will contain the parameters used when invoking the join point (method).
|
229
|
+
You can pass a block as the advice. Notice that all advice blocks and Procs (see below) are required to accept two arguments, the JoinPoint, which will contain useful context information, and "*args", which will contain the parameters used when invoking the join point (method). A block or advice argument is required, but it can be empty.
|
203
230
|
|
204
231
|
around :type => [...], :methods => :all do |join_point, *args|
|
205
232
|
advice_to_execute_before_the_jp
|
@@ -209,6 +236,8 @@ You can pass a block as the advice. Notice that all advice blocks and Procs (see
|
|
209
236
|
end
|
210
237
|
around(:type => [...], :methods => :all) {|join_point, *args| ...} # (...) necessary for precedence...
|
211
238
|
|
239
|
+
In the example, we show that you must be careful to return the correct value, usually the value returned by "proceed" or a value created by the block itself.
|
240
|
+
|
212
241
|
Rather than passing a block as the advice, you can pass a previously-created Proc:
|
213
242
|
|
214
243
|
around :type => [...], :methods => :all, :advice => advice
|
@@ -232,7 +261,7 @@ Aquarium::Extras provides add-ons for Aquarium, such as a Design by Contract imp
|
|
232
261
|
|
233
262
|
The simplest approach is to install the gem:
|
234
263
|
|
235
|
-
gem install -
|
264
|
+
gem install -y aquarium # sudo may be required on non-Windows systems
|
236
265
|
|
237
266
|
== Building the Aquarium gem
|
238
267
|
|
data/RELEASE-PLAN
CHANGED
@@ -1 +1,25 @@
|
|
1
|
-
|
1
|
+
== Release Plan
|
2
|
+
|
3
|
+
=== Versions 0.1.X
|
4
|
+
|
5
|
+
Mostly bug fixes and enhancements. Only "minor" breakages of backwards compatibility.
|
6
|
+
|
7
|
+
=== Versions 0.2.0
|
8
|
+
|
9
|
+
Change the argument list for advice to |join_point, object, *args| from |join_point, *args|,
|
10
|
+
where "*args" are the arguments passed to the invoked join point (method). Since nontrivial
|
11
|
+
advice usually needs the object being advised, it became clear that having to call
|
12
|
+
"join_point.context.advised_object" is too tedious.
|
13
|
+
|
14
|
+
This change will obvious break existing aspects.
|
15
|
+
|
16
|
+
=== Versions 0.3+.0
|
17
|
+
|
18
|
+
More refinements to the API and functionality. The main thrust will be expanding the pointcut
|
19
|
+
language to include conditionals and stack context constructs, as well as more intuitive ways
|
20
|
+
of expressing sets of types, such as types nested arbitrarily deep in module "namespaces",
|
21
|
+
etc.
|
22
|
+
|
23
|
+
=== Version 1.0.0
|
24
|
+
|
25
|
+
Reasonable stable and full-featured API and DSL.
|
data/UPGRADE
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
== Upgrading to Aquarium-0.1.7
|
2
|
+
|
3
|
+
This is primarily a bug-fix release, so there should be no upgrading or incompatibility issues.
|
4
|
+
|
1
5
|
== Upgrading to Aquarium-0.1.6
|
2
6
|
|
3
7
|
As described in the CHANGES, the JoinPoint#type, JoinPoint#type=, JoinPoint#object, and JoinPoint#object=
|
@@ -19,8 +19,13 @@ module Aquarium
|
|
19
19
|
end
|
20
20
|
attr_accessor :state
|
21
21
|
|
22
|
-
# A simpler version of the following would be
|
23
|
-
# STATE_CHANGE = pointcut :method => :state
|
22
|
+
# A simpler version of the following pointcut would be
|
23
|
+
# STATE_CHANGE = pointcut :method => :state=
|
24
|
+
# Note that the :attribute_options => :writer option is important, especially
|
25
|
+
# given the advice block below, because if the reader is allowed to be advised,
|
26
|
+
# we get an infinite recursion of advice invocation! The correct solution is
|
27
|
+
# the planned extension of the pointcut language to support condition tests for
|
28
|
+
# context. I.e., we don't want the advice applied when it's already inside advice!
|
24
29
|
STATE_CHANGE = pointcut :attribute => :state, :attribute_options => :writer
|
25
30
|
end
|
26
31
|
end
|
@@ -31,7 +36,8 @@ include Aquarium::Aspects
|
|
31
36
|
|
32
37
|
observer = Aspect.new :after, :pointcut => Aquarium::ClassWithStateAndBehavior::STATE_CHANGE do |jp, *args|
|
33
38
|
p "State has changed. "
|
34
|
-
|
39
|
+
state = jp.context.advised_object.state
|
40
|
+
p " New state is #{state.nil? ? 'nil' : state.inspect}"
|
35
41
|
p " Equivalent to *args: #{args.inspect}"
|
36
42
|
end
|
37
43
|
|
@@ -17,8 +17,13 @@ module Aquarium
|
|
17
17
|
end
|
18
18
|
attr_accessor :state
|
19
19
|
|
20
|
-
# A simpler version of the following would be
|
21
|
-
# STATE_CHANGE = pointcut :method => :state
|
20
|
+
# A simpler version of the following pointcut would be
|
21
|
+
# STATE_CHANGE = pointcut :method => :state=
|
22
|
+
# Note that the :attribute_options => :writer option is important, especially
|
23
|
+
# given the advice block below, because if the reader is allowed to be advised,
|
24
|
+
# we get an infinite recursion of advice invocation! The correct solution is
|
25
|
+
# the planned extension of the pointcut language to support condition tests for
|
26
|
+
# context. I.e., we don't want the advice applied when it's already inside advice!
|
22
27
|
STATE_CHANGE = pointcut :attribute => :state, :attribute_options => :writer
|
23
28
|
end
|
24
29
|
end
|
@@ -39,7 +39,7 @@ bar1.do_something_else :b3, :b4
|
|
39
39
|
include Aquarium::Aspects
|
40
40
|
|
41
41
|
Aspect.new :around, :types => [Aquarium::Foo, Aquarium::Bar], :methods => :all,
|
42
|
-
:method_options => :
|
42
|
+
:method_options => :exclude_ancestor_methods do |execution_point, *args|
|
43
43
|
begin
|
44
44
|
p "Entering: #{execution_point.target_type.name}##{execution_point.method_name}: args = #{args.inspect}"
|
45
45
|
execution_point.proceed
|
@@ -56,7 +56,7 @@ describe "An example with advice on the public instance methods (excluding ances
|
|
56
56
|
it "should trace all calls to the public methods defined by Foo" do
|
57
57
|
# The "begin/ensure/end" idiom shown causes the advice to return the correct value; the result
|
58
58
|
# of the "proceed", rather than the value returned by "p"!
|
59
|
-
aspect = Aquarium::Aspects::Aspect.new :around, :type => Aquarium::Foo, :methods => :all, :method_options => :
|
59
|
+
aspect = Aquarium::Aspects::Aspect.new :around, :type => Aquarium::Foo, :methods => :all, :method_options => :exclude_ancestor_methods do |execution_point, *args|
|
60
60
|
begin
|
61
61
|
o = execution_point.context.advised_object
|
62
62
|
o.log "Entering: #{execution_point.target_type.name}##{execution_point.method_name}: args = #{args.inspect}"
|
@@ -79,7 +79,7 @@ end
|
|
79
79
|
|
80
80
|
describe "An example with advice on the public instance methods (excluding ancestor methods) of Bar" do
|
81
81
|
it "should not trace any calls to the public methods defined by the included BarModule" do
|
82
|
-
aspect = Aquarium::Aspects::Aspect.new :around, :type => Aquarium::Bar, :methods => :all, :method_options => :
|
82
|
+
aspect = Aquarium::Aspects::Aspect.new :around, :type => Aquarium::Bar, :methods => :all, :method_options => :exclude_ancestor_methods do |execution_point, *args|
|
83
83
|
begin
|
84
84
|
o = execution_point.context.advised_object
|
85
85
|
o.log "Entering: #{execution_point.target_type.name}##{execution_point.method_name}: args = #{args.inspect}"
|
@@ -88,12 +88,12 @@ module Aquarium
|
|
88
88
|
# <tt>:within_method => method || [method_list]</tt>::
|
89
89
|
# <tt>:within_methods => method || [method_list]</tt>::
|
90
90
|
# One or an array of methods, method names and/or method regular expessions to match.
|
91
|
-
#
|
92
|
-
# with
|
93
|
-
#
|
91
|
+
# Unless :attributes are specified, defaults to :all, which searches for all public
|
92
|
+
# instance methods with an implied :method_options => :exclude_ancestor_methods, unless
|
93
|
+
# :method_options provided explicitly.
|
94
94
|
#
|
95
95
|
# <tt>:method_options => [options]</tt>::
|
96
|
-
# One or more options supported by Aquarium::Finders::MethodFinder. Defaults to :
|
96
|
+
# One or more options supported by Aquarium::Finders::MethodFinder. Defaults to :exclude_ancestor_methods
|
97
97
|
#
|
98
98
|
# <tt>:attributes => attribute || [attribute_list]</tt>::
|
99
99
|
# <tt>:attribute => attribute || [attribute_list]</tt>::
|
@@ -108,6 +108,8 @@ module Aquarium
|
|
108
108
|
# One or more of <tt>:readers</tt>, <tt>:reader</tt> (synonymous),
|
109
109
|
# <tt>:writers</tt>, and/or <tt>:writer</tt> (synonymous). By default, both
|
110
110
|
# readers and writers are matched.
|
111
|
+
#
|
112
|
+
# See also the project README for extensive examples of these options.
|
111
113
|
def initialize *options, &block
|
112
114
|
process_input options, &block
|
113
115
|
init_pointcuts
|
@@ -115,14 +117,14 @@ module Aquarium
|
|
115
117
|
advise_join_points
|
116
118
|
end
|
117
119
|
|
118
|
-
def join_points_matched
|
119
|
-
|
120
|
-
@pointcuts.each do |pointcut|
|
121
|
-
matched_jps = matched_jps.union pointcut.join_points_matched
|
122
|
-
end
|
123
|
-
matched_jps
|
120
|
+
def join_points_matched
|
121
|
+
get_jps :join_points_matched
|
124
122
|
end
|
125
123
|
|
124
|
+
def join_points_not_matched
|
125
|
+
get_jps :join_points_not_matched
|
126
|
+
end
|
127
|
+
|
126
128
|
def unadvise
|
127
129
|
return if @specification[:noop]
|
128
130
|
@pointcuts.each do |pointcut|
|
@@ -189,6 +191,8 @@ module Aquarium
|
|
189
191
|
pc_options[:objects] = objects_given.to_a if objects_given?
|
190
192
|
pc_options[:methods] = methods_given.to_a if methods_given?
|
191
193
|
pc_options[:method_options] = method_options_given.to_a if method_options_given?
|
194
|
+
pc_options[:attributes] = attributes_given.to_a if attributes_given?
|
195
|
+
pc_options[:attribute_options] = attribute_options_given.to_a if attribute_options_given?
|
192
196
|
pointcuts << Aquarium::Aspects::Pointcut.new(pc_options)
|
193
197
|
end
|
194
198
|
@pointcuts = Set.new(pointcuts)
|
@@ -198,7 +202,6 @@ module Aquarium
|
|
198
202
|
advice = @advice.to_proc
|
199
203
|
@pointcuts.each do |pointcut|
|
200
204
|
interesting_join_points(pointcut).each do |join_point|
|
201
|
-
type_or_object = Aspect.type_or_object_string join_point.type_or_object
|
202
205
|
add_advice_framework join_point
|
203
206
|
Aquarium::Aspects::Advice.sort_by_priority_order(specified_advice_kinds).reverse.each do |advice_kind|
|
204
207
|
advice_chain = Aspect.get_advice_chain join_point.type_or_object, join_point.method_name
|
@@ -209,7 +212,7 @@ module Aquarium
|
|
209
212
|
end
|
210
213
|
|
211
214
|
# Ignore any inserted methods that are part of the aspect implementation,
|
212
|
-
# i.e., those that match the Aspect
|
215
|
+
# i.e., those that match the prefix returned by Aspect.aspect_method_prefix.
|
213
216
|
def interesting_join_points pointcut
|
214
217
|
pointcut.join_points_matched.reject do |join_point|
|
215
218
|
join_point.method_name.to_s =~ /^#{Aspect.aspect_method_prefix}/
|
@@ -229,6 +232,14 @@ module Aquarium
|
|
229
232
|
advice_chain = Aspect.get_advice_chain join_point.type_or_object, join_point.method_name
|
230
233
|
end
|
231
234
|
|
235
|
+
def get_jps message
|
236
|
+
jps = Set.new
|
237
|
+
@pointcuts.each do |pointcut|
|
238
|
+
jps = jps.union(pointcut.send(message))
|
239
|
+
end
|
240
|
+
jps
|
241
|
+
end
|
242
|
+
|
232
243
|
# Useful for debugging...
|
233
244
|
def self.advice_chain_inspect advice_chain
|
234
245
|
return "[nil]" if advice_chain.nil?
|
@@ -338,7 +349,7 @@ module Aquarium
|
|
338
349
|
end
|
339
350
|
|
340
351
|
def remove_advice_framework_for join_point
|
341
|
-
if
|
352
|
+
if Aquarium::Utils::TypeUtils.is_type?(join_point.type_or_object)
|
342
353
|
restore_type_method join_point
|
343
354
|
else
|
344
355
|
restore_object_method join_point
|
@@ -355,15 +366,12 @@ module Aquarium
|
|
355
366
|
EVAL_WRAPPER
|
356
367
|
end
|
357
368
|
|
358
|
-
def do_restore_type_method join_point
|
359
|
-
end
|
360
|
-
|
361
369
|
def restore_object_method join_point
|
362
370
|
saved = saved_method_name join_point
|
363
371
|
singleton = class << join_point.target_object; self; end
|
364
372
|
singleton.class_eval do
|
365
373
|
alias_method join_point.method_name, saved
|
366
|
-
|
374
|
+
send join_point.visibility, join_point.method_name
|
367
375
|
undef_method saved.intern
|
368
376
|
end
|
369
377
|
advice_chain_name = "@#{Aspect.advice_chain_attr_name join_point.type_or_object, join_point.method_name}".intern
|
@@ -371,48 +379,39 @@ module Aquarium
|
|
371
379
|
end
|
372
380
|
|
373
381
|
def self.set_advice_chain type_or_object, method_name, advice_chain
|
374
|
-
self.
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
end
|
384
|
-
|
385
|
-
def self.set_type_advice_chain type_or_object, method_name, advice_chain
|
386
|
-
chain_class_var = ("@@" + self.advice_chain_attr_name(type_or_object, method_name)).intern
|
387
|
-
type_or_object.class_eval do
|
388
|
-
class_variable_set chain_class_var, advice_chain
|
389
|
-
end
|
390
|
-
end
|
391
|
-
|
392
|
-
def self.set_object_advice_chain type_or_object, method_name, advice_chain
|
393
|
-
chain_class_var = ("@" + self.advice_chain_attr_name(type_or_object, method_name)).intern
|
394
|
-
type_or_object.instance_eval do
|
395
|
-
instance_variable_set chain_class_var, advice_chain
|
382
|
+
advice_chain_attr_sym = self.make_advice_chain_attr_sym type_or_object, method_name
|
383
|
+
if Aquarium::Utils::TypeUtils.is_type?(type_or_object)
|
384
|
+
type_or_object.class_eval do
|
385
|
+
class_variable_set advice_chain_attr_sym, advice_chain
|
386
|
+
end
|
387
|
+
else
|
388
|
+
type_or_object.instance_eval do
|
389
|
+
instance_variable_set advice_chain_attr_sym, advice_chain
|
390
|
+
end
|
396
391
|
end
|
397
392
|
end
|
398
|
-
|
399
393
|
|
400
|
-
def self.
|
401
|
-
|
402
|
-
type_or_object
|
403
|
-
|
394
|
+
def self.get_advice_chain type_or_object, method_name
|
395
|
+
advice_chain_attr_sym = self.make_advice_chain_attr_sym type_or_object, method_name
|
396
|
+
if Aquarium::Utils::TypeUtils.is_type?(type_or_object)
|
397
|
+
type_or_object.class_eval do
|
398
|
+
class_variable_get advice_chain_attr_sym
|
399
|
+
end
|
400
|
+
else
|
401
|
+
type_or_object.instance_eval do
|
402
|
+
instance_variable_get advice_chain_attr_sym
|
403
|
+
end
|
404
404
|
end
|
405
405
|
end
|
406
406
|
|
407
|
-
def self.
|
408
|
-
|
409
|
-
type_or_object.
|
410
|
-
instance_variable_get chain_class_var
|
411
|
-
end
|
407
|
+
def self.make_advice_chain_attr_sym type_or_object, method_name
|
408
|
+
ats = Aquarium::Utils::TypeUtils.is_type?(type_or_object) ? "@@" : "@"
|
409
|
+
chain_class_var = (ats + self.advice_chain_attr_name(type_or_object, method_name)).intern
|
412
410
|
end
|
411
|
+
|
413
412
|
|
414
413
|
def private_method_defined? type_or_object, method_name
|
415
|
-
if
|
414
|
+
if Aquarium::Utils::TypeUtils.is_type? type_or_object
|
416
415
|
type_or_object.private_instance_methods.include? method_name.to_s
|
417
416
|
else
|
418
417
|
type_or_object.private_methods.include? method_name.to_s
|
@@ -421,7 +420,7 @@ module Aquarium
|
|
421
420
|
|
422
421
|
def self.advice_chain_attr_name type_or_object, method_name
|
423
422
|
type_or_object_key = Aquarium::Utils::NameUtils.make_type_or_object_key(type_or_object)
|
424
|
-
class_or_object_prefix = is_type?(type_or_object) ? "class_" : ""
|
423
|
+
class_or_object_prefix = Aquarium::Utils::TypeUtils.is_type?(type_or_object) ? "class_" : ""
|
425
424
|
valid_name = Aquarium::Utils::NameUtils.make_valid_attr_name_from_method_name method_name
|
426
425
|
"#{self.aspect_method_prefix}#{class_or_object_prefix}advice_chain_#{type_or_object_key}_#{valid_name}"
|
427
426
|
end
|
@@ -432,7 +431,8 @@ module Aquarium
|
|
432
431
|
|
433
432
|
def saved_method_name join_point
|
434
433
|
to_key = Aquarium::Utils::NameUtils.make_type_or_object_key(join_point.type_or_object)
|
435
|
-
|
434
|
+
valid_name = Aquarium::Utils::NameUtils.make_valid_attr_name_from_method_name join_point.method_name
|
435
|
+
"#{Aspect.aspect_method_prefix}saved_#{to_key}_#{valid_name}"
|
436
436
|
end
|
437
437
|
|
438
438
|
def specified_advice_kinds
|
@@ -453,6 +453,7 @@ module Aquarium
|
|
453
453
|
:method => :methods,
|
454
454
|
:within_method => :methods,
|
455
455
|
:within_methods => :methods,
|
456
|
+
:attribute => :attributes,
|
456
457
|
:pointcut => :pointcuts,
|
457
458
|
:within_pointcut => :pointcuts,
|
458
459
|
:within_pointcuts => :pointcuts
|
@@ -548,14 +549,6 @@ module Aquarium
|
|
548
549
|
end
|
549
550
|
end
|
550
551
|
|
551
|
-
def self.is_type? type_or_object
|
552
|
-
type_or_object.kind_of?(Class) or type_or_object.kind_of?(Module)
|
553
|
-
end
|
554
|
-
|
555
|
-
def self.type_or_object_string type_or_object
|
556
|
-
is_type?(type_or_object) ? "type" : "object"
|
557
|
-
end
|
558
|
-
|
559
552
|
def bad_options message
|
560
553
|
raise Aquarium::Utils::InvalidOptions.new("Invalid options given. " + message + " (options: #{@original_options.inspect})")
|
561
554
|
end
|