aquarium 0.1.7 → 0.1.8

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.
@@ -1,3 +1,5 @@
1
+ require 'aquarium/utils/array_utils'
2
+
1
3
  module Aquarium
2
4
  module Aspects
3
5
  # Some classes and modules support a :default_object flag and use it if no type or
@@ -5,21 +7,31 @@ module Aquarium
5
7
  # this module have a hash @specification defined with keys :default_object, :types,
6
8
  # and :objects.
7
9
  module DefaultObjectHandler
8
- def default_object_given
9
- @specification[:default_object]
10
+ include Aquarium::Utils::ArrayUtils
11
+
12
+ def default_objects_given
13
+ if @default_objects.nil?
14
+ ary1 = make_array(@specification[:default_objects])
15
+ ary2 = make_array(@specification[:default_object])
16
+ @default_objects = ary1 + ary2
17
+ end
18
+ @default_objects
10
19
  end
11
20
 
12
- def default_object_given?
13
- not (default_object_given.nil? or default_object_given.empty?)
21
+ def default_objects_given?
22
+ not default_objects_given.empty?
14
23
  end
15
24
 
16
25
  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
26
+ return unless default_objects_given?
27
+ default_objects_given.each do |object|
28
+ if (object.kind_of?(Class) || object.kind_of?(Module))
29
+ @specification[:types] ||= []
30
+ @specification[:types] << default_objects_given
31
+ else
32
+ @specification[:objects] ||= []
33
+ @specification[:objects] << default_objects_given
34
+ end
23
35
  end
24
36
  end
25
37
  end
@@ -0,0 +1,61 @@
1
+
2
+ module Aquarium
3
+ module Aspects
4
+
5
+ # Defines methods shared by several classes that take :exclude_* arguments.
6
+ module ExclusionHandler
7
+
8
+ def join_point_excluded? jp
9
+ is_excluded_pointcut?(jp) or is_excluded_join_point?(jp) or is_excluded_type_or_object?(jp.type_or_object) or is_excluded_method?(jp.method_name)
10
+ end
11
+
12
+ def is_excluded_pointcut? jp
13
+ return false if @specification[:exclude_pointcuts].nil?
14
+ @specification[:exclude_pointcuts].find do |pc|
15
+ pc.join_points_matched.find do |jp2|
16
+ jp2 == jp || jp2.eql?(jp)
17
+ end
18
+ end
19
+ end
20
+
21
+ # Using @specification[:exclude_join_points].include?(jp) doesn't always work correctly (it probably uses equal())!
22
+ def is_excluded_join_point? jp
23
+ return false if @specification[:exclude_join_points].nil?
24
+ @specification[:exclude_join_points].find {|jp2| jp2 == jp || jp2.eql?(jp)}
25
+ end
26
+
27
+ def is_excluded_type_or_object? type_or_object
28
+ unless @specification[:exclude_objects].nil?
29
+ return true if @specification[:exclude_objects].include?(type_or_object)
30
+ end
31
+ unless @specification[:exclude_types].nil?
32
+ return true if @specification[:exclude_types].find do |t|
33
+ case t
34
+ when String: type_or_object.name.eql?(t)
35
+ when Symbol: type_or_object.name.eql?(t.to_s)
36
+ when Regexp: type_or_object.name =~ t
37
+ else type_or_object == t
38
+ end
39
+ end
40
+ end
41
+ false
42
+ end
43
+
44
+ def is_excluded_method? method
45
+ is_explicitly_excluded_method?(method) or matches_excluded_method_regex?(method)
46
+ end
47
+
48
+ def is_explicitly_excluded_method? method
49
+ return false if @specification[:exclude_methods].nil?
50
+ @specification[:exclude_methods].include? method
51
+ end
52
+
53
+ def matches_excluded_method_regex? method
54
+ return false if @specification[:exclude_methods].nil?
55
+ regexs = @specification[:exclude_methods].find_all {|s| s.kind_of? Regexp}
56
+ return false if regexs.empty?
57
+ regexs.find {|re| method.to_s =~ re}
58
+ end
59
+ end
60
+ end
61
+ end
@@ -38,17 +38,18 @@ module Aquarium
38
38
  # We require the same object id, not just equal objects.
39
39
  def <=> other
40
40
  return 0 if object_id == other.object_id
41
+ return 1 if other.nil?
41
42
  result = self.class <=> other.class
42
43
  return result unless result == 0
43
- result = (self.advice_kind.nil? && other.advice_kind.nil?) ? 0 : self.advice_kind <=> other.advice_kind
44
+ result = (self.advice_kind.nil? and other.advice_kind.nil?) ? 0 : self.advice_kind <=> other.advice_kind
44
45
  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
