aquarium 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|