aquarium 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +4 -0
- data/EXAMPLES.rd +4 -0
- data/MIT-LICENSE +20 -0
- data/README +250 -0
- data/RELEASE-PLAN +1 -0
- data/Rakefile +236 -0
- data/UPGRADE +3 -0
- data/examples/aspect_design_example.rb +36 -0
- data/examples/design_by_contract_example.rb +88 -0
- data/examples/method_missing_example.rb +44 -0
- data/examples/method_tracing_example.rb +64 -0
- data/lib/aquarium.rb +7 -0
- data/lib/aquarium/aspects.rb +6 -0
- data/lib/aquarium/aspects/advice.rb +189 -0
- data/lib/aquarium/aspects/aspect.rb +577 -0
- data/lib/aquarium/aspects/default_object_handler.rb +27 -0
- data/lib/aquarium/aspects/dsl.rb +1 -0
- data/lib/aquarium/aspects/dsl/aspect_dsl.rb +61 -0
- data/lib/aquarium/aspects/join_point.rb +158 -0
- data/lib/aquarium/aspects/pointcut.rb +254 -0
- data/lib/aquarium/aspects/pointcut_composition.rb +36 -0
- data/lib/aquarium/extensions.rb +5 -0
- data/lib/aquarium/extensions/hash.rb +85 -0
- data/lib/aquarium/extensions/regexp.rb +20 -0
- data/lib/aquarium/extensions/set.rb +49 -0
- data/lib/aquarium/extensions/string.rb +13 -0
- data/lib/aquarium/extensions/symbol.rb +22 -0
- data/lib/aquarium/extras.rb +4 -0
- data/lib/aquarium/extras/design_by_contract.rb +64 -0
- data/lib/aquarium/finders.rb +4 -0
- data/lib/aquarium/finders/finder_result.rb +121 -0
- data/lib/aquarium/finders/method_finder.rb +228 -0
- data/lib/aquarium/finders/object_finder.rb +74 -0
- data/lib/aquarium/finders/type_finder.rb +127 -0
- data/lib/aquarium/utils.rb +9 -0
- data/lib/aquarium/utils/array_utils.rb +29 -0
- data/lib/aquarium/utils/hash_utils.rb +28 -0
- data/lib/aquarium/utils/html_escaper.rb +17 -0
- data/lib/aquarium/utils/invalid_options.rb +9 -0
- data/lib/aquarium/utils/method_utils.rb +18 -0
- data/lib/aquarium/utils/nil_object.rb +13 -0
- data/lib/aquarium/utils/set_utils.rb +32 -0
- data/lib/aquarium/version.rb +30 -0
- data/rake_tasks/examples.rake +7 -0
- data/rake_tasks/examples_specdoc.rake +8 -0
- data/rake_tasks/examples_with_rcov.rake +8 -0
- data/rake_tasks/verify_rcov.rake +7 -0
- data/spec/aquarium/aspects/advice_chain_node_spec.rb +34 -0
- data/spec/aquarium/aspects/advice_spec.rb +103 -0
- data/spec/aquarium/aspects/aspect_invocation_spec.rb +111 -0
- data/spec/aquarium/aspects/aspect_spec.rb +978 -0
- data/spec/aquarium/aspects/aspect_with_nested_types_spec.rb +129 -0
- data/spec/aquarium/aspects/concurrent_aspects_spec.rb +423 -0
- data/spec/aquarium/aspects/concurrent_aspects_with_objects_and_types_spec.rb +103 -0
- data/spec/aquarium/aspects/concurrently_accessed.rb +21 -0
- data/spec/aquarium/aspects/dsl/aspect_dsl_spec.rb +514 -0
- data/spec/aquarium/aspects/join_point_spec.rb +302 -0
- data/spec/aquarium/aspects/pointcut_and_composition_spec.rb +131 -0
- data/spec/aquarium/aspects/pointcut_or_composition_spec.rb +111 -0
- data/spec/aquarium/aspects/pointcut_spec.rb +800 -0
- data/spec/aquarium/extensions/hash_spec.rb +187 -0
- data/spec/aquarium/extensions/regex_spec.rb +40 -0
- data/spec/aquarium/extensions/set_spec.rb +105 -0
- data/spec/aquarium/extensions/string_spec.rb +25 -0
- data/spec/aquarium/extensions/symbol_spec.rb +37 -0
- data/spec/aquarium/extras/design_by_contract_spec.rb +68 -0
- data/spec/aquarium/finders/finder_result_spec.rb +359 -0
- data/spec/aquarium/finders/method_finder_spec.rb +878 -0
- data/spec/aquarium/finders/method_sorting_spec.rb +16 -0
- data/spec/aquarium/finders/object_finder_spec.rb +230 -0
- data/spec/aquarium/finders/type_finder_spec.rb +210 -0
- data/spec/aquarium/spec_example_classes.rb +117 -0
- data/spec/aquarium/spec_helper.rb +3 -0
- data/spec/aquarium/utils/array_utils_spec.rb +47 -0
- data/spec/aquarium/utils/hash_utils_spec.rb +48 -0
- data/spec/aquarium/utils/html_escaper_spec.rb +18 -0
- data/spec/aquarium/utils/method_utils_spec.rb +50 -0
- data/spec/aquarium/utils/nil_object_spec.rb +19 -0
- data/spec/aquarium/utils/set_utils_spec.rb +60 -0
- metadata +132 -0
data/UPGRADE
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Example demonstrating emerging ideas about good aspect-oriented design. Specifically, this
|
3
|
+
# example follows ideas of Jonathan Aldrich on "Open Modules", where a "module" (in the generic
|
4
|
+
# sense of the word...) is responsible for defining and maintaining the pointcuts that it is
|
5
|
+
# willing to expose to potential aspects. Aspects are only allowed to advise the module through
|
6
|
+
# the pointcut. (Enforcing this constraint is TBD)
|
7
|
+
# Griswold, Sullivan, and collaborators have expanded on these ideas. See their IEEE Software,
|
8
|
+
# March 2006 paper.
|
9
|
+
|
10
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
11
|
+
require 'aquarium'
|
12
|
+
|
13
|
+
module Aquarium
|
14
|
+
class ClassWithStateAndBehavior
|
15
|
+
def initialize *args
|
16
|
+
@state = args
|
17
|
+
p "Initializing: #{args.inspect}"
|
18
|
+
end
|
19
|
+
attr_accessor :state
|
20
|
+
|
21
|
+
# A simpler version of the following would be
|
22
|
+
# STATE_CHANGE = pointcut :method => :state
|
23
|
+
STATE_CHANGE = pointcut :attribute => :state, :attribute_options => :writer
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Observe state changes in the class, using the class-defined pointcut.
|
28
|
+
|
29
|
+
observer = after :pointcut => Aquarium::ClassWithStateAndBehavior::STATE_CHANGE do |jp, *args|
|
30
|
+
p "State has changed. "
|
31
|
+
p " New state is #{jp.context.advised_object.state.inspect}"
|
32
|
+
p " Equivalent to *args: #{args.inspect}"
|
33
|
+
end
|
34
|
+
|
35
|
+
object = Aquarium::ClassWithStateAndBehavior.new(:a1, :a2, :a3)
|
36
|
+
object.state = [:b1, :b2]
|
@@ -0,0 +1,88 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Example demonstrating "Design by Contract", Bertrand Meyer's idea for programmatically-
|
3
|
+
# specifying the contract of use for a class or module and testing it at runtime (usually
|
4
|
+
# during the testing process)
|
5
|
+
# This example is adapted from spec/extras/design_by_contract_spec.rb.
|
6
|
+
# Note: the DesignByContract module uses the AspectDSL module. The #precondition, #postcondition,
|
7
|
+
# and #invariant methods shown below delegate to AspectDSL methods. Those methods implicitly use
|
8
|
+
# "self" as the :object to advise.
|
9
|
+
|
10
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
11
|
+
require 'aquarium/extras/design_by_contract'
|
12
|
+
|
13
|
+
module Aquarium
|
14
|
+
class PreCond
|
15
|
+
def action *args
|
16
|
+
p "inside :action"
|
17
|
+
end
|
18
|
+
|
19
|
+
precondition :method => :action, :message => "Must pass more than one argument." do |jp, *args|
|
20
|
+
args.size > 0
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
p "This call will fail because the precondition is not satisfied:"
|
26
|
+
begin
|
27
|
+
Aquarium::PreCond.new.action
|
28
|
+
rescue Aquarium::Extras::DesignByContract::ContractError => e
|
29
|
+
p e.inspect
|
30
|
+
end
|
31
|
+
p "This call will pass because the precondition is satisfied:"
|
32
|
+
Aquarium::PreCond.new.action :a1
|
33
|
+
|
34
|
+
module Aquarium
|
35
|
+
class PostCond
|
36
|
+
def action *args
|
37
|
+
p "inside :action"
|
38
|
+
end
|
39
|
+
|
40
|
+
postcondition :method => :action,
|
41
|
+
:message => "Must pass more than one argument and first argument must be non-empty." do |jp, *args|
|
42
|
+
args.size > 0 && ! args[0].empty?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
p "These two calls will fail because the postcondition is not satisfied:"
|
48
|
+
begin
|
49
|
+
Aquarium::PostCond.new.action
|
50
|
+
rescue Aquarium::Extras::DesignByContract::ContractError => e
|
51
|
+
p e.inspect
|
52
|
+
end
|
53
|
+
begin
|
54
|
+
Aquarium::PostCond.new.action ""
|
55
|
+
rescue Aquarium::Extras::DesignByContract::ContractError => e
|
56
|
+
p e.inspect
|
57
|
+
end
|
58
|
+
p "This call will pass because the postcondition is satisfied:"
|
59
|
+
Aquarium::PostCond.new.action :a1
|
60
|
+
|
61
|
+
module Aquarium
|
62
|
+
class InvarCond
|
63
|
+
def initialize
|
64
|
+
@invar = 0
|
65
|
+
end
|
66
|
+
attr_reader :invar
|
67
|
+
def good_action
|
68
|
+
p "inside :good_action"
|
69
|
+
end
|
70
|
+
def bad_action
|
71
|
+
p "inside :bad_action"
|
72
|
+
@invar = 1
|
73
|
+
end
|
74
|
+
|
75
|
+
invariant :methods => /action$/, :message => "Must not change the @invar value." do |jp, *args|
|
76
|
+
jp.context.advised_object.invar == 0
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
p "This call will fail because the invariant is not satisfied:"
|
82
|
+
begin
|
83
|
+
Aquarium::InvarCond.new.bad_action
|
84
|
+
rescue Aquarium::Extras::DesignByContract::ContractError => e
|
85
|
+
p e.inspect
|
86
|
+
end
|
87
|
+
p "This call will pass because the invariant is satisfied:"
|
88
|
+
Aquarium::InvarCond.new.good_action
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Example demonstrating "around" advice for method_missing. This is a technique for
|
3
|
+
# avoiding collisions when different toolkits want to override method_missing in the
|
4
|
+
# same classes, e.g., Object. Using around advice as shown allows a toolkit to add
|
5
|
+
# custom behavior while invoking the "native" method_missing to handle unrecognized
|
6
|
+
# method calls.
|
7
|
+
# Note that it is essential to use around advice, not before or after advice, because
|
8
|
+
# neither can prevent the call to the "wrapped" method_missing, which is presumably
|
9
|
+
# not what you want.
|
10
|
+
# In this (contrived) example, an Echo class uses method_missing to simply echo
|
11
|
+
# the method name and arguments. An aspect is used to intercept any calls to a
|
12
|
+
# fictitious "log" method and handle those in a different way.
|
13
|
+
|
14
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
15
|
+
require 'aquarium'
|
16
|
+
|
17
|
+
module Aquarium
|
18
|
+
class Echo
|
19
|
+
def method_missing sym, *args
|
20
|
+
p "Echoing: #{sym.to_s}: #{args.join(" ")}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
p "Before advising Echo:"
|
26
|
+
echo1 = Aquarium::Echo.new
|
27
|
+
echo1.say "hello", "world!"
|
28
|
+
echo1.log "something", "interesting..."
|
29
|
+
echo1.shout "theater", "in", "a", "crowded", "firehouse!"
|
30
|
+
|
31
|
+
around :type => Aquarium::Echo, :method => :method_missing do |join_point, sym, *args|
|
32
|
+
if sym == :log
|
33
|
+
p "--- Sending to log: #{args.join(" ")}"
|
34
|
+
else
|
35
|
+
join_point.proceed
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
p "After advising Echo:"
|
40
|
+
echo2 = Aquarium::Echo.new
|
41
|
+
echo2.say "hello", "world!"
|
42
|
+
echo2.log "something", "interesting..."
|
43
|
+
echo2.shout "theater", "in", "a", "crowded", "firehouse!"
|
44
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Example demonstrating "around" advice that traces calls to all methods in
|
3
|
+
# classes Foo and Bar
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
6
|
+
require 'aquarium'
|
7
|
+
|
8
|
+
module Aquarium
|
9
|
+
class Foo
|
10
|
+
def initialize *args
|
11
|
+
p "Inside: Foo#initialize: args = #{args.inspect}"
|
12
|
+
end
|
13
|
+
def do_it *args
|
14
|
+
p "Inside: Foo#do_it: args = #{args.inspect}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module BarModule
|
19
|
+
def initialize *args
|
20
|
+
p "Inside: BarModule#initialize: args = #{args.inspect}"
|
21
|
+
end
|
22
|
+
def do_something_else *args
|
23
|
+
p "Inside: BarModule#do_something_else: args = #{args.inspect}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Bar
|
28
|
+
include BarModule
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
p "Before advising the methods:"
|
33
|
+
foo1 = Aquarium::Foo.new :a1, :a2
|
34
|
+
foo1.do_it :b1, :b2
|
35
|
+
|
36
|
+
bar1 = Aquarium::Bar.new :a3, :a4
|
37
|
+
bar1.do_something_else :b3, :b4
|
38
|
+
|
39
|
+
around :types => [Aquarium::Foo, Aquarium::Bar], :methods => :all, :method_options => :suppress_ancestor_methods do |execution_point, *args|
|
40
|
+
p "Entering: #{execution_point.type.name}##{execution_point.method_name}: args = #{args.inspect}"
|
41
|
+
execution_point.proceed
|
42
|
+
p "Leaving: #{execution_point.type.name}##{execution_point.method_name}: args = #{args.inspect}"
|
43
|
+
end
|
44
|
+
|
45
|
+
p "After advising the methods. Notice that #intialize isn't advised:"
|
46
|
+
foo2 = Aquarium::Foo.new :a5, :a6
|
47
|
+
foo2.do_it :b5, :b6
|
48
|
+
|
49
|
+
bar1 = Aquarium::Bar.new :a7, :a8
|
50
|
+
bar1.do_something_else :b7, :b8
|
51
|
+
|
52
|
+
around :types => [Aquarium::Foo, Aquarium::Bar], :methods => :initialize, :method_options => :private do |execution_point, *args|
|
53
|
+
p "Entering: #{execution_point.type.name}##{execution_point.method_name}: args = #{args.inspect}"
|
54
|
+
execution_point.proceed
|
55
|
+
p "Leaving: #{execution_point.type.name}##{execution_point.method_name}: args = #{args.inspect}"
|
56
|
+
end
|
57
|
+
|
58
|
+
p "After advising the private methods. Notice that #intialize is advised:"
|
59
|
+
foo2 = Aquarium::Foo.new :a9, :a10
|
60
|
+
foo2.do_it :b9, :b10
|
61
|
+
|
62
|
+
bar1 = Aquarium::Bar.new :a11, :a12
|
63
|
+
bar1.do_something_else :b11, :b12
|
64
|
+
|
data/lib/aquarium.rb
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
require 'aquarium/utils/array_utils'
|
2
|
+
require 'aquarium/extensions/string'
|
3
|
+
require 'aquarium/extensions/symbol'
|
4
|
+
require 'aquarium/utils/invalid_options'
|
5
|
+
require 'aquarium/utils/nil_object'
|
6
|
+
|
7
|
+
module Aquarium
|
8
|
+
module Aspects
|
9
|
+
module Advice
|
10
|
+
def self.kinds_in_priority_order
|
11
|
+
[:around, :before, :after, :after_returning, :after_raising]
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.kinds; self.kinds_in_priority_order; end
|
15
|
+
|
16
|
+
def self.sort_by_priority_order advice_kinds
|
17
|
+
advice_kinds.sort do |x,y|
|
18
|
+
self.kinds_in_priority_order.index(x.to_sym) <=> self.kinds_in_priority_order.index(y.to_sym)
|
19
|
+
end.map {|x| x.to_sym}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Supports Enumerable, but not the sorting methods, as this class is a linked list structure.
|
24
|
+
# This is of limited usefulness, because you wouldn't use an iterator to invoke the procs
|
25
|
+
# in the chain, because each proc will invoke the next node arbitrarily or possibly not at all
|
26
|
+
# in the case of around advice!
|
27
|
+
class AdviceChainNode
|
28
|
+
include Enumerable
|
29
|
+
def initialize options = {}, &proc_block
|
30
|
+
@proc = Proc.new &proc_block
|
31
|
+
options[:next_node] ||= nil # assign so the attribute is always created
|
32
|
+
options.each do |key, value|
|
33
|
+
instance_variable_set "@#{key}".intern, value
|
34
|
+
(class << self; self; end).class_eval <<-EOF
|
35
|
+
attr_accessor(:#{key})
|
36
|
+
EOF
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def call jp, *args
|
41
|
+
begin
|
42
|
+
@proc.call jp, *args
|
43
|
+
rescue => e
|
44
|
+
class_or_instance_method_separater = jp.is_instance_method? ? "#" : "."
|
45
|
+
context_message = "Exception raised while executing \"#{jp.context.advice_kind}\" advice for \"#{jp.type_or_object.inspect}#{class_or_instance_method_separater}#{jp.method_name}\": "
|
46
|
+
backtrace = e.backtrace
|
47
|
+
e2 = e.exception(context_message + e.message)
|
48
|
+
e2.set_backtrace backtrace
|
49
|
+
raise e2
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Supports Enumerable
|
54
|
+
def each
|
55
|
+
node = self
|
56
|
+
while node.nil? == false
|
57
|
+
yield node
|
58
|
+
node = node.next_node
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def size
|
63
|
+
inject(0) {|memo, node| memo += 1}
|
64
|
+
end
|
65
|
+
|
66
|
+
def empty?
|
67
|
+
next_node.nil?
|
68
|
+
end
|
69
|
+
|
70
|
+
def inspect &block
|
71
|
+
block ? yield(self) : super
|
72
|
+
end
|
73
|
+
|
74
|
+
NIL_OBJECT = Aquarium::Utils::NilObject.new
|
75
|
+
end
|
76
|
+
|
77
|
+
class NoAdviceChainNode < AdviceChainNode
|
78
|
+
# Note that we extract the block passed to the original method call, if any,
|
79
|
+
# from the context and pass it to method invocation.
|
80
|
+
def initialize options = {}
|
81
|
+
super(options) { |jp, *args|
|
82
|
+
block_for_method = jp.context.block_for_method
|
83
|
+
invoking_object = jp.is_instance_method? ? jp.context.advised_object : jp.type
|
84
|
+
method = invoking_object.method(@alias_method_name)
|
85
|
+
block_for_method.nil? ?
|
86
|
+
method.call(*args) :
|
87
|
+
method.call(*args, &block_for_method)
|
88
|
+
}
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class BeforeAdviceChainNode < AdviceChainNode
|
93
|
+
def initialize options = {}
|
94
|
+
super(options) { |jp, *args|
|
95
|
+
before_jp = jp.make_current_context_join_point :advice_kind => :before
|
96
|
+
advice.call(before_jp, *args)
|
97
|
+
next_node.call(jp, *args)
|
98
|
+
}
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class AfterReturningAdviceChainNode < AdviceChainNode
|
103
|
+
def initialize options = {}
|
104
|
+
super(options) { |jp, *args|
|
105
|
+
returned_value = next_node.call(jp, *args)
|
106
|
+
next_jp = jp.make_current_context_join_point :advice_kind => :after_returning, :returned_value => returned_value
|
107
|
+
advice.call(next_jp, *args)
|
108
|
+
next_jp.context.returned_value # allow advice to modify the returned value
|
109
|
+
}
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Note that the advice is not invoked if the exception is not of a type specified when the advice was created.
|
114
|
+
# However, the default is to advise all thrown objects.
|
115
|
+
class AfterRaisingAdviceChainNode < AdviceChainNode
|
116
|
+
include Aquarium::Utils::ArrayUtils
|
117
|
+
def initialize options = {}
|
118
|
+
super(options) { |jp, *args|
|
119
|
+
begin
|
120
|
+
next_node.call(jp, *args)
|
121
|
+
rescue Object => raised_exception
|
122
|
+
if after_raising_exceptions_list_includes raised_exception
|
123
|
+
next_jp = jp.make_current_context_join_point :advice_kind => :after_raising, :raised_exception => raised_exception
|
124
|
+
advice.call(next_jp, *args)
|
125
|
+
raised_exception = next_jp.context.raised_exception # allow advice to modify raised exception
|
126
|
+
end
|
127
|
+
raise raised_exception
|
128
|
+
end
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
def after_raising_exceptions_list_includes raised_exception
|
134
|
+
after_raising_exceptions_list.find {|x| raised_exception.kind_of? x}
|
135
|
+
end
|
136
|
+
|
137
|
+
def after_raising_exceptions_list
|
138
|
+
list = make_array(@after_raising)
|
139
|
+
(list.nil? || list.empty? || (list.size == 1 && list[0] == "")) ? [Object] : list
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
class AfterAdviceChainNode < AdviceChainNode
|
144
|
+
def initialize options = {}
|
145
|
+
super(options) { |jp, *args|
|
146
|
+
# advice.call is invoked in each bloc, rather than once in an "ensure" clause, so the invocation in the rescue class
|
147
|
+
# can allow the advice to change the exception that will be raised.
|
148
|
+
begin
|
149
|
+
returned_value = next_node.call(jp, *args)
|
150
|
+
next_jp = jp.make_current_context_join_point :advice_kind => :after, :returned_value => returned_value
|
151
|
+
advice.call(next_jp, *args)
|
152
|
+
next_jp.context.returned_value # allow advice to modify the returned value
|
153
|
+
rescue Object => raised_exception
|
154
|
+
next_jp = jp.make_current_context_join_point :advice_kind => :after, :raised_exception => raised_exception
|
155
|
+
advice.call(next_jp, *args)
|
156
|
+
raise next_jp.context.raised_exception
|
157
|
+
end
|
158
|
+
}
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
class AroundAdviceChainNode < AdviceChainNode
|
163
|
+
def initialize options = {}
|
164
|
+
super(options) { |jp, *args|
|
165
|
+
around_jp = jp.make_current_context_join_point :advice_kind => :around, :proceed_proc => next_node
|
166
|
+
advice.call(around_jp, *args)
|
167
|
+
}
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# The advice_kind argument must be one of the values returned by Advice.kinds or one of the special values
|
172
|
+
# ":no" or ":none", signfying a node for which there is no advice, where the actual method being advised is
|
173
|
+
# called directly instead. This kind of node is normally used as the terminal leaf in the chain.
|
174
|
+
module AdviceChainNodeFactory
|
175
|
+
def self.make_node options = {}
|
176
|
+
advice_kind = options[:advice_kind]
|
177
|
+
raise Aquarium::Utils::InvalidOptions.new("Unknown advice kind specified: #{advice_kind}") unless valid(advice_kind)
|
178
|
+
advice_kind = :no if advice_kind == :none
|
179
|
+
advice_chain_node_name = advice_kind.to_s.to_camel_case + "AdviceChainNode"
|
180
|
+
clazz = Aquarium::Aspects.const_get advice_chain_node_name
|
181
|
+
clazz.new options
|
182
|
+
end
|
183
|
+
|
184
|
+
def self.valid advice_kind
|
185
|
+
advice_kind == :no || advice_kind == :none || Advice.kinds.include?(advice_kind)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|