aquarium 0.1.8 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/CHANGES +59 -2
  2. data/README +33 -16
  3. data/RELEASE-PLAN +28 -5
  4. data/UPGRADE +11 -0
  5. data/examples/aspect_design_example.rb +2 -2
  6. data/examples/aspect_design_example_spec.rb +2 -2
  7. data/examples/design_by_contract_example.rb +4 -4
  8. data/examples/design_by_contract_example_spec.rb +4 -4
  9. data/examples/method_missing_example.rb +4 -1
  10. data/examples/method_missing_example_spec.rb +4 -1
  11. data/examples/method_tracing_example.rb +2 -2
  12. data/examples/method_tracing_example_spec.rb +16 -16
  13. data/lib/aquarium/aspects/advice.rb +47 -25
  14. data/lib/aquarium/aspects/aspect.rb +81 -39
  15. data/lib/aquarium/aspects/dsl/aspect_dsl.rb +1 -1
  16. data/lib/aquarium/aspects/exclusion_handler.rb +2 -2
  17. data/lib/aquarium/aspects/join_point.rb +28 -28
  18. data/lib/aquarium/aspects/pointcut.rb +61 -15
  19. data/lib/aquarium/extras/design_by_contract.rb +7 -7
  20. data/lib/aquarium/finders.rb +0 -1
  21. data/lib/aquarium/finders/method_finder.rb +10 -20
  22. data/lib/aquarium/finders/type_finder.rb +141 -75
  23. data/lib/aquarium/utils.rb +1 -0
  24. data/lib/aquarium/utils/logic_error.rb +9 -0
  25. data/lib/aquarium/utils/method_utils.rb +4 -3
  26. data/lib/aquarium/utils/nil_object.rb +1 -0
  27. data/lib/aquarium/utils/type_utils.rb +19 -0
  28. data/lib/aquarium/version.rb +2 -2
  29. data/spec/aquarium/aspects/advice_chain_node_spec.rb +2 -2
  30. data/spec/aquarium/aspects/advice_spec.rb +28 -5
  31. data/spec/aquarium/aspects/aspect_invocation_spec.rb +522 -289
  32. data/spec/aquarium/aspects/aspect_spec.rb +59 -41
  33. data/spec/aquarium/aspects/aspect_with_nested_types_spec.rb +7 -7
  34. data/spec/aquarium/aspects/aspect_with_subtypes_spec.rb +2 -2
  35. data/spec/aquarium/aspects/concurrent_aspects_spec.rb +1 -2
  36. data/spec/aquarium/aspects/concurrent_aspects_with_objects_and_types_spec.rb +1 -1
  37. data/spec/aquarium/aspects/dsl/aspect_dsl_spec.rb +34 -34
  38. data/spec/aquarium/aspects/join_point_spec.rb +79 -0
  39. data/spec/aquarium/aspects/pointcut_or_composition_spec.rb +13 -3
  40. data/spec/aquarium/aspects/pointcut_spec.rb +310 -63
  41. data/spec/aquarium/extras/design_by_contract_spec.rb +4 -4
  42. data/spec/aquarium/finders/method_finder_spec.rb +208 -54
  43. data/spec/aquarium/finders/type_finder_spec.rb +24 -88
  44. data/spec/aquarium/finders/type_finder_with_descendents_and_ancestors_spec.rb +206 -0
  45. data/spec/aquarium/spec_example_classes.rb +75 -12
  46. data/spec/aquarium/utils/logic_error_spec.rb +10 -0
  47. data/spec/aquarium/utils/type_utils_sample_classes.rb +203 -0
  48. data/spec/aquarium/utils/type_utils_spec.rb +47 -1
  49. metadata +48 -39
  50. data/lib/aquarium/finders/object_finder.rb +0 -75
  51. data/spec/aquarium/finders/object_finder_spec.rb +0 -231
@@ -36,6 +36,13 @@ module Aquarium
36
36
  # <tt>:type => type || [type_list]</tt>::
