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,27 @@
|
|
1
|
+
module Aquarium
|
2
|
+
module Aspects
|
3
|
+
# Some classes and modules support a :default_object flag and use it if no type or
|
4
|
+
# object is specified. For "convenience", requires that classes and modules including
|
5
|
+
# this module have a hash @specification defined with keys :default_object, :types,
|
6
|
+
# and :objects.
|
7
|
+
module DefaultObjectHandler
|
8
|
+
def default_object_given
|
9
|
+
@specification[:default_object]
|
10
|
+
end
|
11
|
+
|
12
|
+
def default_object_given?
|
13
|
+
not (default_object_given.nil? or default_object_given.empty?)
|
14
|
+
end
|
15
|
+
|
16
|
+
def use_default_object_if_defined
|
17
|
+
return unless default_object_given?
|
18
|
+
object = default_object_given.to_a.first # there will be only one...
|
19
|
+
if (object.kind_of?(Class) || object.kind_of?(Module))
|
20
|
+
@specification[:types] = default_object_given
|
21
|
+
else
|
22
|
+
@specification[:objects] = default_object_given
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'aquarium/aspects/dsl/aspect_dsl'
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'aquarium/aspects/aspect'
|
2
|
+
|
3
|
+
# Convenience methods added to Object to promote an AOP DSL. If you don't want these methods added to Object,
|
4
|
+
# then only require aspect.rb and create instances of Aspect.
|
5
|
+
|
6
|
+
module Aquarium
|
7
|
+
module Aspects
|
8
|
+
module DSL
|
9
|
+
module AspectDSL
|
10
|
+
def advise *options, &block
|
11
|
+
o = append_implicit_self options
|
12
|
+
Aspect.new *o, &block
|
13
|
+
end
|
14
|
+
|
15
|
+
%w[before after after_returning after_raising around].each do |advice_kind|
|
16
|
+
class_eval(<<-ADVICE_METHODS, __FILE__, __LINE__)
|
17
|
+
def #{advice_kind} *options, &block
|
18
|
+
advise :#{advice_kind}, *options, &block
|
19
|
+
end
|
20
|
+
ADVICE_METHODS
|
21
|
+
end
|
22
|
+
|
23
|
+
%w[after after_returning after_raising].each do |after_kind|
|
24
|
+
class_eval(<<-AFTER, __FILE__, __LINE__)
|
25
|
+
def before_and_#{after_kind} *options, &block
|
26
|
+
advise(:before, :#{after_kind}, *options, &block)
|
27
|
+
end
|
28
|
+
AFTER
|
29
|
+
end
|
30
|
+
|
31
|
+
alias :after_returning_from :after_returning
|
32
|
+
alias :after_raising_within :after_raising
|
33
|
+
alias :after_raising_within_or_returning_from :after
|
34
|
+
|
35
|
+
alias :before_and_after_returning_from :before_and_after_returning
|
36
|
+
alias :before_and_after_raising_within :before_and_after_raising
|
37
|
+
alias :before_and_after_raising_within_or_returning_from :before_and_after
|
38
|
+
|
39
|
+
def pointcut *options, &block
|
40
|
+
o = append_implicit_self options
|
41
|
+
Pointcut.new *o, &block
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def append_implicit_self options
|
46
|
+
opts = options.dup
|
47
|
+
if (!opts.empty?) && opts.last.kind_of?(Hash)
|
48
|
+
opts.last[:default_object] = self
|
49
|
+
else
|
50
|
+
opts << {:default_object => self}
|
51
|
+
end
|
52
|
+
opts
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Object
|
60
|
+
include Aquarium::Aspects::DSL::AspectDSL
|
61
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'aquarium/utils'
|
2
|
+
|
3
|
+
def bad_attributes message, options
|
4
|
+
raise Aquarium::Utils::InvalidOptions.new("Invalid attributes. " + message + ". Options were: #{options.inspect}")
|
5
|
+
end
|
6
|
+
|
7
|
+
module Aquarium
|
8
|
+
module Aspects
|
9
|
+
class JoinPoint
|
10
|
+
|
11
|
+
class Context
|
12
|
+
attr_accessor :advice_kind, :advised_object, :parameters, :block_for_method, :returned_value, :raised_exception, :proceed_proc
|
13
|
+
|
14
|
+
alias :target_object :advised_object
|
15
|
+
alias :target_object= :advised_object=
|
16
|
+
|
17
|
+
def initialize options
|
18
|
+
update options
|
19
|
+
assert_valid options
|
20
|
+
end
|
21
|
+
|
22
|
+
def update options
|
23
|
+
options.each do |key, value|
|
24
|
+
instance_variable_set "@#{key}".intern, value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def proceed enclosing_join_point, *args, &block
|
29
|
+
raise "JoinPoint#proceed can only be called if @proceed_proc is set." unless @proceed_proc
|
30
|
+
args = parameters if (args.nil? or args.size == 0)
|
31
|
+
enclosing_join_point.context.block_for_method = block if block
|
32
|
+
proceed_proc.call enclosing_join_point, *args
|
33
|
+
end
|
34
|
+
protected :proceed
|
35
|
+
|
36
|
+
alias :to_s :inspect
|
37
|
+
|
38
|
+
# We require the same object id, not just equal objects.
|
39
|
+
def <=> other
|
40
|
+
return 0 if object_id == other.object_id
|
41
|
+
result = self.class <=> other.class
|
42
|
+
return result unless result == 0
|
43
|
+
result = (self.advice_kind.nil? && other.advice_kind.nil?) ? 0 : self.advice_kind <=> other.advice_kind
|
44
|
+
return result unless result == 0
|
45
|
+
result = (self.advised_object.object_id.nil? && other.advised_object.object_id.nil?) ? 0 : self.advised_object.object_id <=> other.advised_object.object_id
|
46
|
+
return result unless result == 0
|
47
|
+
result = (self.parameters.nil? && other.parameters.nil?) ? 0 : self.parameters <=> other.parameters
|
48
|
+
return result unless result == 0
|
49
|
+
result = (self.returned_value.nil? && other.returned_value.nil?) ? 0 : self.returned_value <=> other.returned_value
|
50
|
+
return result unless result == 0
|
51
|
+
(self.raised_exception.nil? && other.raised_exception.nil?) ? 0 : self.raised_exception <=> other.raised_exception
|
52
|
+
end
|
53
|
+
|
54
|
+
def eql? other
|
55
|
+
(self <=> other) == 0
|
56
|
+
end
|
57
|
+
|
58
|
+
alias :== :eql?
|
59
|
+
alias :=== :eql?
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
def assert_valid options
|
64
|
+
bad_attributes("Must specify an :advice_kind", options) unless advice_kind
|
65
|
+
bad_attributes("Must specify an :advised_object", options) unless advised_object
|
66
|
+
bad_attributes("Must specify a :parameters", options) unless parameters
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
attr_accessor :type, :object, :method_name, :context
|
71
|
+
|
72
|
+
def is_instance_method?
|
73
|
+
@is_instance_method
|
74
|
+
end
|
75
|
+
|
76
|
+
def initialize options = {}
|
77
|
+
@type = options[:type]
|
78
|
+
@object = options[:object]
|
79
|
+
@method_name = options[:method_name] || options[:method]
|
80
|
+
@is_instance_method = options[:is_instance_method]
|
81
|
+
is_class_method = options[:is_class_method].nil? ? false : options[:is_class_method]
|
82
|
+
@is_instance_method = (!is_class_method) if @is_instance_method.nil?
|
83
|
+
assert_valid options
|
84
|
+
end
|
85
|
+
|
86
|
+
# deal with warnings for Object#type being obsolete:
|
87
|
+
def get_type
|
88
|
+
@type
|
89
|
+
end
|
90
|
+
|
91
|
+
def type_or_object
|
92
|
+
@type || @object
|
93
|
+
end
|
94
|
+
|
95
|
+
# TODO while convenient, it couples advice-type information where it doesn't belong!
|
96
|
+
def proceed *args, &block
|
97
|
+
context.method(:proceed).call self, *args, &block
|
98
|
+
end
|
99
|
+
|
100
|
+
def make_current_context_join_point context_options
|
101
|
+
new_jp = dup
|
102
|
+
if new_jp.context.nil?
|
103
|
+
new_jp.context = JoinPoint::Context.new context_options
|
104
|
+
else
|
105
|
+
new_jp.context = context.dup
|
106
|
+
new_jp.context.update context_options
|
107
|
+
end
|
108
|
+
new_jp
|
109
|
+
end
|
110
|
+
|
111
|
+
# We require the same object id, not just equal objects.
|
112
|
+
def <=> other
|
113
|
+
return 0 if object_id == other.object_id
|
114
|
+
result = self.class <=> other.class
|
115
|
+
return result unless result == 0
|
116
|
+
result = (self.get_type.nil? && other.get_type.nil?) ? 0 : self.get_type.to_s <=> other.get_type.to_s
|
117
|
+
return result unless result == 0
|
118
|
+
result = (self.object.object_id.nil? && other.object.object_id.nil?) ? 0 : self.object.object_id <=> other.object.object_id
|
119
|
+
result = self.object.object_id <=> other.object.object_id
|
120
|
+
return result unless result == 0
|
121
|
+
result = (self.method_name.nil? && other.method_name.nil?) ? 0 : self.method_name.to_s <=> other.method_name.to_s
|
122
|
+
return result unless result == 0
|
123
|
+
result = self.is_instance_method? == other.is_instance_method?
|
124
|
+
return 1 unless result == true
|
125
|
+
result = (self.context.nil? && other.context.nil?) ? 0 : self.context <=> other.context
|
126
|
+
return result
|
127
|
+
end
|
128
|
+
|
129
|
+
def eql? other
|
130
|
+
return (self <=> other) == 0
|
131
|
+
end
|
132
|
+
|
133
|
+
alias :== :eql?
|
134
|
+
alias :=== :eql?
|
135
|
+
|
136
|
+
def inspect
|
137
|
+
"JoinPoint: {type = #{type.inspect}, object = #{object.inspect}, method_name = #{method_name}, is_instance_method? #{is_instance_method?}, context = #{context.inspect}}"
|
138
|
+
end
|
139
|
+
|
140
|
+
alias :to_s :inspect
|
141
|
+
|
142
|
+
|
143
|
+
protected
|
144
|
+
|
145
|
+
def assert_valid options
|
146
|
+
error_message = ""
|
147
|
+
error_message << "Must specify a :method_name. " unless method_name
|
148
|
+
error_message << "Must specify either a :type or :object. " unless (type or object)
|
149
|
+
error_message << "Can't specify both a :type or :object. " if (type and object)
|
150
|
+
bad_attributes(error_message, options) if error_message.length > 0
|
151
|
+
end
|
152
|
+
|
153
|
+
public
|
154
|
+
|
155
|
+
NIL_OBJECT = Aquarium::Utils::NilObject.new
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,254 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'aquarium/aspects/join_point'
|
3
|
+
require 'aquarium/utils'
|
4
|
+
require 'aquarium/extensions'
|
5
|
+
require 'aquarium/finders/finder_result'
|
6
|
+
require 'aquarium/finders/type_finder'
|
7
|
+
require 'aquarium/finders/method_finder'
|
8
|
+
require 'aquarium/aspects/default_object_handler'
|
9
|
+
|
10
|
+
module Aquarium
|
11
|
+
module Aspects
|
12
|
+
# == Pointcut
|
13
|
+
# Pointcuts are queries on JoinPoints combined with binding of context data to
|
14
|
+
# that will be useful during advice execution. The Pointcut locates the join points
|
15
|
+
# that match the input criteria, remembering the found join points as well as the
|
16
|
+
# the criteria that yielded no matches (mostly useful for debugging Pointcut definitions)
|
17
|
+
class Pointcut
|
18
|
+
include Aquarium::Utils::ArrayUtils
|
19
|
+
include Aquarium::Utils::HashUtils
|
20
|
+
include Aquarium::Utils::SetUtils
|
21
|
+
include DefaultObjectHandler
|
22
|
+
|
23
|
+
attr_reader :specification
|
24
|
+
|
25
|
+
# Construct a Pointcut for methods in types or objects.
|
26
|
+
# Pointcut.new :type{s} => [...] | :object{s} => [...] \
|
27
|
+
# {, :method{s} => [], :method_options => [...], \
|
28
|
+
# :attribute{s} => [...], :attribute_options[...]}
|
29
|
+
# where
|
30
|
+
# the "{}" indicate optional elements. For example, you can use
|
31
|
+
# :types or :type.
|
32
|
+
#
|
33
|
+
# <tt>:types => type || [type_list]</tt>::
|
34
|
+
# <tt>:type => type || [type_list]</tt>::
|
35
|
+
# One or an array of types, type names and/or type regular expessions to match.
|
36
|
+
#
|
37
|
+
# <tt>:objects => object || [object_list]</tt>::
|
38
|
+
# <tt>:object => object || [object_list]</tt>::
|
39
|
+
# Objects to match.
|
40
|
+
#
|
41
|
+
# <tt>:default_object => object</tt>::
|
42
|
+
# An "internal" flag used by AspectDSL#pointcut when no object or type is specified,
|
43
|
+
# the value of :default_object will be used, if defined. AspectDSL#pointcut sets the
|
44
|
+
# value to self, so that the user doesn't have to in the appropriate contexts.
|
45
|
+
# This flag is subject to change, so don't use it explicitly!
|
46
|
+
#
|
47
|
+
# <tt>:methods => method || [method_list]</tt>::
|
48
|
+
# <tt>:method => method || [method_list]</tt>::
|
49
|
+
# One or an array of methods, method names and/or method regular expessions to match.
|
50
|
+
# By default, unless :attributes are specified, searches for public instance methods
|
51
|
+
# with the method option :suppress_ancestor_methods implied, unless explicit method
|
52
|
+
# options are given.
|
53
|
+
#
|
54
|
+
# <tt>:method_options => [options]</tt>::
|
55
|
+
# One or more options supported by Aquarium::Finders::MethodFinder. The :suppress_ancestor_methods
|
56
|
+
# option is most useful.
|
57
|
+
#
|
58
|
+
# <tt>:attributes => attribute || [attribute_list]</tt>::
|
59
|
+
# <tt>:attribute => attribute || [attribute_list]</tt>::
|
60
|
+
# One or an array of attribute names and/or regular expessions to match.
|
61
|
+
# This is syntactic sugar for the corresponding attribute readers and/or writers
|
62
|
+
# methods, as specified using the <tt>:attrbute_options. Any matches will be
|
63
|
+
# joined with the matched :methods.</tt>.
|
64
|
+
#
|
65
|
+
# <tt>:attribute_options => [options]</tt>::
|
66
|
+
# One or more of <tt>:readers</tt>, <tt>:reader</tt> (synonymous),
|
67
|
+
# <tt>:writers</tt>, and/or <tt>:writer</tt> (synonymous). By default, both
|
68
|
+
# readers and writers are matched.
|
69
|
+
def initialize options = {}
|
70
|
+
init_specification options
|
71
|
+
init_candidate_types
|
72
|
+
init_candidate_objects
|
73
|
+
init_join_points
|
74
|
+
end
|
75
|
+
|
76
|
+
attr_reader :join_points_matched, :join_points_not_matched, :specification, :candidate_types, :candidate_objects
|
77
|
+
|
78
|
+
# Two Considered equivalent only if the same join points matched and not_matched sets are equal,
|
79
|
+
# the specifications are equal, and the candidate types and candidate objects are equal.
|
80
|
+
# if you care only about the matched join points, then just compare #join_points_matched
|
81
|
+
def eql? other
|
82
|
+
object_id == other.object_id ||
|
83
|
+
(specification == other.specification &&
|
84
|
+
candidate_types == other.candidate_types &&
|
85
|
+
candidate_objects == other.candidate_objects &&
|
86
|
+
join_points_matched == other.join_points_matched &&
|
87
|
+
join_points_not_matched == other.join_points_not_matched)
|
88
|
+
end
|
89
|
+
|
90
|
+
alias :== :eql?
|
91
|
+
alias :=== :eql?
|
92
|
+
|
93
|
+
def empty?
|
94
|
+
return join_points_matched.empty? && join_points_not_matched.empty?
|
95
|
+
end
|
96
|
+
|
97
|
+
def inspect
|
98
|
+
"Pointcut: {specification: #{specification.inspect}, candidate_types: #{candidate_types.inspect}, candidate_objects: #{candidate_objects.inspect}, join_points_matched: #{join_points_matched.inspect}, join_points_not_matched: #{join_points_not_matched.inspect}}"
|
99
|
+
end
|
100
|
+
|
101
|
+
alias to_s inspect
|
102
|
+
|
103
|
+
def self.make_attribute_method_names attribute_name_regexps_or_names, attribute_options = []
|
104
|
+
readers = make_attribute_readers attribute_name_regexps_or_names
|
105
|
+
return readers if read_only attribute_options
|
106
|
+
|
107
|
+
writers = make_attribute_writers readers
|
108
|
+
return writers if write_only attribute_options
|
109
|
+
return readers + writers
|
110
|
+
end
|
111
|
+
|
112
|
+
protected
|
113
|
+
|
114
|
+
attr_writer :join_points_matched, :join_points_not_matched, :specification, :candidate_types, :candidate_objects
|
115
|
+
|
116
|
+
def init_specification options
|
117
|
+
@specification = {}
|
118
|
+
options ||= {}
|
119
|
+
@specification[:method_options] = Set.new(make_array(options[:method_options]))
|
120
|
+
@specification[:attribute_options] = Set.new(make_array(options[:attribute_options]) )
|
121
|
+
@specification[:types] = Set.new(make_array(options[:types], options[:type]))
|
122
|
+
@specification[:objects] = Set.new(make_array(options[:objects], options[:object]))
|
123
|
+
@specification[:default_object] = Set.new(make_array(options[:default_object]))
|
124
|
+
use_default_object_if_defined unless (types_given? || objects_given?)
|
125
|
+
@specification[:attributes] = Set.new(make_array(options[:attributes], options[:attribute]))
|
126
|
+
raise Aquarium::Utils::InvalidOptions.new(":all is not yet supported for :attributes.") if @specification[:attributes] == Set.new([:all])
|
127
|
+
init_methods_specification options
|
128
|
+
end
|
129
|
+
|
130
|
+
def init_methods_specification options
|
131
|
+
@specification[:methods] = Set.new(make_array(options[:methods], options[:method]))
|
132
|
+
@specification[:methods].add(:all) if @specification[:methods].empty? and @specification[:attributes].empty?
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.read_only attribute_options
|
136
|
+
read_option(attribute_options) && !write_option(attribute_options)
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.write_only attribute_options
|
140
|
+
write_option(attribute_options) && !read_option(attribute_options)
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.read_option attribute_options
|
144
|
+
attribute_options.include?(:readers) || attribute_options.include?(:reader)
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.write_option attribute_options
|
148
|
+
attribute_options.include?(:writers) || attribute_options.include?(:writer)
|
149
|
+
end
|
150
|
+
|
151
|
+
%w[types objects methods attributes method_options attribute_options].each do |name|
|
152
|
+
class_eval(<<-EOF, __FILE__, __LINE__)
|
153
|
+
def #{name}_given
|
154
|
+
@specification[:#{name}]
|
155
|
+
end
|
156
|
+
|
157
|
+
def #{name}_given?
|
158
|
+
not (#{name}_given.nil? or #{name}_given.empty?)
|
159
|
+
end
|
160
|
+
EOF
|
161
|
+
end
|
162
|
+
|
163
|
+
private
|
164
|
+
|
165
|
+
def init_candidate_types
|
166
|
+
explicit_types, type_regexps_or_names = @specification[:types].partition do |type|
|
167
|
+
type.kind_of?(Module) || type.kind_of?(Class)
|
168
|
+
end
|
169
|
+
@candidate_types = Aquarium::Finders::TypeFinder.new.find :types => type_regexps_or_names
|
170
|
+
@candidate_types.append_matched(make_hash(explicit_types) {|x| Set.new([])}) # Append already-known types
|
171
|
+
end
|
172
|
+
|
173
|
+
def init_candidate_objects
|
174
|
+
object_hash = {}
|
175
|
+
@specification[:objects].each {|o| object_hash[o] = Set.new([])}
|
176
|
+
@candidate_objects = Aquarium::Finders::FinderResult.new object_hash
|
177
|
+
end
|
178
|
+
|
179
|
+
def init_join_points
|
180
|
+
@join_points_matched = Set.new
|
181
|
+
@join_points_not_matched = Set.new
|
182
|
+
results = find_methods_for_types make_all_method_names
|
183
|
+
add_join_points @join_points_matched, results.matched, :type
|
184
|
+
add_join_points @join_points_not_matched, results.not_matched, :type
|
185
|
+
results = find_methods_for_objects make_all_method_names
|
186
|
+
add_join_points @join_points_matched, results.matched, :object
|
187
|
+
add_join_points @join_points_not_matched, results.not_matched, :object
|
188
|
+
end
|
189
|
+
|
190
|
+
def find_methods_for_types which_methods
|
191
|
+
return Aquarium::Finders::FinderResult::NIL_OBJECT if candidate_types.matched.size == 0
|
192
|
+
Aquarium::Finders::MethodFinder.new.find :types => candidate_types.matched_keys,
|
193
|
+
:methods => which_methods,
|
194
|
+
:options => @specification[:method_options].to_a
|
195
|
+
end
|
196
|
+
|
197
|
+
def find_methods_for_objects which_methods
|
198
|
+
return Aquarium::Finders::FinderResult::NIL_OBJECT if candidate_objects.matched.size == 0
|
199
|
+
Aquarium::Finders::MethodFinder.new.find :objects => candidate_objects.matched_keys,
|
200
|
+
:methods => which_methods,
|
201
|
+
:options => @specification[:method_options].to_a
|
202
|
+
end
|
203
|
+
|
204
|
+
def add_join_points which_join_points_list, results_hash, type_or_object_sym
|
205
|
+
is_instance_method = @specification[:method_options].include?(:class) ? false : true
|
206
|
+
results_hash.each_pair do |type_or_object, method_name_list|
|
207
|
+
method_name_list.each do |method_name|
|
208
|
+
which_join_points_list << Aquarium::Aspects::JoinPoint.new(
|
209
|
+
type_or_object_sym => type_or_object,
|
210
|
+
:method_name => method_name,
|
211
|
+
:is_instance_method => is_instance_method)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def make_all_method_names
|
217
|
+
@specification[:methods] + Pointcut.make_attribute_method_names(@specification[:attributes], @specification[:attribute_options])
|
218
|
+
end
|
219
|
+
|
220
|
+
def self.make_attribute_readers attributes
|
221
|
+
readers = attributes.map do |regexp_or_name|
|
222
|
+
if regexp_or_name.kind_of? Regexp
|
223
|
+
exp = remove_trailing_equals_and_or_dollar regexp_or_name.source
|
224
|
+
Regexp.new(remove_leading_colon_or_at_sign(exp + '.*\b$'))
|
225
|
+
else
|
226
|
+
exp = remove_trailing_equals_and_or_dollar regexp_or_name.to_s
|
227
|
+
remove_leading_colon_or_at_sign(exp.to_s)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
Set.new(readers.sort_by {|exp| exp.to_s})
|
231
|
+
end
|
232
|
+
|
233
|
+
def self.make_attribute_writers attributes
|
234
|
+
writers = attributes.map do |regexp_or_name|
|
235
|
+
if regexp_or_name.kind_of? Regexp
|
236
|
+
# remove the "\b$" from the end of the reader expression, if present.
|
237
|
+
Regexp.new(remove_trailing_equals_and_or_dollar(regexp_or_name.source) + '=$')
|
238
|
+
else
|
239
|
+
regexp_or_name + '='
|
240
|
+
end
|
241
|
+
end
|
242
|
+
Set.new(writers.sort_by {|exp| exp.to_s})
|
243
|
+
end
|
244
|
+
|
245
|
+
def self.remove_trailing_equals_and_or_dollar exp
|
246
|
+
exp.gsub(/\=?\$?$/, '')
|
247
|
+
end
|
248
|
+
|
249
|
+
def self.remove_leading_colon_or_at_sign exp
|
250
|
+
exp.gsub(/^\^?(@|:)/, '')
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|