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,36 @@
1
+ require 'aquarium/aspects/pointcut'
2
+ require 'aquarium/utils/array_utils'
3
+
4
+ # == Pointcut (composition)
5
+ # Since Pointcuts are queries, they can be composed, _i.e.,_ unions and intersections of
6
+ # them can be computed, yielding new Pointcuts.
7
+ class Aquarium::Aspects::Pointcut
8
+
9
+ def or pointcut2
10
+ result = Aquarium::Aspects::Pointcut.new
11
+ result.specification = specification.or(pointcut2.specification) do |value1, value2|
12
+ value1.union_using_eql_comparison value2
13
+ end
14
+ result.join_points_matched = join_points_matched.union_using_eql_comparison pointcut2.join_points_matched
15
+ result.join_points_not_matched = join_points_not_matched.union_using_eql_comparison pointcut2.join_points_not_matched
16
+ result.candidate_types = candidate_types.union pointcut2.candidate_types
17
+ result.candidate_objects = candidate_objects.union pointcut2.candidate_objects
18
+ result
19
+ end
20
+
21
+ alias :union :or
22
+
23
+ def and pointcut2
24
+ result = Aquarium::Aspects::Pointcut.new
25
+ result.specification = specification.and(pointcut2.specification) do |value1, value2|
26
+ value1.intersection_using_eql_comparison value2
27
+ end
28
+ result.join_points_matched = join_points_matched.intersection_using_eql_comparison pointcut2.join_points_matched
29
+ result.join_points_not_matched = join_points_not_matched.intersection_using_eql_comparison pointcut2.join_points_not_matched
30
+ result.candidate_types = candidate_types.intersection pointcut2.candidate_types
31
+ result.candidate_objects = candidate_objects.intersection pointcut2.candidate_objects
32
+ result
33
+ end
34
+
35
+ alias :intersection :and
36
+ end
@@ -0,0 +1,5 @@
1
+ require 'aquarium/extensions/hash'
2
+ require 'aquarium/extensions/regexp'
3
+ require 'aquarium/extensions/set'
4
+ require 'aquarium/extensions/string'
5
+ require 'aquarium/extensions/symbol'
@@ -0,0 +1,85 @@
1
+ require 'aquarium/utils/array_utils'
2
+
3
+ module Aquarium
4
+ module Extensions
5
+ module HashHelper
6
+
7
+ # Intersection of self with a second hash, which returns a new hash.
8
+ # If the same key is present in both, but the values are
9
+ # not "==" or "eql?", then the optional block is invoked to compute the intersection
10
+ # of the two values. If no block is given, it is assumed that the two key-value pairs
11
+ # should not be considered overlapping.
12
+ def intersection other_hash
13
+ return {} if other_hash.nil? or other_hash.empty?
14
+ keys2 = Set.new(self.keys).intersection(Set.new(other_hash.keys))
15
+ result = {}
16
+ keys2.each do |key|
17
+ values1 = self[key]
18
+ values2 = other_hash[key]
19
+ if values1 == values2 or values1.eql?(values2)
20
+ result[key] = values1
21
+ else block_given?
22
+ result[key] = yield values1, values2
23
+ end
24
+ end
25
+ result
26
+ end
27
+
28
+ alias :and :intersection
29
+
30
+ # Union of self with a second hash, which returns a new hash. If both hashes have
31
+ # the same key, the value will be the result of evaluating the given block. If no
32
+ # block is given, the result will be same behavior that "merge" provides; the
33
+ # value in the second hash "wins".
34
+ def union other_hash
35
+ result = {}
36
+ self.each {|key, value| result[key] = value}
37
+ return result if other_hash.nil? or other_hash.empty?
38
+ other_hash.each do |key, value|
39
+ if result[key].nil? or ! block_given?
40
+ result[key] = value
41
+ else
42
+ result[key] = yield result[key], value
43
+ end
44
+ end
45
+ result
46
+ end
47
+
48
+ alias :or :union
49
+
50
+ # It appears that Hash#== uses Object#== (i.e., self.object_id == other.object_id) when
51
+ # comparing hash keys. (Array#== uses the overridden #== for the elements.)
52
+ def eql_when_keys_compared? other
53
+ return true if self.object_id == other.object_id
54
+ return false unless self.class == other.class
55
+ keys1 = sort_keys(self.keys)
56
+ keys2 = sort_keys(other.keys)
57
+ return false unless keys1.eql?(keys2)
58
+ (0...keys1.size).each do |index|
59
+ # Handle odd cases where eql? and == behavior differently
60
+ return false unless self[keys1[index]].eql?(other[keys2[index]]) || self[keys1[index]] == other[keys2[index]]
61
+ end
62
+ true
63
+ end
64
+
65
+ def equivalent_key key
66
+ i = keys.index(key)
67
+ i.nil? ? nil : keys[i]
68
+ end
69
+
70
+ private
71
+
72
+ def sort_keys keys
73
+ keys.sort do |x, y|
74
+ x2 = x.respond_to?(:<=>) ? x : x.inspect
75
+ y2 = y.respond_to?(:<=>) ? y : y.inspect
76
+ x2 <=> y2
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ class Hash
84
+ include Aquarium::Extensions::HashHelper
85
+ end
@@ -0,0 +1,20 @@
1
+ # Adding useful methods to Regexp.
2
+ module Aquarium
3
+ module Extensions
4
+ module RegexpHelper
5
+ def empty?
6
+ source.strip.empty?
7
+ end
8
+ def strip
9
+ Regexp.new(source.strip)
10
+ end
11
+ def <=> other
12
+ to_s <=> other.to_s
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ class Regexp
19
+ include Aquarium::Extensions::RegexpHelper
20
+ end
@@ -0,0 +1,49 @@
1
+ require 'set'
2
+
3
+ # Override #== to fix behavior where it seems to ignore overrides of Object#== or Object#eql? when comparing set elements.
4
+ # Note that we can't put these definitions inside a helper module, as we do for other methods, and include in the reopened
5
+ # Hash class. If we do this, the method is not used!
6
+ class Set
7
+ def == set
8
+ equal?(set) and return true
9
+ set.is_a?(Set) && size == set.size or return false
10
+ ary = to_a
11
+ set.all? { |o| ary.include?(o) }
12
+ end
13
+
14
+ alias :eql? :==
15
+
16
+ def union_using_eql_comparison other
17
+ first = dup
18
+ second = other.dup
19
+ first.size > second.size ? do_union(first, second) : do_union(second, first)
20
+ end
21
+
22
+ def intersection_using_eql_comparison other
23
+ first = dup
24
+ second = other.dup
25
+ first.size > second.size ? do_intersection(first, second) : do_intersection(second, first)
26
+ end
27
+
28
+ private
29
+
30
+ def do_union larger, smaller
31
+ smaller.each do |x|
32
+ larger.add(x) unless contained_in(larger, x)
33
+ end
34
+ larger
35
+ end
36
+
37
+ def do_intersection larger, smaller
38
+ result = Set.new
39
+ smaller.each do |x|
40
+ result.add(x) if contained_in(larger, x)
41
+ end
42
+ result
43
+ end
44
+
45
+ def contained_in set, element
46
+ set.each {|x| return true if element == x}
47
+ false
48
+ end
49
+ end
@@ -0,0 +1,13 @@
1
+ module Aquarium
2
+ module Extensions
3
+ module StringHelper
4
+ def to_camel_case
5
+ split('_').map {|s| s[0,1]=s[0,1].capitalize; s}.join
6
+ end
7
+ end
8
+ end
9
+ end
10
+
11
+ class String
12
+ include Aquarium::Extensions::StringHelper
13
+ end
@@ -0,0 +1,22 @@
1
+ # Adding useful methods to Symbol.
2
+ module Aquarium
3
+ module Extensions
4
+ module SymbolHelper
5
+ def empty?
6
+ return to_s.strip.empty?
7
+ end
8
+
9
+ def strip
10
+ return to_s.strip.to_sym
11
+ end
12
+
13
+ def <=> other_symbol
14
+ self.to_s <=> other_symbol.to_s
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ class Symbol
21
+ include Aquarium::Extensions::SymbolHelper
22
+ end
@@ -0,0 +1,4 @@
1
+ # Extra add-ons, etc. for Aquarium. Note that this file is NOT included in aquarium.rb.
2
+ # You have to include it explicitly in your code.
3
+
4
+ require 'aquarium/extras/design_by_contract'
@@ -0,0 +1,64 @@
1
+ # A simple Design by Contract module. Adds advice to test that the contract, which is specified with
2
+ # a block passes. Note that it doesn't attempt to handle the correct behavior under contract
3
+ # inheritance (TODO).
4
+
5
+ require 'aquarium'
6
+
7
+ module Aquarium
8
+ module Extras
9
+ module DesignByContract
10
+
11
+ class ContractError < Exception
12
+ def initialize(message)
13
+ super
14
+ end
15
+ end
16
+
17
+ def precondition *args, &contract_block
18
+ message = handle_message_arg *args
19
+ add_advice :before, "precondition", message, *args, &contract_block
20
+ end
21
+
22
+ def postcondition *args, &contract_block
23
+ message = handle_message_arg *args
24
+ add_advice :after_returning, "postcondition", message, *args, &contract_block
25
+ end
26
+
27
+ def invariant *args, &contract_block
28
+ message = handle_message_arg *args
29
+ around *args do |jp, *args2|
30
+ DesignByContract.test_condition "invariant failure (before invocation): #{message}", jp, *args2, &contract_block
31
+ jp.proceed
32
+ DesignByContract.test_condition "invariant failure (after invocation): #{message}", jp, *args2, &contract_block
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def self.test_condition message, jp, *args
39
+ unless yield(jp, *args)
40
+ raise ContractError.new(message)
41
+ end
42
+ end
43
+
44
+ def add_advice kind, test_kind, message, *args, &contract_block
45
+ self.send(kind, *args) do |jp, *args2|
46
+ DesignByContract.test_condition "#{test_kind} failure: #{message}", jp, *args2, &contract_block
47
+ end
48
+ end
49
+
50
+ def handle_message_arg *args
51
+ options = args[-1]
52
+ return unless options.kind_of?(Hash)
53
+ message = options[:message]
54
+ options.delete :message
55
+ message || "(no error message)"
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ class Object
62
+ include Aquarium::Extras::DesignByContract
63
+ end
64
+
@@ -0,0 +1,4 @@
1
+ require 'aquarium/finders/finder_result'
2
+ require 'aquarium/finders/method_finder'
3
+ require 'aquarium/finders/object_finder'
4
+ require 'aquarium/finders/type_finder'
@@ -0,0 +1,121 @@
1
+ require 'aquarium/utils'
2
+ require 'aquarium/extensions'
3
+
4
+ module Aquarium
5
+ module Finders
6
+ class FinderResult
7
+ include Aquarium::Utils::HashUtils
8
+ include Aquarium::Utils::SetUtils
9
+
10
+ attr_accessor :not_matched, :matched
11
+
12
+ def convert_hash_values_to_sets hash
13
+ h = {}
14
+ hash.each do |key, value|
15
+ if value.is_a? Set
16
+ h[key] = value
17
+ elsif value.is_a? Array
18
+ h[key] = Set.new value
19
+ else
20
+ h[key] = Set.new([value])
21
+ end
22
+ end
23
+ h
24
+ end
25
+
26
+ private :convert_hash_values_to_sets
27
+
28
+ def initialize hash = {}
29
+ @matched = convert_hash_values_to_sets(hash.reject {|key, value| key.eql?(:not_matched)})
30
+ @not_matched = convert_hash_values_to_sets(make_hash(hash[:not_matched]) {|x| Set.new} || {})
31
+ end
32
+
33
+ NIL_OBJECT = FinderResult.new unless const_defined?(:NIL_OBJECT)
34
+
35
+ # Convenience method to get the keys for the matched.
36
+ def matched_keys
37
+ @matched.keys
38
+ end
39
+
40
+ # Convenience method to get the keys for the items that did not match.
41
+ def not_matched_keys
42
+ @not_matched.keys
43
+ end
44
+
45
+ def << other_result
46
+ append_matched other_result.matched
47
+ append_not_matched other_result.not_matched
48
+ self
49
+ end
50
+
51
+ # "Or" two results together
52
+ def or other_result
53
+ result = FinderResult.new
54
+ result.matched = hash_union(matched, other_result.matched)
55
+ result.not_matched = hash_union(not_matched, other_result.not_matched)
56
+ result
57
+ end
58
+
59
+ alias :union :or
60
+
61
+ # "And" two results together
62
+ def and other_result
63
+ result = FinderResult.new
64
+ result.matched = hash_intersection(matched, other_result.matched)
65
+ result.not_matched = hash_intersection(not_matched, other_result.not_matched)
66
+ result
67
+ end
68
+
69
+ alias :intersection :and
70
+
71
+ def append_matched other_hash = {}
72
+ @matched = convert_hash_values_to_sets hash_union(matched, other_hash)
73
+ end
74
+
75
+ def append_not_matched other_hash = {}
76
+ @not_matched = convert_hash_values_to_sets hash_union(not_matched, other_hash)
77
+ @not_matched.each_key {|key| purge_matched key}
78
+ end
79
+
80
+ def eql? other
81
+ object_id == other.object_id ||
82
+ (matched == other.matched and not_matched == other.not_matched)
83
+ end
84
+
85
+ alias :== :eql?
86
+
87
+ def inspect
88
+ "FinderResult: {matched: #{matched.inspect}, not_matched: #{not_matched.inspect}}"
89
+ end
90
+
91
+ alias :to_s :inspect
92
+
93
+ def empty?
94
+ matched.empty?
95
+ end
96
+
97
+ private
98
+
99
+ def purge_matched key
100
+ if @matched.keys.include? key
101
+ @not_matched[key] = @not_matched[key].delete_if {|nm| @matched[key].include?(nm)}
102
+ end
103
+ end
104
+
105
+ def hash_intersection hash1, hash2
106
+ return {} if hash1.nil? or hash2.nil?
107
+ hash1.intersection(hash2) do |value1, value2|
108
+ make_set(value1).intersection_using_eql_comparison(make_set(value2))
109
+ end
110
+ end
111
+
112
+ def hash_union hash1, hash2
113
+ return hash1 if hash2.nil? or hash2.empty?
114
+ return hash2 if hash1.nil? or hash1.empty?
115
+ hash1.union(hash2) do |value1, value2|
116
+ make_set(value1).union_using_eql_comparison(make_set(value2))
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,228 @@
1
+ require 'set'
2
+ require File.dirname(__FILE__) + '/../utils/array_utils'
3
+ require File.dirname(__FILE__) + '/../utils/invalid_options'
4
+ require File.dirname(__FILE__) + '/finder_result'
5
+
6
+ # Find methods and types and objects.
7
+ module Aquarium
8
+ module Finders
9
+ class MethodFinder
10
+ include Aquarium::Utils::ArrayUtils
11
+
12
+ # Returns a Aquarium::Finders::FinderResult for the hash of types, type names, and/or regular expressions
13
+ # and the corresponding method name <b>symbols</b> found.
14
+ # Method names, not method objects, are always returned, because we can only get
15
+ # method objects for instance methods if we have an instance!
16
+ #
17
+ # finder_result = MethodFinder.new.find :types => ... {, :methods => ..., :options => [...]}
18
+ # where
19
+ # "{}" indicate optional arguments
20
+ #
21
+ # <tt>:types => types_and_type_names_and_regexps</tt>::
22
+ # <tt>:type => types_and_type_names_and_regexps</tt>::
23
+ # One or more types, type names and/or regular expessions to match.
24
+ # Specify one or an array of values.
25
+ #
26
+ # <tt>:objects => objects</tt>::
27
+ # <tt>:object => objects</tt>::
28
+ # One or more objects to match.
29
+ # Specify one or an array of values.
30
+ # Note: Currently, string or symbol objects will be misinterpreted as type names!
31
+ #
32
+ # <tt>:methods => method_names_and_regexps</tt>::
33
+ # <tt>:method => method_names_and_regexps</tt>::
34
+ # One or more method names and regular expressions to match.
35
+ # Specify one or an array of values.
36
+ #
37
+ # <tt>:options => method_options</tt>::
38
+ # By default, searches for public instance methods. Specify one or more
39
+ # of the following options for alternatives. You can combine any of the
40
+ # <tt>:public</tt>, <tt>:protected</tt>, and <tt>:private</tt>, as well as
41
+ # <tt>:instance</tt> and <tt>:class</tt>.
42
+ #
43
+ # <tt>:public</tt>:: Search for public methods (default).
44
+ # <tt>:private</tt>:: Search for private methods.
45
+ # <tt>:protected</tt>:: Search for protected methods.
46
+ # <tt>:instance</tt>:: Search for instance methods.
47
+ # <tt>:class</tt>:: Search for class methods.
48
+ # <tt>:singleton</tt>:: Search for singleton methods. (Using :class for objects
49
+ # won't work and :class, :public, :protected, and :private are ignored when
50
+ # looking for singleton methods.)
51
+ # <tt>:suppress_ancestor_methods</tt>:: Suppress "ancestor" methods. This
52
+ # means that if you search for a override method +foo+ in a
53
+ # +Derived+ class that is defined in the +Base+ class, you won't find it!
54
+ #
55
+ def find options = {}
56
+ init_specification options
57
+ types_and_objects = input_types + input_objects
58
+ return Aquarium::Finders::FinderResult.new if types_and_objects.empty?
59
+ method_names_or_regexps = input_methods
60
+ if method_names_or_regexps.empty?
61
+ not_matched = {}
62
+ types_and_objects.each {|t| not_matched[t] = []}
63
+ return Aquarium::Finders::FinderResult.new(:not_matched => not_matched)
64
+ end
65
+ method_options = make_array options[:options]
66
+ find_all_by types_and_objects, method_names_or_regexps, method_options
67
+ end
68
+
69
+ # finder_result = MethodFinder.new.find_all_by types_and_objects, [methods, [options]]
70
+ # where if no +methods+ are specified, all are returned, subject to the +options+,
71
+ # as in #find.
72
+ def find_all_by types_and_objects, method_names_or_regexps = :all, *scope_options
73
+ return Aquarium::Finders::FinderResult.new if types_and_objects.nil?
74
+ @specification = make_options_hash scope_options
75
+ types_and_objects = make_array types_and_objects
76
+ names_or_regexps = make_methods_array method_names_or_regexps
77
+ types_and_objects_to_matched_methods = {}
78
+ types_and_objects_not_matched = {}
79
+ types_and_objects.each do |type_or_object|
80
+ reflection_method_names = make_methods_reflection_method_names type_or_object, "methods"
81
+ found_methods = Set.new
82
+ names_or_regexps.each do |name_or_regexp|
83
+ method_array = []
84
+ reflection_method_names.each do |reflect|
85
+ method_array += reflect_methods(type_or_object, reflect).grep(make_regexp(name_or_regexp))
86
+ end
87
+ if @specification[:suppress_ancestor_methods]
88
+ method_array = remove_ancestor_methods type_or_object, reflection_method_names, method_array
89
+ end
90
+ found_methods += method_array
91
+ end
92
+ if found_methods.empty?
93
+ types_and_objects_not_matched[type_or_object] = method_names_or_regexps
94
+ else
95
+ types_and_objects_to_matched_methods[type_or_object] = found_methods.to_a.sort.map {|m| m.intern}
96
+ end
97
+ end
98
+ Aquarium::Finders::FinderResult.new types_and_objects_to_matched_methods.merge(:not_matched => types_and_objects_not_matched)
99
+ end
100
+
101
+ NIL_OBJECT = MethodFinder.new unless const_defined?(:NIL_OBJECT)
102
+
103
+ def self.is_recognized_method_option string_or_symbol
104
+ %w[public private protected
105
+ instance class suppress_ancestor_methods].include? string_or_symbol.to_s
106
+ end
107
+
108
+ protected
109
+
110
+ def init_specification options
111
+ options[:options] = make_array(options[:options]) unless options[:options].nil?
112
+ validate options
113
+ @specification = options
114
+ end
115
+
116
+ def input_types
117
+ make_array @specification[:types], @specification[:type]
118
+ end
119
+
120
+ def input_objects
121
+ make_array @specification[:objects], @specification[:object]
122
+ end
123
+
124
+ def input_methods
125
+ make_array @specification[:methods], @specification[:method]
126
+ end
127
+
128
+ private
129
+
130
+ def make_methods_array *array_or_single_item
131
+ ary = make_array(*array_or_single_item).reject {|m| m.to_s.strip.empty?}
132
+ ary = [/^.+$/] if ary.include?(:all)
133
+ ary
134
+ end
135
+
136
+ def make_regexp name_or_regexp
137
+ name_or_regexp.kind_of?(Regexp) ? name_or_regexp : /^#{Regexp.escape(name_or_regexp.to_s)}$/
138
+ end
139
+
140
+ def remove_ancestor_methods type_or_object, reflection_method_names, method_array
141
+ type = type_or_object
142
+ unless (type_or_object.instance_of?(Class) or type_or_object.instance_of?(Module))
143
+ type = type_or_object.class
144
+ # Must recalc reflect methods if we've switched to the type of the input object.
145
+ reflection_method_names = make_methods_reflection_method_names type, "methods"
146
+ end
147
+ ancestors = eval "#{type.to_s}.ancestors + #{type.to_s}.included_modules"
148
+ return method_array if ancestors.nil? || ancestors.size <= 1 # 1 for type_or_object itself!
149
+ ancestors.each do |ancestor|
150
+ unless ancestor.name == type.to_s
151
+ reflection_method_names.each do |reflect|
152
+ method_array -= ancestor.method(reflect).call
153
+ end
154
+ end
155
+ end
156
+ method_array
157
+ end
158
+
159
+ def make_options_hash *scope_options
160
+ return {} if scope_options.nil?
161
+ options = {}
162
+ scope_options.flatten.each {|o| options[o] = '' unless o.nil?}
163
+ unless options[:class] || options[:instance] || options[:singleton]
164
+ options[:instance] = ''
165
+ end
166
+ options
167
+ end
168
+
169
+ def make_methods_reflection_method_names type_or_object, root_method_name
170
+ is_type = type_or_object.instance_of?(Class) || type_or_object.instance_of?(Module)
171
+ scope_prefixes = []
172
+ class_instance_prefixes = []
173
+ @specification.each do |opt, value|
174
+ opt_string = opt.to_s
175
+ case opt_string
176
+ when "public", "private", "protected"
177
+ scope_prefixes += [opt_string + "_"]
178
+ when "instance"
179
+ class_instance_prefixes += is_type ? [opt_string + "_"] : [""]
180
+ when "class"
181
+ # We want to use the "bare" (public_|private_|)<root_method_name> calls
182
+ # to get class methods, because we will invoke these methods on class objects!
183
+ # For instances, class methods aren't supported.
184
+ class_instance_prefixes += [""] if is_type
185
+ when "singleton"
186
+ class_instance_prefixes += [opt_string + "_"]
187
+ else
188
+ true # do nothing; "true" is here to make rcov happy.
189
+ end
190
+ end
191
+ scope_prefixes = ["public_"] if scope_prefixes.empty?
192
+ class_instance_prefixes = [""] if (class_instance_prefixes.empty? and is_type)
193
+ results = []
194
+ scope_prefixes.each do |scope_prefix|
195
+ class_instance_prefixes.each do |class_instance_prefix|
196
+ prefix = class_instance_prefix.eql?("singleton_") ? class_instance_prefix : scope_prefix + class_instance_prefix
197
+ results += [(prefix + root_method_name).intern]
198
+ end
199
+ end
200
+ results
201
+ end
202
+
203
+ def reflect_methods type_or_object, reflect_method
204
+ if type_or_object.kind_of?(String) or type_or_object.kind_of?(Symbol)
205
+ eval "#{type_or_object.to_s}.#{reflect_method}"
206
+ else
207
+ return [] unless type_or_object.respond_to? reflect_method
208
+ m = type_or_object.method reflect_method
209
+ m.call type_or_object
210
+ end
211
+ end
212
+
213
+ def validate options
214
+ allowed = %w[type types object objects method methods options class instance public private protected suppress_ancestor_methods].map {|x| x.intern}
215
+ okay, bad = options.keys.partition {|x| allowed.include?(x)}
216
+ raise Aquarium::Utils::InvalidOptions.new("Unrecognized option(s): #{bad.inspect}") unless bad.empty?
217
+ method_options = options[:options]
218
+ return if method_options.nil?
219
+ if method_options.include?(:singleton) &&
220
+ (method_options.include?(:class) || method_options.include?(:public) ||
221
+ method_options.include?(:protected) || method_options.include?(:private))
222
+ raise Aquarium::Utils::InvalidOptions.new("The :class:, :public, :protected, and :private flags can't be used with the :singleton flag.")
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end
228
+