+ result = (self.advised_object.object_id.nil? and other.advised_object.object_id.nil?) ? 0 : self.advised_object.object_id <=> other.advised_object.object_id
46
47
  return result unless result == 0
47
- result = (self.parameters.nil? && other.parameters.nil?) ? 0 : self.parameters <=> other.parameters
48
+ result = (self.parameters.nil? and other.parameters.nil?) ? 0 : self.parameters <=> other.parameters
48
49
  return result unless result == 0
49
- result = (self.returned_value.nil? && other.returned_value.nil?) ? 0 : self.returned_value <=> other.returned_value
50
+ result = (self.returned_value.nil? and other.returned_value.nil?) ? 0 : self.returned_value <=> other.returned_value
50
51
  return result unless result == 0
51
- (self.raised_exception.nil? && other.raised_exception.nil?) ? 0 : self.raised_exception <=> other.raised_exception
52
+ (self.raised_exception.nil? and other.raised_exception.nil?) ? 0 : self.raised_exception <=> other.raised_exception
52
53
  end
53
54
 
54
55
  def eql? other
@@ -68,7 +69,7 @@ module Aquarium
68
69
  end
69
70
 
70
71
  %w[type object].each do |attr|
71
- class_eval <<-EOF
72
+ class_eval(<<-EOF, __FILE__, __LINE__)
72
73
  # Deprecated, as JoinPoint#type overrides Module#type in a non-substitutable way!
73
74
  # JoinPoint#target_#{attr} will be removed in the next release.
74
75
  # Use JoinPoint#target_#{attr} instead
@@ -105,16 +106,15 @@ module Aquarium
105
106
  @target_type = options[:type]
106
107
  @target_object = options[:object]
107
108
  @method_name = options[:method_name] || options[:method]
108
- @instance_method = options[:instance_method]
109
109
  class_method = options[:class_method].nil? ? false : options[:class_method]
110
- @instance_method = (!class_method) if @instance_method.nil?
110
+ @instance_method = options[:instance_method].nil? ? (!class_method) : options[:instance_method]
111
111
  @instance_or_class_method = @instance_method ? :instance : :class
112
112
  @visibility = Aquarium::Utils::MethodUtils.visibility(type_or_object, @method_name, class_or_instance_method_flag)
113
113
  assert_valid options
114
114
  end
115
115
 
116
116
  def type_or_object
117
- @target_type || @target_object
117
+ target_type || target_object
118
118
  end
119
119
 
120
120
  alias_method :target_type_or_object, :type_or_object
@@ -128,7 +128,6 @@ module Aquarium
128
128
  return results.matched.size == 1 ? true : false
129
129
  end
130
130
 
131
- # TODO while convenient, it couples advice-type information where it doesn't belong!
132
131
  def proceed *args, &block
133
132
  context.method(:proceed).call self, *args, &block
134
133
  end
@@ -146,20 +145,22 @@ module Aquarium
146
145
 
147
146
  # We require the same object id, not just equal objects.
148
147
  def <=> other
149
- return 0 if object_id == other.object_id
148
+ return 0 if object_id == other.object_id
149
+ return 1 if other.nil?
150
150
  result = self.class <=> other.class
151
151
  return result unless result == 0
152
- result = (self.target_type.nil? && other.target_type.nil?) ? 0 : self.target_type.to_s <=> other.target_type.to_s
152
+ result = (self.target_type.nil? and other.target_type.nil?) ? 0 : self.target_type.to_s <=> other.target_type.to_s
153
153
  return result unless result == 0
154
- result = (self.target_object.object_id.nil? && other.target_object.object_id.nil?) ? 0 : self.target_object.object_id <=> other.target_object.object_id
155
- result = self.target_object.object_id <=> other.target_object.object_id
154
+ result = (self.target_object.nil? and other.target_object.nil?) ? 0 : self.target_object.object_id <=> other.target_object.object_id
156
155
  return result unless result == 0
157
- result = (self.method_name.nil? && other.method_name.nil?) ? 0 : self.method_name.to_s <=> other.method_name.to_s
156
+ result = (self.method_name.nil? and other.method_name.nil?) ? 0 : self.method_name.to_s <=> other.method_name.to_s
158
157
  return result unless result == 0
159
158
  result = self.instance_method? == other.instance_method?
160
159
  return 1 unless result == true
161
- result = (self.context.nil? && other.context.nil?) ? 0 : self.context <=> other.context
162
- return result
160
+ return 0 if self.context.nil? and other.context.nil?
161
+ return -1 if self.context.nil? and !other.context.nil?
162
+ return 1 if !self.context.nil? and other.context.nil?
163
+ return self.context <=> other.context
163
164
  end
