aquarium 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +50 -0
- data/README +35 -6
- data/RELEASE-PLAN +8 -5
- data/Rakefile +17 -1
- data/UPGRADE +5 -0
- data/lib/aquarium/aspects/advice.rb +6 -6
- data/lib/aquarium/aspects/aspect.rb +11 -17
- data/lib/aquarium/aspects/join_point.rb +5 -5
- data/lib/aquarium/aspects/pointcut.rb +68 -40
- data/lib/aquarium/finders/method_finder.rb +1 -0
- data/lib/aquarium/finders/type_finder.rb +12 -8
- data/lib/aquarium/utils/default_logger.rb +1 -0
- data/lib/aquarium/utils/method_utils.rb +4 -4
- data/lib/aquarium/utils/name_utils.rb +3 -3
- data/lib/aquarium/utils/type_utils.rb +25 -10
- data/lib/aquarium/version.rb +2 -2
- data/spec/aquarium/aspects/advice_spec.rb +13 -1
- data/spec/aquarium/aspects/aspect_spec.rb +63 -44
- data/spec/aquarium/aspects/join_point_spec.rb +1 -1
- data/spec/aquarium/aspects/pointcut_spec.rb +22 -10
- data/spec/aquarium/finders/method_finder_spec.rb +3 -2
- data/spec/aquarium/finders/type_finder_spec.rb +2 -2
- data/spec/aquarium/finders/type_finder_with_descendents_and_ancestors_spec.rb +8 -8
- data/spec/aquarium/utils/method_utils_spec.rb +17 -6
- data/spec/aquarium/utils/type_utils_spec.rb +45 -23
- metadata +3 -3
data/CHANGES
CHANGED
@@ -1,3 +1,53 @@
|
|
1
|
+
== Version 0.4.0
|
2
|
+
|
3
|
+
V0.4.0 adds specs to characterize and test advising Java classes when running on JRuby and adds several
|
4
|
+
API enhancements.
|
5
|
+
|
6
|
+
Bug fixes:
|
7
|
+
17844 JRuby - Advising types, Aquarium thinks the type is a string
|
8
|
+
17883 Workaround for JRUBY-2089
|
9
|
+
18090 JoinPoint#invoke_original_join_point only works with :around advice
|
10
|
+
|
11
|
+
Enhancements:
|
12
|
+
17834 Allow :class and :module (and variants) wherever :type is allowed
|
13
|
+
17881 Add specs that exercise advising Java types and objects using JRuby
|
14
|
+
|
15
|
+
#17844 occurred because of the way JRuby encodes Java packages into modules. Aquarium now properly handles
|
16
|
+
JRuby types.
|
17
|
+
|
18
|
+
#17883 reflected a JRuby bug, so a workaround was required.
|
19
|
+
|
20
|
+
#17834 allows the user to substitute the words "class", "classes", "module" or "modules" anywhere the words
|
21
|
+
"type" and "types" are used in the API, since some users might naturally want to write aspects like this:
|
22
|
+
|
23
|
+
around :calls_to => :my_method, :in_class => MyClass do ...; end
|
24
|
+
|
25
|
+
However, there is no enforcement to ensure that "class" is only used for classes and "module" is only used
|
26
|
+
for modules, etc. Note: it's possible we'll enforce this in some future release, as a way of saying things
|
27
|
+
like "only advise classes that match ...", etc. Caveat Emptor!
|
28
|
+
|
29
|
+
For #18090, a bug prevented JoinPoint#invoke_original_join_point (which allows you to bypass all advices at
|
30
|
+
the join point) from working except for :around advice (and yes, the specs didn't cover this adequately -
|
31
|
+
gasp!). Now fixed.
|
32
|
+
|
33
|
+
For #17881, I created a separate set of specs for JRuby, so it's easier to run the "regular" Aquarium
|
34
|
+
specs using MRI and the JRuby-specific specs separately with JRuby. The new "jruby" directory contains a
|
35
|
+
Rakefile, another set of specs, and Java example code for the specs to use. The default Rakefile task
|
36
|
+
re-runs the main Aquarium spec suite using JRuby, to confirm that the suite passes successfully, then it
|
37
|
+
runs a set of different specs that load sample Java interfaces and classes into JRuby and then advises
|
38
|
+
them with Aquarium.
|
39
|
+
|
40
|
+
I found a few JRuby bugs and other behavior differences between MRI during this release. I was able to handle
|
41
|
+
them with modifications to the Aquarium code. If you "grep" the Aquarium "lib" and "spec" directories for the
|
42
|
+
word "jruby" (ignoring case), you'll find notes about these issues and the workarounds I implemented.
|
43
|
+
|
44
|
+
The separate JRuby spec suite shows what you can and cannot do with Java types. As a side benefit, it also
|
45
|
+
demonstrates how Java types, objects, and methods are mapped to Ruby. There are some important limitations,
|
46
|
+
however. See the jruby.html page on the website or the README for more details.
|
47
|
+
|
48
|
+
Note: JRuby 1.1RC2 was used.
|
49
|
+
|
50
|
+
|
1
51
|
== Version 0.3.1
|
2
52
|
|
3
53
|
V0.3.1 adds numerous performance improvements, especially in the RSpec suite, and some minor API additions.
|
data/README
CHANGED
@@ -5,6 +5,7 @@ Aquarium is a toolkit for Aspect-Oriented Programming (AOP) whose goals include:
|
|
5
5
|
* Management of concurrent aspects (i.e., those acting on the same "join points").
|
6
6
|
* Adding and removing aspects dynamically.
|
7
7
|
* A user-friendly DSL.
|
8
|
+
* Support for advising Java types through JRuby.
|
8
9
|
|
9
10
|
=== Why Is an AOP Framework Useful in Ruby?
|
10
11
|
|
@@ -35,6 +36,11 @@ Only around advice can prevent execution of the join point, except for the speci
|
|
35
36
|
* Concurrent advice on type AND advice on objects of the type can't be removed cleanly.
|
36
37
|
* The pointcut language is still limited, compared to AspectJ's. See also the comparison with AspectJ behavior next.
|
37
38
|
* 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.
|
39
|
+
* There are limitations when advising Java types through JRuby. The separate RSpec suite in the "jruby" directory documentations the details on how to use Aquarium with JRuby-wrapped Java types and the limitations thereof. Here we summarize what works and what doesn't:
|
40
|
+
* Aquarium advice on a method in a Java type will only be invoked when the method is called directly from Ruby.
|
41
|
+
* To have the advice invoked when the method is called from either Java or Ruby, it is necessary to create a subclass of the Java type in Ruby and an override of the method, which can just call "super". Note that it will be necessary for instances of this Ruby type to be used throughout, not instances of a Java parent type.
|
42
|
+
* BUG #18325: If you have Ruby subclasses of Java types *and* you advise a Java method in the hierarchy using @:types_and_descendents => MyJavaBaseClassOrInterface@ *and* you call unadvise on the aspect, the advice "infrastructure" is not correctly removed from the Ruby types. Workaround: Only advise methods in Ruby subclasses of Java types where the method is explicitly overridden in the Ruby class. (The spec and the "Rubyforge bug report":http://rubyforge.org/tracker/index.php?func=detail&aid=18325&group_id=4281&atid=16494 provide examples.)
|
43
|
+
* BUG #18326: Normally, you can use either Java- or Ruby-style method names (e.g., "doSomething" vs. "do_something"), for Java types. However, if you write an aspect using the Java-style for a method name and a Ruby subclass of the Java type where the method is actually defined (i.e., the Ruby class doesn't override the method), it appears that the JoinPoint was advised, but the advice is never called. Workaround: Use the Ruby-style name in this scenario.
|
38
44
|
|
39
45
|
=== Differences With Other Ruby AOP Toolkits
|
40
46
|
|
@@ -44,13 +50,24 @@ The goal of Aquarium is to provide a superset of the functionality provided by t
|
|
44
50
|
|
45
51
|
=== Differences With AspectJ Behavior
|
46
52
|
|
47
|
-
Many of AspectJ's
|
53
|
+
Many of AspectJ's features are not currently supported by Aquarium, but some of them are planned for future releases.
|
48
54
|
|
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.
|
50
|
-
*
|
55
|
+
* Attribute reading and writing join points are not supported. The :attributes and :attributes_options parameters (and their synonyms) for Aspect.new are actually "syntactic sugar" for the corresponding accessor methods.
|
56
|
+
* More advanced AspectJ pointcut language features are not supported, such as the runtime pointcut designators like "if" conditionals and "cflow" (context flow) and the compile time "within" and "withincode" designators. Most of AspectJ pointcut language features are planned, however.
|
51
57
|
* 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.
|
52
58
|
* 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
|
-
|
59
|
+
|
60
|
+
However, Aquarium does have a few advantages over AspectJ, especially when advising Java types when running in JRuby.
|
61
|
+
|
62
|
+
* Aquarium can add and remove advice dynamically, at runtime.
|
63
|
+
* Aquarium can advise individual objects, not just classes.
|
64
|
+
* Aquarium can advise JDK classes. AspectJ can also do this, but not by default.
|
65
|
+
* Aquarium supports named advice that can be defined separately from the aspects that use the advice.
|
66
|
+
* Aquarium can advise ancestor (parent) types, not just derived (descendent) types of specified types.
|
67
|
+
|
68
|
+
Note: at the time of this writing (V0.4.0 release), there is an important limitation of Aquarium when used with java code; it appears that advice is only invoked if an advised method is called directly from Ruby code. If the advised method is called by other Java code, the advice is *not* invoked. Whether or not this limitation can be removed is under investigation.
|
69
|
+
|
70
|
+
Also, as of V0.4.0, the interaction behavior of Aquarium and AspectJ or Spring aspects has not been investigated.
|
54
71
|
|
55
72
|
=== Examples
|
56
73
|
|
@@ -142,7 +159,9 @@ You can specify a single type, a type name, a type regular expression, or an arr
|
|
142
159
|
around :types => /A::.*Helper$/, ...
|
143
160
|
around :types => [/A::.*Helper$/, /B::Foo.*/], ...
|
144
161
|
|
145
|
-
|
162
|
+
Everywhere "type" is used, you can substitute "class", "classes", "module", or "modules". Note that they are treated as synonyms; there is currently no enforcement that the values passed with ":class", for example, are actually classes, not modules.
|
163
|
+
|
164
|
+
Using the plural versions of the synonyms with method specifications sometimes read better:
|
146
165
|
|
147
166
|
around :calls_to => :all_methods, :on_types => [A, B, ...], ...
|
148
167
|
around :calls_to => :all_methods, :in_types => [A, B, ...], ...
|
@@ -154,6 +173,8 @@ You can specify types and their descendents (subclasses or included modules) or
|
|
154
173
|
around :types_and_ancestors = A, ...
|
155
174
|
around :type_and_descendents = A, ...
|
156
175
|
around :types_and_descendents = A, ...
|
176
|
+
around :classes_and_descendents = A, ...
|
177
|
+
around :modules_and_descendents = A, ...
|
157
178
|
|
158
179
|
Some of the synonyms:
|
159
180
|
|
@@ -324,6 +345,7 @@ Rather than passing a block as the advice, you can pass a previously-created Pro
|
|
324
345
|
around :type => [...], :methods => :all, :call => advice # synonym for advice.
|
325
346
|
around :type => [...], :methods => :all, :invoke => advice # synonym for advice.
|
326
347
|
|
348
|
+
Finally, when running in JRuby, you can advise Java types! See the examples in the separate RSpec suite in the "jruby" directory.
|
327
349
|
|
328
350
|
=== Packages
|
329
351
|
|
@@ -353,7 +375,7 @@ do the following:
|
|
353
375
|
|
354
376
|
== Running Aquarium's RSpec Specs
|
355
377
|
|
356
|
-
In order to run Aquarium's full suite of specs (rake pre_commit) you must install the following gems:
|
378
|
+
In order to run Aquarium's full suite of specs (rake pre_commit) you must install the following gems and tools:
|
357
379
|
|
358
380
|
* rake # Runs the build script
|
359
381
|
* rspec # Used instead of Test::Unit for TDD
|
@@ -365,6 +387,7 @@ In order to run Aquarium's full suite of specs (rake pre_commit) you must instal
|
|
365
387
|
* win32console # Required by the --colour switch if you're on Windows
|
366
388
|
* meta_project # Required in order to make releases at RubyForge
|
367
389
|
* heckle # Required if you use the --heckle switch
|
390
|
+
* jruby # Required if run the separate spec suite in the "jruby" directory
|
368
391
|
|
369
392
|
Once those are all installed, you should be able to run the suite with the following steps:
|
370
393
|
|
@@ -376,6 +399,12 @@ or
|
|
376
399
|
|
377
400
|
Note that Aquarium itself - once built - doesn't have any dependencies outside the Ruby core and stdlib.
|
378
401
|
|
402
|
+
If you want to run the tests for the JRuby support, you must also have JRuby 1.1RC2 or later installed. To run the specs for JRuby, use the command
|
403
|
+
|
404
|
+
* rake verify_jruby
|
405
|
+
|
406
|
+
This command runs the standard Aquarium specs using JRuby instead of MRI, then runs a separate set of specs in the "jruby/spec" directory which test Aquarium with Java classes inside JRuby.
|
407
|
+
|
379
408
|
See http://aquarium.rubyforge.org for further documentation.
|
380
409
|
|
381
410
|
=== Acknowledgments
|
data/RELEASE-PLAN
CHANGED
@@ -15,9 +15,9 @@ This change will obvious break existing aspects.
|
|
15
15
|
|
16
16
|
=== Versions 0.3+.0
|
17
17
|
|
18
|
-
More refinements and
|
19
|
-
reduction. I haven't used mocks much in the specs, but they
|
20
|
-
performance of RSpec runs.
|
18
|
+
More refinements and simplifications to the API and functionality, including much-needed
|
19
|
+
redundancy reduction. I haven't used mocks much in the specs, but they would help improve the
|
20
|
+
performance of the RSpec runs.
|
21
21
|
|
22
22
|
The main thrust of new feature work will be expanding the pointcut language to include
|
23
23
|
conditionals and stack context constructs, as well as more intuitive ways of expressing sets of
|
@@ -39,10 +39,13 @@ How about something like the following?
|
|
39
39
|
end
|
40
40
|
|
41
41
|
I'm not sure it adds much (at this stage of thinking about it...) except that it could make
|
42
|
-
composition of
|
42
|
+
composition of complex pointcuts easier.
|
43
|
+
|
44
|
+
I also want to ensure full support for running in JRuby. In particular, you should be able to
|
45
|
+
advise Java types!
|
43
46
|
|
44
47
|
=== Version 1.0.0
|
45
48
|
|
46
|
-
|
49
|
+
Reasonably stable and full-featured API and DSL. Also, to justify Aquarium's existence ;), I want
|
47
50
|
to produce some non-trivial examples of refactoring known APIs and demonstrating improved clarity,
|
48
51
|
productivity, modularity, etc., etc.
|
data/Rakefile
CHANGED
@@ -44,6 +44,12 @@ Spec::Rake::SpecTask.new('spec_html') do |t|
|
|
44
44
|
t.spec_opts = ['--format html:../doc/output/report.html','--backtrace']
|
45
45
|
end
|
46
46
|
|
47
|
+
desc "Run all specs without rcov"
|
48
|
+
Spec::Rake::SpecTask.new('spec_no_rcov') do |t|
|
49
|
+
t.spec_files = FileList['spec/**/*_spec.rb', 'examples/**/*_spec.rb']
|
50
|
+
t.spec_opts = ['--colour']
|
51
|
+
end
|
52
|
+
|
47
53
|
desc 'Generate HTML documentation for website'
|
48
54
|
task :webgen do
|
49
55
|
Dir.chdir '../doc' do
|
@@ -152,7 +158,7 @@ end
|
|
152
158
|
# ]
|
153
159
|
|
154
160
|
desc "Build the website, but do not publish it"
|
155
|
-
task :website => [:clobber, :verify_rcov, :spec_html, :webgen, :rdoc]
|
161
|
+
task :website => [:clobber, :verify_rcov, :verify_jruby, :spec_html, :webgen, :rdoc]
|
156
162
|
|
157
163
|
task :rdoc_rails do
|
158
164
|
Dir.chdir '../aquarium_on_rails' do
|
@@ -161,6 +167,16 @@ task :rdoc_rails do
|
|
161
167
|
end
|
162
168
|
end
|
163
169
|
|
170
|
+
desc "Verify that the Aquarium specs run under JRuby and that JRuby can advise Java types"
|
171
|
+
task :verify_jruby do
|
172
|
+
puts "Verifying JRuby Support"
|
173
|
+
Dir.chdir 'jruby' do
|
174
|
+
rake = (PLATFORM == "i386-mswin32") ? "rake.cmd" : "rake"
|
175
|
+
sh "jruby -S #{rake}"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
|
164
180
|
task :verify_user do
|
165
181
|
raise "RUBYFORGE_USER environment variable not set!" unless ENV['RUBYFORGE_USER']
|
166
182
|
end
|
data/UPGRADE
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
== Updating to Aquarium-0.4.0
|
2
|
+
|
3
|
+
This release is fully backwards-compatible with previous releases. It expands the API slightly and adds
|
4
|
+
internal improvements to better support JRuby. There should be no upgrade issues.
|
5
|
+
|
1
6
|
== Updating to Aquarium-0.3.1
|
2
7
|
|
3
8
|
There should be no upgrade issues with this release. However, the enhancement #17565 now ensures that a
|
@@ -122,7 +122,7 @@ module Aquarium
|
|
122
122
|
class BeforeAdviceChainNode < AdviceChainNode
|
123
123
|
def initialize options = {}
|
124
124
|
super(options) { |jp, obj, *args|
|
125
|
-
before_jp = jp.make_current_context_join_point :advice_kind => :before
|
125
|
+
before_jp = jp.make_current_context_join_point :advice_kind => :before, :current_advice_node => self
|
126
126
|
advice.call(before_jp, obj, *args)
|
127
127
|
next_node.call(jp, obj, *args)
|
128
128
|
}
|
@@ -133,7 +133,7 @@ module Aquarium
|
|
133
133
|
def initialize options = {}
|
134
134
|
super(options) { |jp, obj, *args|
|
135
135
|
returned_value = next_node.call(jp, obj, *args)
|
136
|
-
next_jp = jp.make_current_context_join_point :advice_kind => :after_returning, :returned_value => returned_value
|
136
|
+
next_jp = jp.make_current_context_join_point :advice_kind => :after_returning, :returned_value => returned_value, :current_advice_node => self
|
137
137
|
advice.call(next_jp, obj, *args)
|
138
138
|
next_jp.context.returned_value # allow advice to modify the returned value
|
139
139
|
}
|
@@ -150,7 +150,7 @@ module Aquarium
|
|
150
150
|
next_node.call(jp, obj, *args)
|
151
151
|
rescue Object => raised_exception
|
152
152
|
if after_raising_exceptions_list_includes raised_exception
|
153
|
-
next_jp = jp.make_current_context_join_point :advice_kind => :after_raising, :raised_exception => raised_exception
|
153
|
+
next_jp = jp.make_current_context_join_point :advice_kind => :after_raising, :raised_exception => raised_exception, :current_advice_node => self
|
154
154
|
advice.call(next_jp, obj, *args)
|
155
155
|
raised_exception = next_jp.context.raised_exception # allow advice to modify raised exception
|
156
156
|
end
|
@@ -177,11 +177,11 @@ module Aquarium
|
|
177
177
|
# can allow the advice to change the exception that will be raised.
|
178
178
|
begin
|
179
179
|
returned_value = next_node.call(jp, obj, *args)
|
180
|
-
next_jp = jp.make_current_context_join_point :advice_kind => :after, :returned_value => returned_value
|
180
|
+
next_jp = jp.make_current_context_join_point :advice_kind => :after, :returned_value => returned_value, :current_advice_node => self
|
181
181
|
advice.call(next_jp, obj, *args)
|
182
182
|
next_jp.context.returned_value # allow advice to modify the returned value
|
183
183
|
rescue Object => raised_exception
|
184
|
-
next_jp = jp.make_current_context_join_point :advice_kind => :after, :raised_exception => raised_exception
|
184
|
+
next_jp = jp.make_current_context_join_point :advice_kind => :after, :raised_exception => raised_exception, :current_advice_node => self
|
185
185
|
advice.call(next_jp, obj, *args)
|
186
186
|
raise next_jp.context.raised_exception
|
187
187
|
end
|
@@ -192,7 +192,7 @@ module Aquarium
|
|
192
192
|
class AroundAdviceChainNode < AdviceChainNode
|
193
193
|
def initialize options = {}
|
194
194
|
super(options) { |jp, obj, *args|
|
195
|
-
around_jp = jp.make_current_context_join_point :advice_kind => :around, :proceed_proc => next_node
|
195
|
+
around_jp = jp.make_current_context_join_point :advice_kind => :around, :proceed_proc => next_node, :current_advice_node => self
|
196
196
|
advice.call(around_jp, obj, *args)
|
197
197
|
}
|
198
198
|
end
|
@@ -224,7 +224,9 @@ module Aquarium
|
|
224
224
|
return
|
225
225
|
end
|
226
226
|
end
|
227
|
-
|
227
|
+
msg = "Warning: No join points were matched. The options specified were #{@original_options.inspect}."
|
228
|
+
msg += " The resulting specification was #{@specification.inspect}." if logger.debug?
|
229
|
+
logger.warn msg
|
228
230
|
end
|
229
231
|
|
230
232
|
def should_warn_if_no_matching_join_points
|
@@ -246,7 +248,6 @@ module Aquarium
|
|
246
248
|
advice = @advice.to_proc
|
247
249
|
@pointcuts.each do |pointcut|
|
248
250
|
interesting_join_points(pointcut).each do |join_point|
|
249
|
-
attr_name = Aspect.make_advice_chain_attr_sym(join_point)
|
250
251
|
add_advice_framework(join_point) if need_advice_framework?(join_point)
|
251
252
|
Advice.sort_by_priority_order(specified_advice_kinds).reverse.each do |advice_kind|
|
252
253
|
add_advice_to_chain join_point, advice_kind, advice
|
@@ -319,19 +320,6 @@ module Aquarium
|
|
319
320
|
EOF
|
320
321
|
end
|
321
322
|
|
322
|
-
def static_method_prefix join_point
|
323
|
-
if join_point.instance_method?
|
324
|
-
"@@type_being_advised = self"
|
325
|
-
else
|
326
|
-
"@@type_being_advised = self"
|
327
|
-
"class << self"
|
328
|
-
end
|
329
|
-
end
|
330
|
-
|
331
|
-
def static_method_suffix join_point
|
332
|
-
join_point.instance_method? ? "" : "end"
|
333
|
-
end
|
334
|
-
|
335
323
|
# When advising an instance, create an override method that gets advised instead of the types method.
|
336
324
|
# Otherwise, all objects will be advised!
|
337
325
|
# Note: this also solves bug #15202.
|
@@ -383,6 +371,7 @@ module Aquarium
|
|
383
371
|
end
|
384
372
|
|
385
373
|
def remove_advice_for_aspect_at join_point
|
374
|
+
return unless Aspect.advice_chain_exists? join_point
|
386
375
|
prune_nodes_in_advice_chain_for join_point
|
387
376
|
advice_chain = Aspect.get_advice_chain join_point
|
388
377
|
remove_advice_framework_for(join_point) if advice_chain.empty?
|
@@ -415,14 +404,19 @@ module Aquarium
|
|
415
404
|
def restore_original_method_text join_point
|
416
405
|
alias_method_name = (Aspect.make_saved_method_name join_point).intern
|
417
406
|
<<-EOF
|
418
|
-
#{
|
407
|
+
#{join_point.instance_method? ? "" : "class << self"}
|
419
408
|
#{unalias_original_method_text alias_method_name, join_point}
|
420
409
|
#{undef_eigenclass_method_text join_point}
|
421
|
-
#{
|
410
|
+
#{join_point.instance_method? ? "" : "end"}
|
422
411
|
EOF
|
423
412
|
end
|
424
413
|
|
425
414
|
# TODO optimize calls to these *_advice_chain methods from other private methods.
|
415
|
+
def self.advice_chain_exists? join_point
|
416
|
+
advice_chain_attr_sym = self.make_advice_chain_attr_sym join_point
|
417
|
+
type_to_advise_for(join_point).class_variable_defined? advice_chain_attr_sym
|
418
|
+
end
|
419
|
+
|
426
420
|
def self.set_advice_chain join_point, advice_chain
|
427
421
|
advice_chain_attr_sym = self.make_advice_chain_attr_sym join_point
|
428
422
|
type_to_advise_for(join_point).send :class_variable_set, advice_chain_attr_sym, advice_chain
|
@@ -12,7 +12,7 @@ module Aquarium
|
|
12
12
|
class ContextNotDefined < Exception; end
|
13
13
|
|
14
14
|
class Context
|
15
|
-
attr_accessor :advice_kind, :advised_object, :parameters, :block_for_method, :returned_value, :raised_exception, :proceed_proc
|
15
|
+
attr_accessor :advice_kind, :advised_object, :parameters, :block_for_method, :returned_value, :raised_exception, :proceed_proc, :current_advice_node
|
16
16
|
|
17
17
|
alias :target_object :advised_object
|
18
18
|
alias :target_object= :advised_object=
|
@@ -44,17 +44,17 @@ module Aquarium
|
|
44
44
|
|
45
45
|
def proceed enclosing_join_point, *args, &block
|
46
46
|
raise ProceedMethodNotAvailable.new("It looks like you tried to call \"JoinPoint#proceed\" (or \"JoinPoint::Context#proceed\") from within advice that isn't \"around\" advice. Only around advice can call proceed. (Specific error: JoinPoint#proceed cannot be called because no \"@proceed_proc\" attribute was set on the corresponding JoinPoint::Context object.)") if @proceed_proc.nil?
|
47
|
-
do_invoke :call, enclosing_join_point, *args, &block
|
47
|
+
do_invoke proceed_proc, :call, enclosing_join_point, *args, &block
|
48
48
|
end
|
49
49
|
|
50
50
|
def invoke_original_join_point enclosing_join_point, *args, &block
|
51
|
-
do_invoke :invoke_original_join_point, enclosing_join_point, *args, &block
|
51
|
+
do_invoke current_advice_node, :invoke_original_join_point, enclosing_join_point, *args, &block
|
52
52
|
end
|
53
53
|
|
54
|
-
def do_invoke method, enclosing_join_point, *args, &block
|
54
|
+
def do_invoke proc_to_send, method, enclosing_join_point, *args, &block
|
55
55
|
args = parameters if (args.nil? or args.size == 0)
|
56
56
|
enclosing_join_point.context.block_for_method = block if block
|
57
|
-
|
57
|
+
proc_to_send.send method, enclosing_join_point, advised_object, *args
|
58
58
|
end
|
59
59
|
protected :do_invoke
|
60
60
|
|
@@ -196,25 +196,36 @@ module Aquarium
|
|
196
196
|
alias to_s inspect
|
197
197
|
|
198
198
|
CANONICAL_OPTIONS = {
|
199
|
-
"types" => %w[type
|
200
|
-
"
|
201
|
-
"
|
199
|
+
"types" => %w[type class classes module modules],
|
200
|
+
"types_and_descendents" => %w[type_and_descendents class_and_descendents classes_and_descendents module_and_descendents modules_and_descendents],
|
201
|
+
"types_and_ancestors" => %w[type_and_ancestors class_and_ancestors classes_and_ancestors module_and_ancestors modules_and_ancestors],
|
202
|
+
"objects" => %w[object],
|
203
|
+
"join_points" => %w[join_point],
|
202
204
|
"methods" => %w[method within_method within_methods calling invoking calls_to invocations_of sending_message_to sending_messages_to],
|
203
205
|
"attributes" => %w[attribute accessing],
|
204
206
|
"method_options" => %w[method_option restricting_methods_to],
|
205
207
|
"attribute_options" => %w[attribute_option],
|
206
|
-
"types_and_descendents" => %w[type_and_descendents on_type_and_descendents on_types_and_descendents within_type_and_descendents within_types_and_descendents],
|
207
|
-
"types_and_ancestors" => %w[type_and_ancestors on_type_and_ancestors on_types_and_ancestors within_type_and_ancestors within_types_and_ancestors],
|
208
208
|
"default_objects" => %w[default_object]
|
209
209
|
}
|
210
|
-
%w[types objects join_points
|
211
|
-
|
210
|
+
%w[types types_and_descendents types_and_ancestors objects join_points ].each do |thing|
|
211
|
+
roots = CANONICAL_OPTIONS[thing].dup + [thing]
|
212
|
+
CANONICAL_OPTIONS["exclude_#{thing}"] = roots.map {|x| "exclude_#{x}"}
|
213
|
+
%w[for on in within].each do |prefix|
|
214
|
+
roots.each do |root|
|
215
|
+
CANONICAL_OPTIONS[thing] << "#{prefix}_#{root}"
|
216
|
+
end
|
217
|
+
end
|
212
218
|
end
|
213
219
|
CANONICAL_OPTIONS["methods"].dup.each do |synonym|
|
214
220
|
CANONICAL_OPTIONS["methods"] << "#{synonym}_methods_matching"
|
215
221
|
end
|
216
|
-
CANONICAL_OPTIONS["
|
217
|
-
|
222
|
+
CANONICAL_OPTIONS["exclude_methods"] = []
|
223
|
+
CANONICAL_OPTIONS["methods"].each do |synonym|
|
224
|
+
CANONICAL_OPTIONS["exclude_methods"] << "exclude_#{synonym}"
|
225
|
+
end
|
226
|
+
CANONICAL_OPTIONS["exclude_pointcuts"] = ["exclude_pointcut"] +
|
227
|
+
%w[for on in within].map {|prefix| ["exclude_#{prefix}_pointcuts", "exclude_#{prefix}_pointcut"]}.flatten
|
228
|
+
|
218
229
|
ATTRIBUTE_OPTIONS = %w[reading writing changing]
|
219
230
|
|
220
231
|
ALL_ALLOWED_OPTIONS = ATTRIBUTE_OPTIONS +
|
@@ -339,8 +350,18 @@ module Aquarium
|
|
339
350
|
def init_join_points
|
340
351
|
@join_points_matched = Set.new
|
341
352
|
@join_points_not_matched = Set.new
|
342
|
-
|
343
|
-
|
353
|
+
types = candidate_types - candidate_types_excluded
|
354
|
+
method_names = make_method_names
|
355
|
+
attribute_method_names = make_attribute_method_names
|
356
|
+
unless types.empty?
|
357
|
+
find_join_points_for(:type, types, method_names) unless method_names.empty?
|
358
|
+
find_join_points_for(:type, types, attribute_method_names) unless attribute_method_names.empty?
|
359
|
+
end
|
360
|
+
unless candidate_objects.empty?
|
361
|
+
find_join_points_for(:object, candidate_objects, method_names) unless method_names.empty?
|
362
|
+
find_join_points_for(:object, candidate_objects, attribute_method_names) unless attribute_method_names.empty?
|
363
|
+
end
|
364
|
+
subtract_attribute_writers if attributes_read_only?
|
344
365
|
add_join_points_for_candidate_join_points
|
345
366
|
remove_excluded_join_points
|
346
367
|
end
|
@@ -364,7 +385,7 @@ module Aquarium
|
|
364
385
|
def find_methods_for type_or_object_sym, candidates, which_methods
|
365
386
|
return Aquarium::Finders::FinderResult::NIL_OBJECT if candidates.matched.size == 0
|
366
387
|
Aquarium::Finders::MethodFinder.new.find type_or_object_sym => candidates.matched_keys,
|
367
|
-
:methods => which_methods,
|
388
|
+
:methods => which_methods.to_a,
|
368
389
|
:exclude_methods => @specification[:exclude_methods],
|
369
390
|
:options => method_options
|
370
391
|
end
|
@@ -385,64 +406,71 @@ module Aquarium
|
|
385
406
|
end
|
386
407
|
end
|
387
408
|
|
409
|
+
def subtract_attribute_writers
|
410
|
+
@join_points_matched.reject! do |jp|
|
411
|
+
jp.method_name.to_s[-1..-1] == '='
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
388
415
|
def is_instance_methods?
|
389
416
|
not @specification[:method_options].include? :class
|
390
417
|
end
|
391
418
|
|
392
|
-
def
|
393
|
-
@specification[:methods]
|
394
|
-
make_attribute_method_names(@specification[:attributes], @specification[:attribute_options]) -
|
395
|
-
@specification[:exclude_methods]
|
419
|
+
def make_method_names
|
420
|
+
@specification[:methods] - @specification[:exclude_methods]
|
396
421
|
end
|
397
422
|
|
398
|
-
def make_attribute_method_names
|
399
|
-
readers = make_attribute_readers
|
400
|
-
return readers if
|
423
|
+
def make_attribute_method_names
|
424
|
+
readers = make_attribute_readers
|
425
|
+
return readers if attributes_read_only?
|
401
426
|
|
402
427
|
writers = make_attribute_writers readers
|
403
|
-
return writers if
|
428
|
+
return writers if attributes_write_only?
|
404
429
|
return readers + writers
|
405
430
|
end
|
406
431
|
|
407
|
-
|
408
|
-
|
432
|
+
# Because Ruby 1.8 regexp library doesn't support negative look behinds, we really
|
433
|
+
# can't set the regular expression to exclude a trailing = reliably. Instead,
|
434
|
+
# #init_join_points above will remove any writer methods, if necessary.
|
435
|
+
def make_attribute_readers
|
436
|
+
readers = @specification[:attributes].map do |regexp_or_name|
|
437
|
+
expr1 = regexp_or_name.kind_of?(Regexp) ? regexp_or_name.source : regexp_or_name.to_s
|
438
|
+
expr = remove_trailing_equals_and_or_dollar(remove_leading_colon_or_at_sign(expr1))
|
409
439
|
if regexp_or_name.kind_of? Regexp
|
410
|
-
|
411
|
-
Regexp.new(remove_leading_colon_or_at_sign(exp + '.*\b$'))
|
440
|
+
Regexp.new(remove_leading_colon_or_at_sign(expr))
|
412
441
|
else
|
413
|
-
|
414
|
-
remove_leading_colon_or_at_sign(exp.to_s)
|
442
|
+
expr
|
415
443
|
end
|
416
444
|
end
|
417
445
|
Set.new(readers.sort_by {|exp| exp.to_s})
|
418
446
|
end
|
419
|
-
|
420
|
-
def make_attribute_writers
|
421
|
-
writers =
|
447
|
+
|
448
|
+
def make_attribute_writers reader_methods
|
449
|
+
writers = reader_methods.map do |regexp_or_name|
|
450
|
+
expr = regexp_or_name.kind_of?(Regexp) ? regexp_or_name.source : regexp_or_name.to_s
|
422
451
|
if regexp_or_name.kind_of? Regexp
|
423
|
-
|
424
|
-
Regexp.new(remove_trailing_equals_and_or_dollar(regexp_or_name.source) + '=$')
|
452
|
+
Regexp.new(expr+'.*=$')
|
425
453
|
else
|
426
|
-
|
454
|
+
expr + '='
|
427
455
|
end
|
428
456
|
end
|
429
457
|
Set.new(writers.sort_by {|exp| exp.to_s})
|
430
458
|
end
|
431
459
|
|
432
|
-
def
|
433
|
-
read_option
|
460
|
+
def attributes_read_only?
|
461
|
+
read_option && !write_option
|
434
462
|
end
|
435
463
|
|
436
|
-
def
|
437
|
-
write_option
|
464
|
+
def attributes_write_only?
|
465
|
+
write_option && !read_option
|
438
466
|
end
|
439
467
|
|
440
|
-
def read_option
|
441
|
-
attribute_options.include?(:readers) or attribute_options.include?(:reader)
|
468
|
+
def read_option
|
469
|
+
@specification[:attribute_options].include?(:readers) or @specification[:attribute_options].include?(:reader)
|
442
470
|
end
|
443
471
|
|
444
|
-
def write_option
|
445
|
-
attribute_options.include?(:writers) or attribute_options.include?(:writer)
|
472
|
+
def write_option
|
473
|
+
@specification[:attribute_options].include?(:writers) or @specification[:attribute_options].include?(:writer)
|
446
474
|
end
|
447
475
|
|
448
476
|
def method_options
|
@@ -108,6 +108,7 @@ module Aquarium
|
|
108
108
|
|
109
109
|
NIL_OBJECT = MethodFinder.new unless const_defined?(:NIL_OBJECT)
|
110
110
|
|
111
|
+
# TODO move Pointcut's options to here.
|
111
112
|
CANONICAL_OPTIONS = {
|
112
113
|
"types" => %w[type for_type for_types on_type on_types in_type in_types within_type within_types],
|
113
114
|
"objects" => %w[object for_object for_objects on_object on_objects in_object in_objects within_object within_objects],
|
@@ -118,6 +118,7 @@ module Aquarium
|
|
118
118
|
result - excluded
|
119
119
|
end
|
120
120
|
|
121
|
+
|
121
122
|
protected
|
122
123
|
|
123
124
|
def handle_errors unknown_options, input_type_nil
|
@@ -152,8 +153,8 @@ module Aquarium
|
|
152
153
|
result
|
153
154
|
end
|
154
155
|
|
155
|
-
def find_namespace_matched
|
156
|
-
expr =
|
156
|
+
def find_namespace_matched regexp, option
|
157
|
+
expr = regexp.source
|
157
158
|
return nil if expr.empty?
|
158
159
|
found_types = [Object]
|
159
160
|
split_expr = expr.split("::")
|
@@ -162,22 +163,25 @@ module Aquarium
|
|
162
163
|
found_types = find_next_types found_types, subexp, (index == 0), (index == (split_expr.size - 1))
|
163
164
|
break if found_types.size == 0
|
164
165
|
end
|
166
|
+
# JRuby returns types that aren't actually defined by the enclosing namespace.
|
167
|
+
# As a sanity check, reject types whose names don't match the full regexp.
|
168
|
+
found_types.reject! {|t| t.name !~ regexp}
|
165
169
|
if found_types.size > 0
|
166
170
|
finish_and_make_successful_result found_types, option
|
167
171
|
else
|
168
|
-
make_failed_result
|
172
|
+
make_failed_result regexp
|
169
173
|
end
|
170
174
|
end
|
171
175
|
|
172
176
|
# For a name (not a regular expression), return the corresponding type.
|
173
177
|
# (Adapted from the RubyQuiz #113 solution by James Edward Gray II)
|
178
|
+
# See also this blog: http://blog.sidu.in/2008/02/loading-classes-from-strings-in-ruby.html
|
179
|
+
# I discovered that eval works fine with JRuby wrapper classes, while splitting on '::' and
|
180
|
+
# calling const_get on each module fails!
|
174
181
|
def find_by_name type_name, option
|
175
|
-
name = type_name.to_s # in case it's a symbol...
|
176
|
-
return nil if name.nil? || name.strip.empty?
|
177
|
-
name.strip!
|
178
182
|
begin
|
179
|
-
found =
|
180
|
-
finish_and_make_successful_result found, option
|
183
|
+
found = eval type_name.to_s, binding, __FILE__, __LINE__
|
184
|
+
finish_and_make_successful_result [found], option
|
181
185
|
rescue NameError
|
182
186
|
make_failed_result type_name
|
183
187
|
end
|