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
@@ -0,0 +1,577 @@
|
|
1
|
+
require 'aquarium/extensions'
|
2
|
+
require 'aquarium/utils'
|
3
|
+
require 'aquarium/aspects/advice'
|
4
|
+
require 'aquarium/aspects/join_point'
|
5
|
+
require 'aquarium/aspects/pointcut'
|
6
|
+
require 'aquarium/aspects/pointcut_composition'
|
7
|
+
require 'aquarium/aspects/default_object_handler'
|
8
|
+
|
9
|
+
module Aquarium
|
10
|
+
module Aspects
|
11
|
+
|
12
|
+
# == Aspect
|
13
|
+
# Aspects "advise" one or more method invocations for one or more types or objects
|
14
|
+
# (including class methods on types). The corresponding advice is a Proc that is
|
15
|
+
# invoked either before the join point, after it returns, after it raises an exception,
|
16
|
+
# after either event, or around the join point, meaning the advice runs and it decides
|
17
|
+
# when and if to invoke the advised method. (Hence, around advice can run code before
|
18
|
+
# and after the join point call and it can "veto" the actual join point call).
|
19
|
+
#
|
20
|
+
# See also Aquarium::Aspects::DSL::AspectDsl for more information.
|
21
|
+
class Aspect
|
22
|
+
include DefaultObjectHandler
|
23
|
+
include Aquarium::Utils::ArrayUtils
|
24
|
+
include Aquarium::Utils::HashUtils
|
25
|
+
include Aquarium::Utils::HtmlEscaper
|
26
|
+
|
27
|
+
attr_accessor :verbose, :log
|
28
|
+
attr_reader :specification, :pointcuts, :advice
|
29
|
+
|
30
|
+
# Aspect.new (:around | :before | :after | :after_returning | :after_raising ) \
|
31
|
+
# (:pointcuts => [...]), | \
|
32
|
+
# ((:types => [...] | :objects => [...]),
|
33
|
+
# :methods => [], :method_options => [...], \
|
34
|
+
# :attributes => [...], :attribute_options[...]), \
|
35
|
+
# (:advice = advice | do |join_point, *args| ...; end)
|
36
|
+
#
|
37
|
+
# where the parameters often have many synonyms (mostly to support a "humane
|
38
|
+
# interface") and they are interpreted as followed:
|
39
|
+
#
|
40
|
+
# <tt>:around</tt>::
|
41
|
+
# Invoke the specified advice "around" the join points. It is up to the advice
|
42
|
+
# itself to call <tt>join_point.proceed</tt> (where <tt>join_point</tt> is the
|
43
|
+
# first argument passed to the advice) if it wants the advised method to actually
|
44
|
+
# execute.
|
45
|
+
#
|
46
|
+
# <tt>:before</tt>::
|
47
|
+
# Invoke the specified advice as before the join point.
|
48
|
+
#
|
49
|
+
# <tt>:after</tt>::
|
50
|
+
# Invoke the specified advice as after the join point either returns successfully
|
51
|
+
# or raises an exception.
|
52
|
+
#
|
53
|
+
# <tt>:after_returning</tt>::
|
54
|
+
# Invoke the specified advice as after the join point returns successfully.
|
55
|
+
#
|
56
|
+
# <tt>:after_raising</tt>::
|
57
|
+
# Invoke the specified advice as after the join point raises an exception.
|
58
|
+
#
|
59
|
+
# <tt>:pointcuts => pointcut || [pointcut_list]</tt>::
|
60
|
+
# <tt>:pointcut => pointcut || [pointcut_list]</tt>::
|
61
|
+
# <tt>:within_pointcut => pointcut || [pointcut_list]</tt>::
|
62
|
+
# <tt>:within_pointcuts => pointcut || [pointcut_list]</tt>::
|
63
|
+
# One or an array of Pointcut objects. Mutually-exclusive with the :types, :objects,
|
64
|
+
# :methods, :attributes, :method_options, and :attribute_options parameters.
|
65
|
+
#
|
66
|
+
# <tt>:types => type || [type_list]</tt>::
|
67
|
+
# <tt>:type => type || [type_list]</tt>::
|
68
|
+
# <tt>:within_type => type || [type_list]</tt>::
|
69
|
+
# <tt>:within_types => type || [type_list]</tt>::
|
70
|
+
# One or an array of types, type names and/or type regular expessions to advise.
|
71
|
+
# All the :types, :objects, :methods, :attributes, :method_options, and :attribute_options
|
72
|
+
# are used to construct Pointcuts internally.
|
73
|
+
#
|
74
|
+
# <tt>:objects => object || [object_list]</tt>::
|
75
|
+
# <tt>:object => object || [object_list]</tt>::
|
76
|
+
# <tt>:within_object => object || [object_list]</tt>::
|
77
|
+
# <tt>:within_objects => object || [object_list]</tt>::
|
78
|
+
# One or an array of objects to advise.
|
79
|
+
#
|
80
|
+
# <tt>:default_object => object</tt>::
|
81
|
+
# An "internal" flag used by the methods that AspectDSL adds to Object. When no object
|
82
|
+
# or type is specified, the value of :default_object will be used, if defined. The
|
83
|
+
# AspectDSL methods set the value to self, so that the user doesn't have to in the
|
84
|
+
# appropriate contexts. This flag is subject to change, so don't use it explicitly!
|
85
|
+
#
|
86
|
+
# <tt>:methods => method || [method_list]</tt>::
|
87
|
+
# <tt>:method => method || [method_list]</tt>::
|
88
|
+
# <tt>:within_method => method || [method_list]</tt>::
|
89
|
+
# <tt>:within_methods => method || [method_list]</tt>::
|
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.
|
94
|
+
#
|
95
|
+
# <tt>:method_options => [options]</tt>::
|
96
|
+
# One or more options supported by Aquarium::Finders::MethodFinder. Defaults to :suppress_ancestor_methods
|
97
|
+
#
|
98
|
+
# <tt>:attributes => attribute || [attribute_list]</tt>::
|
99
|
+
# <tt>:attribute => attribute || [attribute_list]</tt>::
|
100
|
+
# <tt>:within_attribute => attribute || [attribute_list]</tt>::
|
101
|
+
# <tt>:within_attributes => attribute || [attribute_list]</tt>::
|
102
|
+
# One or an array of attribute names and/or regular expessions to match.
|
103
|
+
# This is syntactic sugar for the corresponding attribute readers and/or writers
|
104
|
+
# methods, as specified using the <tt>:attrbute_options. Any matches will be
|
105
|
+
# joined with the matched :methods.</tt>.
|
106
|
+
#
|
107
|
+
# <tt>:attribute_options => [options]</tt>::
|
108
|
+
# One or more of <tt>:readers</tt>, <tt>:reader</tt> (synonymous),
|
109
|
+
# <tt>:writers</tt>, and/or <tt>:writer</tt> (synonymous). By default, both
|
110
|
+
# readers and writers are matched.
|
111
|
+
def initialize *options, &block
|
112
|
+
process_input *options, &block
|
113
|
+
init_pointcuts
|
114
|
+
return if specification[:noop]
|
115
|
+
advise_join_points
|
116
|
+
end
|
117
|
+
|
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
|
124
|
+
end
|
125
|
+
|
126
|
+
def unadvise
|
127
|
+
return if @specification[:noop]
|
128
|
+
@pointcuts.each do |pointcut|
|
129
|
+
interesting_join_points(pointcut).each do |join_point|
|
130
|
+
remove_advice_for_aspect_at join_point
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
alias :unadvise_join_points :unadvise
|
136
|
+
|
137
|
+
def inspect
|
138
|
+
"Aspect: {specification: #{specification.inspect}, pointcuts: #{pointcuts.inspect}, advice: #{advice.inspect}}"
|
139
|
+
end
|
140
|
+
|
141
|
+
alias :to_s :inspect
|
142
|
+
|
143
|
+
# We have to ignore advice in the comparison. As recently discussed in ruby-users, there are very few situations.
|
144
|
+
# where Proc#eql? returns true.
|
145
|
+
def eql? other
|
146
|
+
self.object_id == other.object_id ||
|
147
|
+
(self.class.eql?(other.class) && specification == other.specification && pointcuts == other.pointcuts)
|
148
|
+
end
|
149
|
+
|
150
|
+
alias :== :eql?
|
151
|
+
|
152
|
+
protected
|
153
|
+
|
154
|
+
def process_input *options, &block
|
155
|
+
@original_options = *options
|
156
|
+
make_specification *options, &block
|
157
|
+
@verbose = @specification[:verbose] || false
|
158
|
+
@log = @specification[:log] || ""
|
159
|
+
validate_specification
|
160
|
+
end
|
161
|
+
|
162
|
+
def make_specification *options, &block
|
163
|
+
opts = options.dup
|
164
|
+
rationalize_parameters opts
|
165
|
+
@specification = Aquarium::Utils::MethodUtils.method_args_to_hash(*opts) {|option| ""} # set other hash values to an empty string
|
166
|
+
use_default_object_if_defined unless (types_given? || objects_given? || pointcuts_given?)
|
167
|
+
use_first_nonadvice_symbol_as_method(opts) unless methods_given?
|
168
|
+
@advice = block || @specification[:advice]
|
169
|
+
if @advice.nil? && @specification[:noop].nil?
|
170
|
+
raise Aquarium::Utils::InvalidOptions.new("No advice block or argument specified.")
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def init_pointcuts
|
175
|
+
pointcuts = []
|
176
|
+
if pointcuts_given?
|
177
|
+
pointcuts_given.each do |pointcut|
|
178
|
+
if pointcut.kind_of?(Aquarium::Aspects::Pointcut)
|
179
|
+
pointcuts << pointcut
|
180
|
+
else
|
181
|
+
pointcuts << Aquarium::Aspects::Pointcut.new(pointcut)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
else
|
185
|
+
pc_options = {}
|
186
|
+
pc_options[:types] = types_given.to_a if types_given?
|
187
|
+
pc_options[:objects] = objects_given.to_a if objects_given?
|
188
|
+
pc_options[:methods] = methods_given.to_a if methods_given?
|
189
|
+
pc_options[:method_options] = method_options_given.to_a if method_options_given?
|
190
|
+
pointcuts << Aquarium::Aspects::Pointcut.new(pc_options)
|
191
|
+
end
|
192
|
+
@pointcuts = Set.new(pointcuts)
|
193
|
+
end
|
194
|
+
|
195
|
+
def advise_join_points
|
196
|
+
advice = @advice.to_proc
|
197
|
+
@pointcuts.each do |pointcut|
|
198
|
+
interesting_join_points(pointcut).each do |join_point|
|
199
|
+
type_or_object = Aspect.type_or_object_string join_point.type_or_object
|
200
|
+
add_advice_framework join_point
|
201
|
+
Aquarium::Aspects::Advice.sort_by_priority_order(specified_advice_kinds).reverse.each do |advice_kind|
|
202
|
+
advice_chain = Aspect.get_advice_chain join_point.type_or_object, join_point.method_name
|
203
|
+
add_advice_to_chain join_point, advice_chain, advice_kind, advice
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Ignore any inserted methods that are part of the aspect implementation,
|
210
|
+
# i.e., those that match the Aspect..aspect_method_prefix.
|
211
|
+
def interesting_join_points pointcut
|
212
|
+
pointcut.join_points_matched.reject do |join_point|
|
213
|
+
join_point.method_name.to_s =~ /^#{Aspect.aspect_method_prefix}/
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def add_advice_to_chain join_point, advice_chain, advice_kind, advice
|
218
|
+
options = @specification.merge({
|
219
|
+
:aspect => self,
|
220
|
+
:advice_kind => advice_kind,
|
221
|
+
:advice => advice,
|
222
|
+
:next_node => advice_chain,
|
223
|
+
:static_join_point => join_point})
|
224
|
+
# The returned node becomes the lead node in the chain.
|
225
|
+
advice_chain = Aspect.get_advice_chain join_point.type_or_object, join_point.method_name
|
226
|
+
Aspect.set_advice_chain(join_point.type_or_object, join_point.method_name, Aquarium::Aspects::AdviceChainNodeFactory.make_node(options))
|
227
|
+
advice_chain = Aspect.get_advice_chain join_point.type_or_object, join_point.method_name
|
228
|
+
end
|
229
|
+
|
230
|
+
# Useful for debugging...
|
231
|
+
def self.advice_chain_inspect advice_chain
|
232
|
+
return "[nil]" if advice_chain.nil?
|
233
|
+
"<br/>"+advice_chain.inspect do |advice_chain|
|
234
|
+
"#{advice_chain.class.name}:#{advice_chain.object_id}: aspect = #{advice_chain.aspect.object_id}, next_node = #{advice_chain_inspect advice_chain.next_node}"
|
235
|
+
end.gsub(/\</,"<").gsub(/\>/,">")+"<br/>"
|
236
|
+
end
|
237
|
+
|
238
|
+
def add_advice_framework join_point
|
239
|
+
type_to_advise = join_point.type || (class << join_point.object; self; end)
|
240
|
+
alias_method_name = (saved_method_name join_point).intern
|
241
|
+
return if private_method_defined? join_point.type_or_object, alias_method_name
|
242
|
+
type_to_advise.class_eval(<<-EVAL_WRAPPER, __FILE__, __LINE__)
|
243
|
+
#{static_method_prefix join_point.is_instance_method?}
|
244
|
+
#{alias_original_method_text alias_method_name, join_point}
|
245
|
+
#{static_method_suffix join_point.is_instance_method?}
|
246
|
+
EVAL_WRAPPER
|
247
|
+
Aspect.set_advice_chain join_point.type_or_object, join_point.method_name, Aquarium::Aspects::AdviceChainNodeFactory.make_node(
|
248
|
+
:aspect => nil, # Belongs to all aspects that might advise this join point!
|
249
|
+
:advice_kind => :none,
|
250
|
+
:alias_method_name => alias_method_name,
|
251
|
+
:static_join_point => join_point)
|
252
|
+
advice_chain = Aspect.get_advice_chain join_point.type_or_object, join_point.method_name
|
253
|
+
end
|
254
|
+
|
255
|
+
def static_method_prefix is_instance_method
|
256
|
+
return "" if is_instance_method
|
257
|
+
<<-EOF
|
258
|
+
class << self
|
259
|
+
EOF
|
260
|
+
end
|
261
|
+
|
262
|
+
def static_method_suffix is_instance_method
|
263
|
+
return "" if is_instance_method
|
264
|
+
<<-EOF
|
265
|
+
end
|
266
|
+
EOF
|
267
|
+
end
|
268
|
+
|
269
|
+
# Note that we make the alias for the original method private, so it doesn't pollute the "interface"
|
270
|
+
# of the advised classes. This also means that we have to use the Class.method(name).call()
|
271
|
+
# idiom when invoking it.
|
272
|
+
def alias_original_method_text alias_method_name, join_point
|
273
|
+
self_name = join_point.type.nil? ? "self" : join_point.type.name
|
274
|
+
target_self = join_point.is_instance_method? ? "self" : join_point.type.name
|
275
|
+
<<-EOF
|
276
|
+
alias_method :#{alias_method_name}, :#{join_point.method_name}
|
277
|
+
def #{join_point.method_name} *args, &block_for_method
|
278
|
+
advice_chain = Aspect.get_advice_chain #{self_name}, :#{join_point.method_name}
|
279
|
+
static_join_point = advice_chain.static_join_point
|
280
|
+
advice_join_point = Aspect.make_advice_join_point static_join_point, #{target_self}, args, block_for_method
|
281
|
+
advice_chain.call advice_join_point, *args
|
282
|
+
end
|
283
|
+
private :#{alias_method_name}
|
284
|
+
EOF
|
285
|
+
end
|
286
|
+
|
287
|
+
def unalias_original_method_text alias_method_name, join_point
|
288
|
+
self_name = join_point.type.nil? ? "self" : join_point.type.name
|
289
|
+
<<-EOF
|
290
|
+
alias_method :#{join_point.method_name}, :#{alias_method_name}
|
291
|
+
public :#{join_point.method_name}
|
292
|
+
undef_method :#{alias_method_name}
|
293
|
+
EOF
|
294
|
+
end
|
295
|
+
|
296
|
+
def remove_advice_chain_class_variable_text alias_method_name, join_point
|
297
|
+
self_name = join_point.type.nil? ? "self" : join_point.type.name
|
298
|
+
<<-EOF
|
299
|
+
advice_chain_name = :@@#{Aspect.advice_chain_attr_name join_point.type_or_object, join_point.method_name}
|
300
|
+
remove_class_variable advice_chain_name
|
301
|
+
EOF
|
302
|
+
end
|
303
|
+
|
304
|
+
def self.make_advice_join_point static_join_point, object, method_parameters, block_for_method
|
305
|
+
static_join_point.make_current_context_join_point(
|
306
|
+
:advice_kind => :before,
|
307
|
+
:advised_object => object,
|
308
|
+
:parameters => method_parameters,
|
309
|
+
:block_for_method => block_for_method)
|
310
|
+
end
|
311
|
+
|
312
|
+
def remove_advice_for_aspect_at join_point
|
313
|
+
advice_chain = Aspect.get_advice_chain join_point.type_or_object, join_point.method_name
|
314
|
+
advice_chain = prune_nodes_in advice_chain
|
315
|
+
if advice_chain.empty?
|
316
|
+
remove_advice_framework_for join_point
|
317
|
+
else
|
318
|
+
Aspect.set_advice_chain join_point.type_or_object, join_point.method_name, advice_chain
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def prune_nodes_in advice_chain
|
323
|
+
# Use equal? for the aspects to compare object id only,
|
324
|
+
while advice_chain.empty? == false && advice_chain.aspect.equal?(self)
|
325
|
+
advice_chain = advice_chain.next_node
|
326
|
+
end
|
327
|
+
keeper_node = node = advice_chain
|
328
|
+
while node.empty? == false
|
329
|
+
while node.next_node.aspect.equal?(self)
|
330
|
+
node.next_node = node.next_node.next_node
|
331
|
+
end
|
332
|
+
node = node.next_node
|
333
|
+
end
|
334
|
+
advice_chain
|
335
|
+
end
|
336
|
+
|
337
|
+
def remove_advice_framework_for join_point
|
338
|
+
if Aspect.is_type?(join_point.type_or_object)
|
339
|
+
restore_type_method join_point
|
340
|
+
else
|
341
|
+
restore_object_method join_point
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
def restore_type_method join_point
|
346
|
+
alias_method_name = (saved_method_name join_point).intern
|
347
|
+
join_point.type.class_eval(<<-EVAL_WRAPPER, __FILE__, __LINE__)
|
348
|
+
#{static_method_prefix join_point.is_instance_method?}
|
349
|
+
#{unalias_original_method_text alias_method_name, join_point}
|
350
|
+
#{static_method_suffix join_point.is_instance_method?}
|
351
|
+
#{remove_advice_chain_class_variable_text alias_method_name, join_point}
|
352
|
+
EVAL_WRAPPER
|
353
|
+
end
|
354
|
+
|
355
|
+
def do_restore_type_method join_point
|
356
|
+
end
|
357
|
+
|
358
|
+
def restore_object_method join_point
|
359
|
+
saved = saved_method_name join_point
|
360
|
+
singleton = class << join_point.object; self; end
|
361
|
+
singleton.class_eval do
|
362
|
+
alias_method join_point.method_name, saved
|
363
|
+
public join_point.method_name
|
364
|
+
undef_method saved.intern
|
365
|
+
end
|
366
|
+
advice_chain_name = "@#{Aspect.advice_chain_attr_name join_point.type_or_object, join_point.method_name}".intern
|
367
|
+
join_point.object.method(:remove_instance_variable).call advice_chain_name
|
368
|
+
end
|
369
|
+
|
370
|
+
def self.set_advice_chain type_or_object, method_name, advice_chain
|
371
|
+
self.is_type?(type_or_object) ?
|
372
|
+
self.set_type_advice_chain(type_or_object, method_name, advice_chain) :
|
373
|
+
self.set_object_advice_chain(type_or_object, method_name, advice_chain)
|
374
|
+
end
|
375
|
+
|
376
|
+
def self.get_advice_chain type_or_object, method_name
|
377
|
+
self.is_type?(type_or_object) ?
|
378
|
+
self.get_type_advice_chain(type_or_object, method_name) :
|
379
|
+
self.get_object_advice_chain(type_or_object, method_name)
|
380
|
+
end
|
381
|
+
|
382
|
+
def self.set_type_advice_chain type_or_object, method_name, advice_chain
|
383
|
+
chain_class_var = ("@@" + self.advice_chain_attr_name(type_or_object, method_name)).intern
|
384
|
+
type_or_object.class_eval do
|
385
|
+
class_variable_set chain_class_var, advice_chain
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
def self.set_object_advice_chain type_or_object, method_name, advice_chain
|
390
|
+
chain_class_var = ("@" + self.advice_chain_attr_name(type_or_object, method_name)).intern
|
391
|
+
type_or_object.instance_eval do
|
392
|
+
instance_variable_set chain_class_var, advice_chain
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
|
397
|
+
def self.get_type_advice_chain type_or_object, method_name
|
398
|
+
chain_class_var = ("@@" + self.advice_chain_attr_name(type_or_object, method_name)).intern
|
399
|
+
type_or_object.class_eval do
|
400
|
+
class_variable_get chain_class_var
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
def self.get_object_advice_chain type_or_object, method_name
|
405
|
+
chain_class_var = ("@" + self.advice_chain_attr_name(type_or_object, method_name)).intern
|
406
|
+
type_or_object.instance_eval do
|
407
|
+
instance_variable_get chain_class_var
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
def private_method_defined? type_or_object, method_name
|
412
|
+
if Aspect.is_type? type_or_object
|
413
|
+
type_or_object.private_instance_methods.include? method_name.to_s
|
414
|
+
else
|
415
|
+
type_or_object.private_methods.include? method_name.to_s
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
def self.advice_chain_attr_name type_or_object, method_name
|
420
|
+
type_or_object_key = self.make_type_or_object_key(type_or_object)
|
421
|
+
class_or_object_prefix = is_type?(type_or_object) ? "class_" : ""
|
422
|
+
valid_name = self.make_valid_attr_name_from_method_name method_name
|
423
|
+
"#{self.aspect_method_prefix}#{class_or_object_prefix}advice_chain_#{type_or_object_key}_#{valid_name}"
|
424
|
+
end
|
425
|
+
|
426
|
+
def self.aspect_method_prefix
|
427
|
+
"_aspect_"
|
428
|
+
end
|
429
|
+
|
430
|
+
def saved_method_name join_point
|
431
|
+
to_key = Aspect.make_type_or_object_key(join_point.type_or_object)
|
432
|
+
"#{Aspect.aspect_method_prefix}saved_#{to_key}_#{join_point.method_name}"
|
433
|
+
end
|
434
|
+
|
435
|
+
def self.make_type_or_object_key type_or_object
|
436
|
+
if is_type?(type_or_object)
|
437
|
+
make_valid_type_name type_or_object.name
|
438
|
+
else
|
439
|
+
"#{make_valid_type_name(type_or_object.class.name)}_#{type_or_object.object_id}"
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
def self.make_valid_type_name type_name
|
444
|
+
type_name.gsub(/:/, '_')
|
445
|
+
end
|
446
|
+
|
447
|
+
def self.make_valid_attr_name_from_method_name method_name
|
448
|
+
method_name.to_s.gsub("=","equalsign").gsub("?", "questionmark").gsub("!", "exclamation").gsub("~", "tilde").intern
|
449
|
+
end
|
450
|
+
|
451
|
+
def specified_advice_kinds
|
452
|
+
@specification.keys.select do |key|
|
453
|
+
Aquarium::Aspects::Advice.kinds.include? key
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
def rationalize_parameters opts
|
458
|
+
return unless opts.last.kind_of?(Hash)
|
459
|
+
option_synonyms = {
|
460
|
+
:type => :types,
|
461
|
+
:within_type => :types,
|
462
|
+
:within_types => :types,
|
463
|
+
:object => :objects,
|
464
|
+
:within_object => :objects,
|
465
|
+
:within_objects => :objects,
|
466
|
+
:method => :methods,
|
467
|
+
:within_method => :methods,
|
468
|
+
:within_methods => :methods,
|
469
|
+
:pointcut => :pointcuts,
|
470
|
+
:within_pointcut => :pointcuts,
|
471
|
+
:within_pointcuts => :pointcuts
|
472
|
+
}
|
473
|
+
hash = opts.pop.dup
|
474
|
+
opts.push hash
|
475
|
+
option_synonyms.each do |syn, actual|
|
476
|
+
if hash.has_key? syn
|
477
|
+
hash[actual] = make_array(hash[actual], hash[syn])
|
478
|
+
hash.delete syn
|
479
|
+
end
|
480
|
+
end
|
481
|
+
# Only one advice argument allowed.
|
482
|
+
unless hash.has_key?(:advice)
|
483
|
+
advice_synonyms = {
|
484
|
+
:call => :advice,
|
485
|
+
:invoke => :advice,
|
486
|
+
:advise_with => :advice
|
487
|
+
}
|
488
|
+
advice_synonyms.each do |syn, actual|
|
489
|
+
if hash.has_key? syn
|
490
|
+
hash[actual] = hash[syn]
|
491
|
+
hash.delete syn
|
492
|
+
end
|
493
|
+
end
|
494
|
+
end
|
495
|
+
[:types, :objects, :methods, :method_options, :pointcuts, :default_object].each do |opt|
|
496
|
+
hash[opt] = Set.new(make_array(hash[opt]))
|
497
|
+
end
|
498
|
+
opts
|
499
|
+
end
|
500
|
+
|
501
|
+
def validate_specification
|
502
|
+
bad_options("One of #{Aquarium::Aspects::Advice.kinds.inspect} is required.") unless advice_kinds_given?
|
503
|
+
bad_options(":around can't be used with :before.") if around_given_with? :before
|
504
|
+
bad_options(":around can't be used with :after.") if around_given_with? :after
|
505
|
+
bad_options(":around can't be used with :after_returning.") if around_given_with? :after_returning
|
506
|
+
bad_options(":around can't be used with :after_raising.") if around_given_with? :after_raising
|
507
|
+
bad_options(":after can't be used with :after_returning.") if after_given_with? :after_returning
|
508
|
+
bad_options(":after can't be used with :after_raising.") if after_given_with? :after_raising
|
509
|
+
bad_options(":after_returning can't be used with :after_raising.") if after_returning_given_with? :after_raising
|
510
|
+
unless pointcuts_given? or types_given? or objects_given? #or objects_given_excluding_default?
|
511
|
+
bad_options("At least one of :pointcut(s), :type(s), :object(s) is required.")
|
512
|
+
end
|
513
|
+
if pointcuts_given? and (types_given? or objects_given?)
|
514
|
+
bad_options("Can't specify both :pointcut(s) and one or more of :type(s), and/or :object(s).")
|
515
|
+
end
|
516
|
+
# unless methods_given? or pointcuts_given? or types_given? #or objects_given_excluding_default?
|
517
|
+
# specification_too_short
|
518
|
+
# end
|
519
|
+
end
|
520
|
+
|
521
|
+
def advice_kinds_given
|
522
|
+
Aquarium::Aspects::Advice.kinds.inject([]) {|ary, kind| ary << @specification[kind] if @specification[kind]; ary}
|
523
|
+
end
|
524
|
+
|
525
|
+
def advice_kinds_given?
|
526
|
+
not advice_kinds_given.empty?
|
527
|
+
end
|
528
|
+
|
529
|
+
def around_given_with? other_advice_kind_sym
|
530
|
+
@specification[:around] and @specification[other_advice_kind_sym]
|
531
|
+
end
|
532
|
+
|
533
|
+
def after_given_with? other_advice_kind_sym
|
534
|
+
@specification[:after] and @specification[other_advice_kind_sym]
|
535
|
+
end
|
536
|
+
def after_returning_given_with? other_advice_kind_sym
|
537
|
+
@specification[:after_returning] and @specification[other_advice_kind_sym]
|
538
|
+
end
|
539
|
+
|
540
|
+
%w[pointcuts types objects methods attributes method_options attribute_options].each do |name|
|
541
|
+
class_eval(<<-EOF, __FILE__, __LINE__)
|
542
|
+
def #{name}_given
|
543
|
+
@specification[:#{name}]
|
544
|
+
end
|
545
|
+
|
546
|
+
def #{name}_given?
|
547
|
+
not (#{name}_given.nil? or #{name}_given.empty?)
|
548
|
+
end
|
549
|
+
EOF
|
550
|
+
end
|
551
|
+
|
552
|
+
def use_first_nonadvice_symbol_as_method options
|
553
|
+
2.times do |i|
|
554
|
+
if options.size >= i+1
|
555
|
+
sym = options[i]
|
556
|
+
if sym.kind_of?(Symbol) && !Aquarium::Aspects::Advice::kinds.include?(sym)
|
557
|
+
@specification[:methods] = Set.new([sym])
|
558
|
+
return
|
559
|
+
end
|
560
|
+
end
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
def self.is_type? type_or_object
|
565
|
+
type_or_object.kind_of?(Class) or type_or_object.kind_of?(Module)
|
566
|
+
end
|
567
|
+
|
568
|
+
def self.type_or_object_string type_or_object
|
569
|
+
is_type?(type_or_object) ? "type" : "object"
|
570
|
+
end
|
571
|
+
|
572
|
+
def bad_options message
|
573
|
+
raise Aquarium::Utils::InvalidOptions.new("Invalid options given. " + message + " (options: #{@original_options.inspect})")
|
574
|
+
end
|
575
|
+
end
|
576
|
+
end
|
577
|
+
end
|