164
165
 
165
166
  def eql? other
@@ -1,5 +1,6 @@
1
1
  require 'set'
2
2
  require 'aquarium/aspects/join_point'
3
+ require 'aquarium/aspects/exclusion_handler'
3
4
  require 'aquarium/utils'
4
5
  require 'aquarium/extensions'
5
6
  require 'aquarium/finders/finder_result'
@@ -18,8 +19,9 @@ module Aquarium
18
19
  include Aquarium::Utils::ArrayUtils
19
20
  include Aquarium::Utils::HashUtils
20
21
  include Aquarium::Utils::SetUtils
22
+ include ExclusionHandler
21
23
  include DefaultObjectHandler
22
-
24
+
23
25
  attr_reader :specification
24
26
 
25
27
  # Construct a Pointcut for methods in types or objects.
@@ -66,6 +68,24 @@ module Aquarium
66
68
  # One or more of <tt>:readers</tt>, <tt>:reader</tt> (synonymous),
67
69
  # <tt>:writers</tt>, and/or <tt>:writer</tt> (synonymous). By default, both
68
70
  # readers and writers are matched.
71
+ #
72
+ # <tt>:exclude_pointcuts => pc || [pc_list]</tt>::
73
+ # <tt>:exclude_pointcut => pc || [pc_list]</tt>::
74
+ # <tt>:exclude_join_points => jp || [jp_list]</tt>::
75
+ # <tt>:exclude_join_point => jp || [jp_list]</tt>::
76
+ # <tt>:exclude_types => type || [type_list]</tt>::
77
+ # <tt>:exclude_types => type || [type_list]</tt>::
78
+ # <tt>:exclude_type => type || [type_list]</tt>::
79
+ # <tt>:exclude_objects => object || [object_list]</tt>::
80
+ # <tt>:exclude_object => object || [object_list]</tt>::
81
+ # <tt>:exclude_methods => method || [method_list]</tt>::
82
+ # <tt>:exclude_method => method || [method_list]</tt>::
83
+ # <tt>:exclude_attributes => attribute || [attribute_list]</tt>::
84
+ # <tt>:exclude_attribute => attribute || [attribute_list]</tt>::
85
+ # Exclude the specified "things" from the matched join points. If pointcuts are
86
+ # excluded, they should be subsets of the matched pointcuts. Otherwise, the
87
+ # resulting pointcut will be empty!
88
+ #
69
89
  def initialize options = {}
70
90
  init_specification options
71
91
  init_candidate_types
@@ -113,22 +133,20 @@ module Aquarium
113
133
 
114
134
  attr_writer :join_points_matched, :join_points_not_matched, :specification, :candidate_types, :candidate_objects, :candidate_join_points
115
135
 
136
+ ALLOWED_OPTIONS_SINGULAR = %w[type object join_point method exclude_type exclude_object exclude_join_point exclude_pointcut exclude_method
137
+ default_object attribute method_option attribute_option]
138
+
116
139
  def init_specification options
117
140
  @specification = {}
118
141
  options ||= {}
119
142
  validate_options options
