aquarium 0.1.8 → 0.2.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 (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