37
37
  # One or an array of types, type names and/or type regular expessions to match.
38
38
  #
39
+ # <tt>:types_and_descendents => type || [type_list]</tt>::
40
+ # <tt>:type_and_descendents => type || [type_list]</tt>::
41
+ # <tt>:types_and_ancestors => type || [type_list]</tt>::
42
+ # <tt>:type_and_ancestors => type || [type_list]</tt>::
43
+ # One or an array of types and either their descendents or ancestors.
44
+ # If you want both the descendents _and_ ancestors, use both options.
45
+ #
39
46
  # <tt>:objects => object || [object_list]</tt>::
40
47
  # <tt>:object => object || [object_list]</tt>::
41
48
  # Objects to match.
@@ -86,6 +93,13 @@ module Aquarium
86
93
  # excluded, they should be subsets of the matched pointcuts. Otherwise, the
87
94
  # resulting pointcut will be empty!
88
95
  #
96
+ # <tt>:exclude_types_and_descendents => type || [type_list]</tt>::
97
+ # <tt>:exclude_type_and_descendents => type || [type_list]</tt>::
98
+ # <tt>:exclude_types_and_ancestors => type || [type_list]</tt>::
99
+ # <tt>:exclude_type_and_ancestors => type || [type_list]</tt>::
100
+ # Exclude the specified types and their descendents, ancestors.
101
+ # If you want to exclude both the descendents _and_ ancestors, use both options.
102
+ #
89
103
  def initialize options = {}
90
104
  init_specification options
91
105
  init_candidate_types
@@ -94,7 +108,7 @@ module Aquarium
94
108
  init_join_points
95
109
  end
96
110
 
97
- attr_reader :join_points_matched, :join_points_not_matched, :specification, :candidate_types, :candidate_objects, :candidate_join_points
111
+ attr_reader :join_points_matched, :join_points_not_matched, :specification, :candidate_types, :candidate_types_excluded, :candidate_objects, :candidate_join_points
98
112
 
99
113
  # Two Considered equivalent only if the same join points matched and not_matched sets are equal,
100
114
  # the specifications are equal, and the candidate types and candidate objects are equal.
@@ -103,6 +117,7 @@ module Aquarium
103
117
  object_id == other.object_id ||
104
118
  (specification == other.specification &&
105
119
  candidate_types == other.candidate_types &&
120
+ candidate_types_excluded == other.candidate_types_excluded &&
106
121
  candidate_objects == other.candidate_objects &&
107
122
  join_points_matched == other.join_points_matched &&
108
123
  join_points_not_matched == other.join_points_not_matched)
@@ -115,7 +130,7 @@ module Aquarium
115
130
  end
116
131
 
117
132
  def inspect
118
- "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}}"
133
+ "Pointcut: {specification: #{specification.inspect}, candidate_types: #{candidate_types.inspect}, candidate_types_excluded: #{candidate_types_excluded.inspect}, candidate_objects: #{candidate_objects.inspect}, join_points_matched: #{join_points_matched.inspect}, join_points_not_matched: #{join_points_not_matched.inspect}}"
119
134
  end
120
135
 
121
136
  alias to_s inspect
@@ -131,11 +146,23 @@ module Aquarium
131
146
 
132
147
  protected
133
148
 
134
- attr_writer :join_points_matched, :join_points_not_matched, :specification, :candidate_types, :candidate_objects, :candidate_join_points
149
+ attr_writer :join_points_matched, :join_points_not_matched, :specification, :candidate_types, :candidate_types_excluded, :candidate_objects, :candidate_join_points
135
150
 
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]
151
+ ALLOWED_OPTIONS_SINGULAR = %w[
152
+ type object join_point method
153
+ exclude_type exclude_object exclude_join_point exclude_pointcut exclude_method
154
+ default_object attribute method_option attribute_option]
138
155
 