120
- @specification[:method_options] = Set.new(make_array(options[:method_options]))
121
- @specification[:attribute_options] = Set.new(make_array(options[:attribute_options]) )
122
- @specification[:types] = Set.new(make_array(options[:types], options[:type]))
123
- @specification[:objects] = Set.new(make_array(options[:objects], options[:object]))
124
- @specification[:join_points] = Set.new(make_array(options[:join_points], options[:join_point]))
125
- @specification[:exclude_types] = Set.new(make_array(options[:exclude_type], options[:exclude_types]))
126
- @specification[:exclude_objects] = Set.new(make_array(options[:exclude_object], options[:exclude_objects]))
127
- @specification[:exclude_join_points] = Set.new(make_array(options[:exclude_join_point], options[:exclude_join_points]))
128
- @specification[:exclude_methods] = Set.new(make_array(options[:exclude_method], options[:exclude_methods]))
129
- @specification[:default_object] = Set.new(make_array(options[:default_object]))
143
+ ALLOWED_OPTIONS_SINGULAR.each do |option|
144
+ self.instance_eval(<<-EOF, __FILE__, __LINE__)
145
+ @specification[:#{option}s]= Set.new(make_array(options[:#{option}], options[:#{option}s]))
146
+ EOF
147
+ end
130
148
  use_default_object_if_defined unless (types_given? || objects_given?)
131
- @specification[:attributes] = Set.new(make_array(options[:attributes], options[:attribute]))
149
+
132
150
  raise Aquarium::Utils::InvalidOptions.new(":all is not yet supported for :attributes.") if @specification[:attributes] == Set.new([:all])
133
151
  init_methods_specification options
134
152
  end
@@ -139,10 +157,11 @@ module Aquarium
139
157
  end
140
158
 
141
159
  def validate_options options
142
- knowns = %w[object objects type types join_point join_points
143
- exclude_object exclude_objects exclude_type exclude_types exclude_join_point exclude_join_points exclude_method exclude_methods
144
- method methods attribute attributes
145
- method_options attribute_options default_object].map {|x| x.intern}
160
+ knowns = []
161
+ ALLOWED_OPTIONS_SINGULAR.each do |x|
162
+ knowns << x.intern
163
+ knowns << "#{x}s".intern
164
+ end
146
165
  unknowns = options.keys - knowns
147
166
  raise Aquarium::Utils::InvalidOptions.new("Unknown options specified: #{unknowns.inspect}") if unknowns.size > 0
148
167
  end
@@ -175,7 +194,7 @@ module Aquarium
175
194
  EOF
176
195
  end
177
196
 
178
- %w[types objects join_points methods].each do |name|
197
+ %w[types objects join_points pointcuts methods].each do |name|
179
198
  class_eval(<<-EOF, __FILE__, __LINE__)
180
199
  def exclude_#{name}_given
181
200
  @specification[:exclude_#{name}]
@@ -227,15 +246,20 @@ module Aquarium
227
246
  find_join_points_for :type, candidate_types, make_all_method_names
228
247
  find_join_points_for :object, candidate_objects, make_all_method_names
229
248
  add_join_points_for_candidate_join_points
249
+ remove_excluded_join_points
230
250
  end
231
251
 
232
252
  def add_join_points_for_candidate_join_points
233
- @join_points_matched += @candidate_join_points.matched.keys.find_all do |jp|
234
- not (is_excluded_join_point?(jp) or is_excluded_type_or_object?(jp.type_or_object) or is_excluded_method?(jp.method_name))
235
- end
253
+ @join_points_matched += @candidate_join_points.matched.keys
236
254
  @join_points_not_matched += @candidate_join_points.not_matched.keys
237
255
  end
238
256
 
257
+ def remove_excluded_join_points
258
+ @join_points_matched.delete_if do |jp|
259
+ join_point_excluded? jp
260
+ end
261
+ end
262
+
239
263
  def find_join_points_for type_or_object_sym, candidates, method_names
240
264
  results = find_methods_for type_or_object_sym, candidates, method_names
241
265
  add_join_points results, type_or_object_sym
@@ -272,35 +296,6 @@ module Aquarium
272
296
  @specification[:exclude_methods]
273
297
  end
274
298
 
275
- def is_excluded_join_point? jp
276
- @specification[:exclude_join_points].include? jp
277
- end
278
-
279
- def is_excluded_type_or_object? type_or_object
280
- return true if @specification[:exclude_objects].include?(type_or_object)
281
- @specification[:exclude_types].find do |t|
282
- case t
283
- when String: type_or_object.name.eql?(t)
284
- when Symbol: type_or_object.name.eql?(t.to_s)
285
- when Regexp: type_or_object.name =~ t
286
- end
287
- end
288
- end
289
-
290
- def is_excluded_method? method
291
- is_explicitly_excluded_method?(method) or matches_excluded_method_regex?(method)
292
- end
293
-
294
- def is_explicitly_excluded_method? method
295
- @specification[:exclude_methods].include? method
296
- end
297
-
298
- def matches_excluded_method_regex? method
299
- regexs = @specification[:exclude_methods].find_all {|s| s.kind_of? Regexp}
300
- return false if regexs.empty?
301
- regexs.find {|re| method.to_s =~ re}
302
- end
303
-
304
299
  def self.make_attribute_readers attributes
305
300
  readers = attributes.map do |regexp_or_name|
306
301
  if regexp_or_name.kind_of? Regexp
@@ -173,7 +173,7 @@ module Aquarium
173
173
  # Must recalc reflect methods if we've switched to the type of the input object.
174
174
  reflection_method_names = make_methods_reflection_method_names type, "methods"
175
175
  end
176
- ancestors = eval "#{type.to_s}.ancestors + #{type.to_s}.included_modules"
176
+ ancestors = type.ancestors + type.included_modules
177
177
  return method_array if ancestors.nil? || ancestors.size <= 1 # 1 for type_or_object itself!
178
178
  ancestors.each do |ancestor|
179
179
  unless ancestor.name == type.to_s
@@ -8,6 +8,7 @@ require File.dirname(__FILE__) + '/type_finder'
8
8
 
9
9
  module Aquarium
10
10
  module Finders
11
+ # Deprecated - Will be removed in a future release!
11
12
  class ObjectFinder
12
13
  include Aquarium::Utils::ArrayUtils
13
14
 
@@ -16,27 +16,72 @@ module Aquarium
16
16
  end
17
17
  hash
18
18
  end
19
+
20
+ def self.visibility type_or_instance, method_sym, class_or_instance_only = nil, include_ancestors = true
21
+ find_method(type_or_instance, method_sym, class_or_instance_only, include_ancestors) do |type_or_instance, method_sym, protection|
22
+ return protection
23
+ end
24
+ end
25
+
26
+ def self.has_method type_or_instance, method_sym, class_or_instance_only = nil, include_ancestors = true
27
+ found = find_method(type_or_instance, method_sym, class_or_instance_only, include_ancestors) do |type_or_instance, method_sym, protection|
28
+ return true
29
+ end
30
+ found ? true : false # found could be nil; return false, if so
31
+ end
19
32
 
20
- def self.visibility type_or_instance, method_sym, class_or_instance_only = nil
33
+ def self.find_method type_or_instance, method_sym, class_or_instance_only = nil, include_ancestors = true
21
34
  meta_method_suffixes = determine_meta_method_suffixes type_or_instance, class_or_instance_only
22
35
  meta_method_suffixes.each do |suffix|
23
36
  %w[public protected private].each do |protection|
24
37
  meta_method = "#{protection}_#{suffix}"
25
- if find_method(type_or_instance, method_sym, meta_method)
26
- return protection.intern
38
+ methods = type_or_instance.send(meta_method, include_ancestors)
39
+ if methods.include?(method_sym.to_s)
40
+ return yield(type_or_instance, method_sym, protection.intern)
27
41
  end
28
42
  end
29
43
  end
30
44
  nil
31
45
  end
32
46
 
47
+ # Which type in a hierarchy actually defines a method?
48
+ def self.definer type_or_instance, method_sym, class_or_instance_only = nil
49
+ return nil if type_or_instance.nil? or method_sym.nil?
50
+ return nil unless has_method(type_or_instance, method_sym, class_or_instance_only)
51
+ ancestors = ancestors_for type_or_instance
52
+ determine_definer ancestors, type_or_instance, method_sym, class_or_instance_only
53
+ end
54
+
33
55
  private
34
56
 
57
+ # For objects, include the singleton/eigenclass in case a method of interest was actually defined just for the object.
58
+ def self.ancestors_for object
59
+ if Aquarium::Utils::TypeUtils.is_type? object
60
+ object.ancestors
61
+ else
62
+ eigen = (class << object; self; end)
63
+ [eigen] + eigen.ancestors
64
+ end
65
+ end
66
+
67
+ def self.determine_definer ancestors, type_or_instance, method_sym, class_or_instance_only
68
+ candidates = ancestors.find_all {|a| has_method(a, method_sym, class_or_instance_only, false) { true }}
69
+ if candidates.size == 2 and Aquarium::Utils::TypeUtils.is_type?(type_or_instance) == false
70
+ return determine_actual_parent(type_or_instance, candidates)
71
+ end
72
+ candidates.size == 1 ? candidates.first : raise("Bug: Got multiple types #{candidates.inspect} that implement method #{method_sym}")
73
+ end
74
+
75
+ def self.determine_actual_parent object, candidates
76
+ return nil unless (object.is_a?(candidates[0]) and object.is_a?(candidates[1]))
77
+ candidates[0].name == "" ? candidates[1] : candidates[0]
78
+ end
79
+
35
80
  def self.determine_meta_method_suffixes type_or_instance, class_or_instance_only
36
81
  limits = class_or_instance_only.nil? ? [:instance_method_only, :class_method_only] : [class_or_instance_only]
37
82
  meta_method_suffixes = []
38
83
  limits.each do |limit|
39
- if (Aquarium::Utils::TypeUtils.is_type? type_or_instance)
84
+ if Aquarium::Utils::TypeUtils.is_type? type_or_instance
40
85
  meta_method_suffixes << "instance_methods" if limit == :instance_method_only
41
86
  meta_method_suffixes << "methods" if limit == :class_method_only
42
87
  else
@@ -45,10 +90,6 @@ module Aquarium
45
90
  end
46
91
  meta_method_suffixes
47
92
  end
48
-
49
- def self.find_method type_or_instance, method_sym, meta_method
50
- type_or_instance.send(meta_method).include?(method_sym.to_s)
51
- end
52
93
  end
53
94
  end
54
95
  end