aquarium 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+