aquarium 0.1.6 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/CHANGES +18 -0
  2. data/README +68 -39
  3. data/RELEASE-PLAN +25 -1
  4. data/UPGRADE +4 -0
  5. data/examples/aspect_design_example.rb +9 -3
  6. data/examples/aspect_design_example_spec.rb +7 -2
  7. data/examples/method_tracing_example.rb +1 -1
  8. data/examples/method_tracing_example_spec.rb +2 -2
  9. data/lib/aquarium/aspects/aspect.rb +53 -60
  10. data/lib/aquarium/aspects/dsl/aspect_dsl.rb +0 -1
  11. data/lib/aquarium/aspects/pointcut.rb +72 -17
  12. data/lib/aquarium/aspects/pointcut_composition.rb +4 -1
  13. data/lib/aquarium/extensions/hash.rb +65 -28
  14. data/lib/aquarium/extensions/set.rb +2 -0
  15. data/lib/aquarium/finders/finder_result.rb +13 -2
  16. data/lib/aquarium/finders/method_finder.rb +54 -28
  17. data/lib/aquarium/finders/type_finder.rb +36 -19
  18. data/lib/aquarium/utils/method_utils.rb +3 -12
  19. data/lib/aquarium/utils/name_utils.rb +27 -1
  20. data/lib/aquarium/version.rb +1 -1
  21. data/spec/aquarium/aspects/aspect_invocation_spec.rb +182 -51
  22. data/spec/aquarium/aspects/aspect_spec.rb +43 -8
  23. data/spec/aquarium/aspects/pointcut_and_composition_spec.rb +32 -3
  24. data/spec/aquarium/aspects/pointcut_or_composition_spec.rb +36 -5
  25. data/spec/aquarium/aspects/pointcut_spec.rb +373 -99
  26. data/spec/aquarium/extensions/hash_spec.rb +129 -38
  27. data/spec/aquarium/finders/finder_result_spec.rb +73 -15
  28. data/spec/aquarium/finders/method_finder_spec.rb +156 -72
  29. data/spec/aquarium/finders/object_finder_spec.rb +1 -0
  30. data/spec/aquarium/finders/type_finder_spec.rb +43 -0
  31. data/spec/aquarium/utils/name_utils_spec.rb +79 -4
  32. 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 if the join point raises an exception.
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- and object-based advice can't be removed cleanly.
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 and translation tools will be provided, when necessary.
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 elsewhere...). For less demanding needs, the alternatives are lighter weight and hence may be more appropriate.
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 :attribute(s) Aspect options are convenience wrappers for the corresponding accessor methods.
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 as modules are loaded at runtime have higher precedence than earlier aspects. Ensuring a particular order is not always easy, of course.
51
- * Unlike AspectJ, Aquarium can advise individual objects, can remove advice, and it has named pointcuts that can be defined separately from aspects.
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 |execution_point, *args|
63
- p "Entering: #{execution_point.target_type.name}##{execution_point.method_name}"
64
- result = execution_point.proceed
65
- p "Leaving: #{execution_point.target_type.name}##{execution_point.method_name}"
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 |execution_point, *args|
73
- p "Entering: #{execution_point.target_type.name}##{execution_point.method_name}"
74
- result = execution_point.proceed
75
- p "Leaving: #{execution_point.target_type.name}##{execution_point.method_name}"
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 |execution_point, *args|
109
+ around :critical_operation do |join_point, *args|
108
110
  p "Entering: Foo#critical_operation"
109
- result = execution_point.proceed
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
- 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.
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. If no types or objects are specified, the object defaults to "self".
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
- You can specify a single method symbol (name), a regular expression, or an array of the same. Note that :all is a special keyword meaning all methods and :method and :methods are synonymous.
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 a method options. By default, public instance methods only are matched.
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). Rather, these methods are convenience wrappers
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). It is an error if no block is specified.
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 -r aquarium # sudo may be required
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
- TODO:
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
- p " New state is #{jp.context.advised_object.state.inspect}"
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 => :suppress_ancestor_methods do |execution_point, *args|
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 => :suppress_ancestor_methods do |execution_point, *args|
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 => :suppress_ancestor_methods do |execution_point, *args|
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
- # By default, unless :attributes are specified, searches for public instance methods
92
- # with the method option :suppress_ancestor_methods implied, unless explicit method
93
- # options are given.
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 :suppress_ancestor_methods
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
- matched_jps = Set.new
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..aspect_method_prefix.
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 Aspect.is_type?(join_point.type_or_object)
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
- public join_point.method_name
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.is_type?(type_or_object) ?
375
- self.set_type_advice_chain(type_or_object, method_name, advice_chain) :
376
- self.set_object_advice_chain(type_or_object, method_name, advice_chain)
377
- end
378
-
379
- def self.get_advice_chain type_or_object, method_name
380
- self.is_type?(type_or_object) ?
381
- self.get_type_advice_chain(type_or_object, method_name) :
382
- self.get_object_advice_chain(type_or_object, method_name)
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.get_type_advice_chain type_or_object, method_name
401
- chain_class_var = ("@@" + self.advice_chain_attr_name(type_or_object, method_name)).intern
402
- type_or_object.class_eval do
403
- class_variable_get chain_class_var
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.get_object_advice_chain type_or_object, method_name
408
- chain_class_var = ("@" + self.advice_chain_attr_name(type_or_object, method_name)).intern
409
- type_or_object.instance_eval do
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 Aspect.is_type? type_or_object
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
- "#{Aspect.aspect_method_prefix}saved_#{to_key}_#{join_point.method_name}"
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