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.
Files changed (80) hide show
  1. data/CHANGES +4 -0
  2. data/EXAMPLES.rd +4 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README +250 -0
  5. data/RELEASE-PLAN +1 -0
  6. data/Rakefile +236 -0
  7. data/UPGRADE +3 -0
  8. data/examples/aspect_design_example.rb +36 -0
  9. data/examples/design_by_contract_example.rb +88 -0
  10. data/examples/method_missing_example.rb +44 -0
  11. data/examples/method_tracing_example.rb +64 -0
  12. data/lib/aquarium.rb +7 -0
  13. data/lib/aquarium/aspects.rb +6 -0
  14. data/lib/aquarium/aspects/advice.rb +189 -0
  15. data/lib/aquarium/aspects/aspect.rb +577 -0
  16. data/lib/aquarium/aspects/default_object_handler.rb +27 -0
  17. data/lib/aquarium/aspects/dsl.rb +1 -0
  18. data/lib/aquarium/aspects/dsl/aspect_dsl.rb +61 -0
  19. data/lib/aquarium/aspects/join_point.rb +158 -0
  20. data/lib/aquarium/aspects/pointcut.rb +254 -0
  21. data/lib/aquarium/aspects/pointcut_composition.rb +36 -0
  22. data/lib/aquarium/extensions.rb +5 -0
  23. data/lib/aquarium/extensions/hash.rb +85 -0
  24. data/lib/aquarium/extensions/regexp.rb +20 -0
  25. data/lib/aquarium/extensions/set.rb +49 -0
  26. data/lib/aquarium/extensions/string.rb +13 -0
  27. data/lib/aquarium/extensions/symbol.rb +22 -0
  28. data/lib/aquarium/extras.rb +4 -0
  29. data/lib/aquarium/extras/design_by_contract.rb +64 -0
  30. data/lib/aquarium/finders.rb +4 -0
  31. data/lib/aquarium/finders/finder_result.rb +121 -0
  32. data/lib/aquarium/finders/method_finder.rb +228 -0
  33. data/lib/aquarium/finders/object_finder.rb +74 -0
  34. data/lib/aquarium/finders/type_finder.rb +127 -0
  35. data/lib/aquarium/utils.rb +9 -0
  36. data/lib/aquarium/utils/array_utils.rb +29 -0
  37. data/lib/aquarium/utils/hash_utils.rb +28 -0
  38. data/lib/aquarium/utils/html_escaper.rb +17 -0
  39. data/lib/aquarium/utils/invalid_options.rb +9 -0
  40. data/lib/aquarium/utils/method_utils.rb +18 -0
  41. data/lib/aquarium/utils/nil_object.rb +13 -0
  42. data/lib/aquarium/utils/set_utils.rb +32 -0
  43. data/lib/aquarium/version.rb +30 -0
  44. data/rake_tasks/examples.rake +7 -0
  45. data/rake_tasks/examples_specdoc.rake +8 -0
  46. data/rake_tasks/examples_with_rcov.rake +8 -0
  47. data/rake_tasks/verify_rcov.rake +7 -0
  48. data/spec/aquarium/aspects/advice_chain_node_spec.rb +34 -0
  49. data/spec/aquarium/aspects/advice_spec.rb +103 -0
  50. data/spec/aquarium/aspects/aspect_invocation_spec.rb +111 -0
  51. data/spec/aquarium/aspects/aspect_spec.rb +978 -0
  52. data/spec/aquarium/aspects/aspect_with_nested_types_spec.rb +129 -0
  53. data/spec/aquarium/aspects/concurrent_aspects_spec.rb +423 -0
  54. data/spec/aquarium/aspects/concurrent_aspects_with_objects_and_types_spec.rb +103 -0
  55. data/spec/aquarium/aspects/concurrently_accessed.rb +21 -0
  56. data/spec/aquarium/aspects/dsl/aspect_dsl_spec.rb +514 -0
  57. data/spec/aquarium/aspects/join_point_spec.rb +302 -0
  58. data/spec/aquarium/aspects/pointcut_and_composition_spec.rb +131 -0
  59. data/spec/aquarium/aspects/pointcut_or_composition_spec.rb +111 -0
  60. data/spec/aquarium/aspects/pointcut_spec.rb +800 -0
  61. data/spec/aquarium/extensions/hash_spec.rb +187 -0
  62. data/spec/aquarium/extensions/regex_spec.rb +40 -0
  63. data/spec/aquarium/extensions/set_spec.rb +105 -0
  64. data/spec/aquarium/extensions/string_spec.rb +25 -0
  65. data/spec/aquarium/extensions/symbol_spec.rb +37 -0
  66. data/spec/aquarium/extras/design_by_contract_spec.rb +68 -0
  67. data/spec/aquarium/finders/finder_result_spec.rb +359 -0
  68. data/spec/aquarium/finders/method_finder_spec.rb +878 -0
  69. data/spec/aquarium/finders/method_sorting_spec.rb +16 -0
  70. data/spec/aquarium/finders/object_finder_spec.rb +230 -0
  71. data/spec/aquarium/finders/type_finder_spec.rb +210 -0
  72. data/spec/aquarium/spec_example_classes.rb +117 -0
  73. data/spec/aquarium/spec_helper.rb +3 -0
  74. data/spec/aquarium/utils/array_utils_spec.rb +47 -0
  75. data/spec/aquarium/utils/hash_utils_spec.rb +48 -0
  76. data/spec/aquarium/utils/html_escaper_spec.rb +18 -0
  77. data/spec/aquarium/utils/method_utils_spec.rb +50 -0
  78. data/spec/aquarium/utils/nil_object_spec.rb +19 -0
  79. data/spec/aquarium/utils/set_utils_spec.rb +60 -0
  80. 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