156
+ OTHER_ALLOWED_OPTIONS = %w[
157
+ type_and_descendents types_and_descendents type_and_ancestors types_and_ancestors
158
+ exclude_type_and_descendents exclude_types_and_descendents exclude_type_and_ancestors exclude_types_and_ancestors]
159
+
160
+ OTHER_ALLOWED_SPEC_KEYS = {
161
+ "types_and_descendents" => "type_and_descendents",
162
+ "types_and_ancestors" => "type_and_ancestors",
163
+ "exclude_types_and_descendents" => "exclude_type_and_descendents",
164
+ "exclude_types_and_ancestors" => "exclude_type_and_ancestors" }
165
+
139
166
  def init_specification options
140
167
  @specification = {}
141
168
  options ||= {}
@@ -145,6 +172,12 @@ module Aquarium
145
172
  @specification[:#{option}s]= Set.new(make_array(options[:#{option}], options[:#{option}s]))
146
173
  EOF
147
174
  end
175
+ OTHER_ALLOWED_SPEC_KEYS.keys.each do |option|
176
+ self.instance_eval(<<-EOF, __FILE__, __LINE__)
177
+ @specification[:#{option}]= Set.new(make_array(options[:#{option}], options[:#{OTHER_ALLOWED_SPEC_KEYS[option]}]))
178
+ EOF
179
+ end
180
+
148
181
  use_default_object_if_defined unless (types_given? || objects_given?)
149
182
 
150
183
  raise Aquarium::Utils::InvalidOptions.new(":all is not yet supported for :attributes.") if @specification[:attributes] == Set.new([:all])
@@ -162,6 +195,9 @@ module Aquarium
162
195
  knowns << x.intern
163
196
  knowns << "#{x}s".intern
164
197
  end
198
+ OTHER_ALLOWED_OPTIONS.each do |x|
199
+ knowns << x.intern
200
+ end
165
201
  unknowns = options.keys - knowns
166
202
  raise Aquarium::Utils::InvalidOptions.new("Unknown options specified: #{unknowns.inspect}") if unknowns.size > 0
167
203
  end
@@ -209,16 +245,26 @@ module Aquarium
209
245
  private
210
246
 
211
247
  def init_candidate_types
212
- explicit_types, type_regexps_or_names = @specification[:types].partition do |type|
213
- Aquarium::Utils::TypeUtils.is_type? type
214
- end
215
- excluded_explicit_types, excluded_type_regexps_or_names = @specification[:exclude_types].partition do |type|
216
- Aquarium::Utils::TypeUtils.is_type? type
248
+ finder_options = {}
249
+ exclude_finder_options = {}
250
+ ['', 'exclude_'].each do |prefix|
251
+ ['', '_and_ancestors', '_and_descendents'].each do |suffix|
252
+ # Because the user might be asking for descendents and/or ancestors, we convert explicitly-specified
253
+ # types into names, then "refind" them. While less efficient, it makes the code more uniform.
254
+ eval <<-EOF
255
+ #{prefix}type_regexps_or_names#{suffix} = @specification[:#{prefix}types#{suffix}].map do |t|
256
+ Aquarium::Utils::TypeUtils.is_type?(t) ? t.name : t
257
+ end
258
+ unless #{prefix}type_regexps_or_names#{suffix}.nil?
259
+ finder_options[:"#{prefix}types#{suffix}"] = #{prefix}type_regexps_or_names#{suffix}
260
+ exclude_finder_options[:"types#{suffix}"] = #{prefix}type_regexps_or_names#{suffix} if "#{prefix}".length > 0
261
+ end
262
+ EOF
263
+ end
217
264
  end
218
- possible_types = Aquarium::Finders::TypeFinder.new.find :types => type_regexps_or_names, :exclude_types => excluded_type_regexps_or_names
219
- possible_types.append_matched(make_hash(explicit_types) {|x| Set.new([])})
220
- @candidate_types = possible_types - Aquarium::Finders::TypeFinder.new.find(:types => excluded_type_regexps_or_names)
221
- @candidate_types.matched.delete_if {|type, value| excluded_explicit_types.include? type}
265
+ @candidate_types = Aquarium::Finders::TypeFinder.new.find finder_options
266
+ @candidate_types_excluded = Aquarium::Finders::TypeFinder.new.find exclude_finder_options
267
+ @specification[:exclude_types_calculated] = Set.new(@candidate_types_excluded.matched.keys)
222
268
  end
223
269
 
224
270
  def init_candidate_objects
@@ -243,7 +289,7 @@ module Aquarium
243
289
  def init_join_points
244
290
  @join_points_matched = Set.new
245
291
  @join_points_not_matched = Set.new
246
- find_join_points_for :type, candidate_types, make_all_method_names
292
+ find_join_points_for :type, (candidate_types - candidate_types_excluded), make_all_method_names
247
293
  find_join_points_for :object, candidate_objects, make_all_method_names
248
294
  add_join_points_for_candidate_join_points
249
295
  remove_excluded_join_points
@@ -28,25 +28,25 @@ module Aquarium
28
28
 
29
29
  def invariant *args, &contract_block
30
30
  message = handle_message_arg args
31
- Aspect.new make_args(:around, *args) do |jp, *params|
32
- DesignByContract.test_condition "invariant failure (before invocation): #{message}", jp, *params, &contract_block
31
+ Aspect.new make_args(:around, *args) do |jp, obj, *params|
32
+ DesignByContract.test_condition "invariant failure (before invocation): #{message}", jp, obj, *params, &contract_block
33
33
  result = jp.proceed
34
- DesignByContract.test_condition "invariant failure (after invocation): #{message}", jp, *params, &contract_block
34
+ DesignByContract.test_condition "invariant failure (after invocation): #{message}", jp, obj, *params, &contract_block
35
35
  result
36
36
  end
37
37
  end
38
38
 
39
39
  private
40
40
 
41
- def self.test_condition message, jp, *args
42
- unless yield(jp, *args)
41
+ def self.test_condition message, jp, obj, *args
42
+ unless yield(jp, obj, *args)
43
43
  raise ContractError.new(message)
44
44
  end
45
45
  end
46
46
 
47
47
  def add_advice kind, test_kind, message, *args, &contract_block
48
- Aspect.new make_args(kind, *args) do |jp, *params|
49
- DesignByContract.test_condition "#{test_kind} failure: #{message}", jp, *params, &contract_block
48
+ Aspect.new make_args(kind, *args) do |jp, obj, *params|
49
+ DesignByContract.test_condition "#{test_kind} failure: #{message}", jp, obj, *params, &contract_block
50
50
  end
51
51
  end
52
52
 
@@ -1,4 +1,3 @@
1
1
  require 'aquarium/finders/finder_result'
2
2
  require 'aquarium/finders/method_finder'
3
- require 'aquarium/finders/object_finder'
4
3
  require 'aquarium/finders/type_finder'
@@ -75,16 +75,17 @@ module Aquarium
75
75
  result
76
76
  end
77
77
 
78
- # finder_result = MethodFinder.new.find_all_by types_and_objects, [methods, [options]]
79
- # where if no +methods+ are specified, all are returned, subject to the +options+,
80
- # as in #find.
81
- # Note: Does not support the :exclude_method(s) options.
82
- def find_all_by types_and_objects, method_names_or_regexps = :all, *scope_options
83
- return Aquarium::Finders::FinderResult.new if types_and_objects.nil?
84
- @specification = { :options => init_method_options(scope_options) }
85
- do_find_all_by types_and_objects, method_names_or_regexps
78
+ NIL_OBJECT = MethodFinder.new unless const_defined?(:NIL_OBJECT)
79
+
80
+ RECOGNIZED_METHOD_OPTIONS = %w[public private protected
81
+ instance class exclude_ancestor_methods exclude_ancestor_methods]
82
+
83
+ def self.is_recognized_method_option string_or_symbol
84
+ RECOGNIZED_METHOD_OPTIONS.include? string_or_symbol.to_s
86
85
  end
87
-
86
+
87
+ protected
88
+
88
89
  def do_find_all_by types_and_objects, method_names_or_regexps
89
90
  types_and_objects = make_array types_and_objects
90
91
  names_or_regexps = make_methods_array method_names_or_regexps
@@ -112,17 +113,6 @@ module Aquarium
112
113
  Aquarium::Finders::FinderResult.new types_and_objects_to_matched_methods.merge(:not_matched => types_and_objects_not_matched)
113
114
  end
114
115
 
115
- NIL_OBJECT = MethodFinder.new unless const_defined?(:NIL_OBJECT)
116
-
117
- RECOGNIZED_METHOD_OPTIONS = %w[public private protected
118
- instance class exclude_ancestor_methods exclude_ancestor_methods]
119
-
120
- def self.is_recognized_method_option string_or_symbol
121
- RECOGNIZED_METHOD_OPTIONS.include? string_or_symbol.to_s
122
- end
123
-
124
- protected
125
-
126
116
  def init_specification options
127
117
  options[:options] = init_method_options(options[:options])
128
118
  validate options
@@ -1,5 +1,6 @@
1
1
  require 'set'
2
2
  require File.dirname(__FILE__) + '/../utils/array_utils'
3
+ require File.dirname(__FILE__) + '/../utils/type_utils'
3
4
  require File.dirname(__FILE__) + '/../utils/invalid_options'
4
5
  require File.dirname(__FILE__) + '/../extensions/hash'
5
6
  require File.dirname(__FILE__) + '/../extensions/regexp'
@@ -11,32 +12,59 @@ module Aquarium
11
12
  module Finders
12
13
  class TypeFinder
13
14
  include Aquarium::Utils::ArrayUtils
15
+ include Aquarium::Utils::TypeUtils
14
16
 
15
17
  # Usage:
16
- # finder_result = TypeFinder.new.find [ :types => ... | :names => ... ], [ :options => [...] ]
18
+ # finder_result = TypeFinder.new.find [options => [...] ]
17
19
  # where
18
20
  # <tt>:types => types_and_type_names_and_regexps</tt>::
19
- # The types or type names/regular expessions to match.
20
- # Specify one or an array of values.
21
- #
22
21
  # <tt>:names => types_and_type_names_and_regexps</tt>::
23
- # A synonym for <tt>:types</tt>. (Sugar)
22
+ # <tt>:type => types_and_type_names_and_regexps</tt>::
23
+ # <tt>:name => types_and_type_names_and_regexps</tt>::
24
+ # A single type or array of types, specified using any combination of the type
25
+ # name strings, the type "constants" and/or regular expessions. The four different
26
+ # flags are just "sugar" for each other.
27
+ #
28
+ # <tt>:types_and_descendents => types_and_type_names_and_regexps</tt>::
29
+ # <tt>:names_and_descendents => types_and_type_names_and_regexps</tt>::
30
+ # <tt>:type_and_descendents => types_and_type_names_and_regexps</tt>::
31
+ # <tt>:name_and_descendents => types_and_type_names_and_regexps</tt>::
32
+ #
33
+ # Same as for <tt>:types</tt> <i>etc.</i>, but also match their descendents.
34
+ #
35
+ # <tt>:types_and_ancestors => types_and_type_names_and_regexps</tt>::
36
+ # <tt>:names_and_ancestors => types_and_type_names_and_regexps</tt>::
37
+ # <tt>:type_and_ancestors => types_and_type_names_and_regexps</tt>::
38
+ # <tt>:name_and_ancestors => types_and_type_names_and_regexps</tt>::
39
+ #
40
+ # Same as for <tt>:types</tt> <i>etc.</i>, but also match their ancestors.
41
+ # This option will also match <tt>Class</tt>, <tt>Module</tt>, <i>etc.</>,
42
+ # so use with caution!
24
43
  #
25
- # <tt>:type => type_or_type_name_or_regexp</tt>::
26
- # Sugar for specifying one type
44
+ # To get both descendents and ancestors, use both options with the same type
45
+ # specification.
27
46
  #
28
- # <tt>:name => type_or_type_name_or_regexp</tt>::
29
- # Sugar for specifying one type name.
47
+ # The "other options" include the following:
30
48
  #
31
- # <tt>:exclude_type => type_or_type_name_or_regexp</tt>::
32
- # <tt>:exclude_types => type_or_type_name_or_regexp</tt>::
33
- # <tt>:exclude_name => type_or_type_name_or_regexp</tt>::
34
- # <tt>:exclude_names => type_or_type_name_or_regexp</tt>::
35
- # Exclude matching types from the list. These excluded types <b>won't</b> appear
36
- # in the FinderResult#not_matched.
37
49
  #
38
- # Actually, there is actually no difference between <tt>:types</tt>,
39
- # <tt>:type</tt>, <tt>:names</tt>, and <tt>:name</tt>. The extra forms are "sugar"...
50
+ # <tt>:exclude_type => types_and_type_names_and_regexps</tt>::
51
+ # <tt>:exclude_types => types_and_type_names_and_regexps</tt>::
52
+ # <tt>:exclude_name => types_and_type_names_and_regexps</tt>::
53
+ # <tt>:exclude_names => types_and_type_names_and_regexps</tt>::
54
+ # Exclude the specified type or list of types from the list of matched types.
55
+ # These excluded types <b>won't</b> appear in the FinderResult#not_matched.
56
+ #
57
+ # <tt>:exclude_types_and_descendents => types_and_type_names_and_regexps</tt>::
58
+ # <tt>:exclude_names_and_descendents => types_and_type_names_and_regexps</tt>::
59
+ # <tt>:exclude_type_and_descendents => types_and_type_names_and_regexps</tt>::
60
+ # <tt>:exclude_name_and_descendents => types_and_type_names_and_regexps</tt>::
61
+ #
62
+ # <tt>:exclude_types_and_ancestors => types_and_type_names_and_regexps</tt>::
63
+ # <tt>:exclude_names_and_ancestors => types_and_type_names_and_regexps</tt>::
64
+ # <tt>:exclude_type_and_ancestors => types_and_type_names_and_regexps</tt>::
65
+ # <tt>:exclude_name_and_ancestors => types_and_type_names_and_regexps</tt>::
66
+ #
67
+ # Exclude the descendents or ancestors, as well.
40
68
  #
41
69
  # Because of the special sigificance of the module ("namespace") separator "::", the rules
42
70
  # for the regular expressions are as follows. Assume that "subexp" is a "sub regular
@@ -57,79 +85,67 @@ module Aquarium
57
85
  # It behaves as <tt>/...::#{subexp}::.../</tt>, meaning that the subexp must match
58
86
  # the whole name between the "::" exactly.
59
87
  #
88
+ # Note: a common idiom in aspects is to include descendents of a type, but not the type
89
+ # itself. You can do as in the following example:
90
+ # <tt>... :type_and_descendents => "Foo", :exclude_type => "Foo"
60
91
  def find options = {}
61
92
  result = Aquarium::Finders::FinderResult.new
62
93
  excluded = Aquarium::Finders::FinderResult.new
63
94
  unknown_options = []
95
+ input_type_nil = false
64
96
  options.each do |option, value|
65
97
  unless TypeFinder.is_recognized_option option
66
98
  unknown_options << option
67
99
  next
68
100
  end
69
-
101
+ if value.nil?
102
+ input_type_nil = true
103
+ next
104
+ end
70
105
  if option.to_s =~ /^exclude_/
71
- excluded << find_all_by(value)
106
+ excluded << find_matching(value, option)
72
107
  else
73
- result << find_all_by(value)
108
+ result << find_matching(value, option)
74
109
  end
75
110
  end
76
- raise Aquarium::Utils::InvalidOptions.new("Unknown options: #{unknown_options.inspect}.") if unknown_options.size > 0
111
+ handle_errors unknown_options, input_type_nil
77
112
  result - excluded
78
113
  end
79
114
 
80
- # For a name (not a regular expression), return the corresponding type.
81
- # (Adapted from the RubyQuiz #113 solution by James Edward Gray II)
82
- def find_by_name type_name
83
- name = type_name.to_s # in case it's a symbol...
84
- return nil if name.nil? || name.strip.empty?
85
- name.strip!
86
- found = []
87
- begin
88
- found << name.split("::").inject(Object) { |parent, const| parent.const_get(const) }
89
- Aquarium::Finders::FinderResult.new(make_return_hash(found, []))
90
- rescue NameError => ne
91
- Aquarium::Finders::FinderResult.new(make_return_hash([], [type_name]))
92
- end
93
- end
94
-
95
- alias :find_by_type :find_by_name
96
-
97
- def find_all_by regexpes_or_names
98
- raise Aquarium::Utils::InvalidOptions.new("Input type(s) can't be nil!") if regexpes_or_names.nil?
99
- find_matching regexpes_or_names
115
+ protected
116
+
117
+ def handle_errors unknown_options, input_type_nil
118
+ message = ""
119
+ message += "Unknown options: #{unknown_options.inspect}. " unless unknown_options.empty?
120
+ message += "Input type specification can't be nil! " if input_type_nil
121
+ raise Aquarium::Utils::InvalidOptions.new(message) unless message.empty?
100
122
  end
101
123
 
102
124
  def self.is_recognized_option option_or_symbol
103
- %w[name names type types exclude_type exclude_types exclude_name exclude_names].include? option_or_symbol.to_s
104
- end
105
-
106
- private
107
-
108
- def strip expression
109
- return nil if expression.nil?
110
- expression.respond_to?(:strip) ? expression.strip : expression
111
- end
112
-
113
- def empty expression
114
- expression.nil? || (expression.respond_to?(:empty?) && expression.empty?)
125
+ %w[name names type types].each do |t|
126
+ ['', "exclude_"].each do |excl|
127
+ return true if ["#{excl}#{t}", "#{excl}#{t}_and_descendents", "#{excl}#{t}_and_ancestors"].include?(option_or_symbol.to_s)
128
+ end
129
+ end
130
+ false
115
131
  end
116
132
 
117
- def find_matching regexpes_or_names
133
+ def find_matching regexpes_or_names, option
118
134
  result = Aquarium::Finders::FinderResult.new
119
135
  expressions = make_array regexpes_or_names
120
136
  expressions.each do |expression|
121
137
  expr = strip expression
122
138
  next if empty expr
123
139
  if expr.kind_of? Regexp
124
- result << find_namespace_matched(expr)
140
+ result << find_namespace_matched(expr, option)
125
141
  else
126
- result << find_by_name(expr)
142
+ result << find_by_name(expr, option)
127
143
  end
128
144
  end
129
145
  result
130
146
  end
131
147
 
132
- def find_namespace_matched expression
148
+ def find_namespace_matched expression, option
133
149
  expr = expression.kind_of?(Regexp) ? expression.source : expression.to_s
134
150
  return nil if expr.empty?
135
151
  found_types = [Module]
@@ -140,12 +156,26 @@ module Aquarium
140
156
  break if found_types.size == 0
141
157
  end
142
158
  if found_types.size > 0
143
- Aquarium::Finders::FinderResult.new make_return_hash(found_types, [])
159
+ finish_and_make_successful_result found_types, option
144
160
  else
145
- Aquarium::Finders::FinderResult.new :not_matched => {expression => Set.new([])}
161
+ make_failed_result expression
146
162
  end
147
163
  end
148
164
 
165
+ # For a name (not a regular expression), return the corresponding type.
166
+ # (Adapted from the RubyQuiz #113 solution by James Edward Gray II)
167
+ def find_by_name type_name, option
168
+ name = type_name.to_s # in case it's a symbol...
169
+ return nil if name.nil? || name.strip.empty?
170
+ name.strip!
171
+ begin
172
+ found = [name.split("::").inject(Object) { |parent, const| parent.const_get(const) }]
173
+ finish_and_make_successful_result found, option
174
+ rescue NameError
175
+ make_failed_result type_name
176
+ end
177
+ end
178
+
149
179
  def find_next_types enclosing_types, subexp, suppress_lh_ctrl_a, suppress_rh_ctrl_z
150
180
  # grep <parent>.constants because "subexp" may be a regexp string!.
151
181
  # Then use const_get to get the type itself.
@@ -154,30 +184,66 @@ module Aquarium
154
184
  rhs = suppress_rh_ctrl_z ? "" : "\\Z"
155
185
  regexp = /#{lhs}#{subexp}#{rhs}/
156
186
  enclosing_types.each do |parent|
157
- parent.constants.grep(regexp).each do |m|
158
- found_types << get_type_from_parent(parent, m, regexp)
187
+ next unless parent.respond_to?(:constants)
188
+ parent.constants.grep(regexp) do |name|
189
+ begin
190
+ found_types << parent.const_get(name)
191
+ rescue NameError # ignore
192
+ end
159
193
  end
160
194
  end
161
195
  found_types
162
196
  end
163
197
 
164
- def make_return_hash found, unmatched
165
- h={}
166
- h[:not_matched] = unmatched if unmatched.size > 0
167
- found.each {|x| h[x] = Set.new([])}
168
- h
198
+ def finish_and_make_successful_result found, option
199
+ all = prettify(found + handle_ancestors_and_descendents(found, option))
200
+ hash = make_return_hash(all)
201
+ Aquarium::Finders::FinderResult.new hash
202
+ end
203
+
204
+ def make_failed_result name
205
+ Aquarium::Finders::FinderResult.new :not_matched => {name => Set.new([])}
206
+ end
207
+
208
+ def handle_ancestors_and_descendents types, option
209
+ result = []
210
+ result << add_descendents(types) if should_find_descendents(option)
211
+ result << add_ancestors(types) if should_find_ancestors(option)
212
+ result
213
+ end
214
+
215
+ def add_descendents types
216
+ types.inject([]) { |memo, t| memo << Aquarium::Utils::TypeUtils.descendents(t) }
217
+ end
218
+ def add_ancestors types
219
+ types.inject([]) { |memo, t| memo << t.ancestors }
169
220
  end
170
221
 
171
- protected
172
- def get_type_from_parent parent, name, regexp
173
- begin
174
- parent.const_get(name)
175
- rescue => e
176
- msg = "ERROR: for enclosing type '#{parent.inspect}', #{parent.inspect}.constants.grep(/#{regexp.source}/) returned a list including #{name.inspect}."
177
- msg += "However, #{parent.inspect}.const_get('#{name}') raised exception #{e}. Please report this bug to the Aquarium team. Thanks."
178
- raise e.exception(msg)
179
- end
222
+ def should_find_descendents option
223
+ option.to_s.include? "_descendents"
224
+ end
225
+ def should_find_ancestors option
226
+ option.to_s.include? "_ancestors"
227
+ end
228
+
229
+
230
+ def make_return_hash found
231
+ prettify(found).inject({}) {|hash, x| hash[x] = Set.new([]); hash}
232
+ end
233
+
234
+ def prettify array
235
+ array.flatten.uniq.inject([]) {|memo, x| memo << x unless empty(x); memo}
180
236
  end
237
+
238
+ def strip expression
239
+ return nil if expression.nil?
240
+ expression.respond_to?(:strip) ? expression.strip : expression
241
+ end
242
+
243
+ def empty thing
244
+ thing.nil? || (thing.respond_to?(:empty?) && thing.empty?)
245
+ end
246
+
181
247
  end
182
248
  end
183
249
  end