aquarium 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +4 -0
- data/EXAMPLES.rd +4 -0
- data/MIT-LICENSE +20 -0
- data/README +250 -0
- data/RELEASE-PLAN +1 -0
- data/Rakefile +236 -0
- data/UPGRADE +3 -0
- data/examples/aspect_design_example.rb +36 -0
- data/examples/design_by_contract_example.rb +88 -0
- data/examples/method_missing_example.rb +44 -0
- data/examples/method_tracing_example.rb +64 -0
- data/lib/aquarium.rb +7 -0
- data/lib/aquarium/aspects.rb +6 -0
- data/lib/aquarium/aspects/advice.rb +189 -0
- data/lib/aquarium/aspects/aspect.rb +577 -0
- data/lib/aquarium/aspects/default_object_handler.rb +27 -0
- data/lib/aquarium/aspects/dsl.rb +1 -0
- data/lib/aquarium/aspects/dsl/aspect_dsl.rb +61 -0
- data/lib/aquarium/aspects/join_point.rb +158 -0
- data/lib/aquarium/aspects/pointcut.rb +254 -0
- data/lib/aquarium/aspects/pointcut_composition.rb +36 -0
- data/lib/aquarium/extensions.rb +5 -0
- data/lib/aquarium/extensions/hash.rb +85 -0
- data/lib/aquarium/extensions/regexp.rb +20 -0
- data/lib/aquarium/extensions/set.rb +49 -0
- data/lib/aquarium/extensions/string.rb +13 -0
- data/lib/aquarium/extensions/symbol.rb +22 -0
- data/lib/aquarium/extras.rb +4 -0
- data/lib/aquarium/extras/design_by_contract.rb +64 -0
- data/lib/aquarium/finders.rb +4 -0
- data/lib/aquarium/finders/finder_result.rb +121 -0
- data/lib/aquarium/finders/method_finder.rb +228 -0
- data/lib/aquarium/finders/object_finder.rb +74 -0
- data/lib/aquarium/finders/type_finder.rb +127 -0
- data/lib/aquarium/utils.rb +9 -0
- data/lib/aquarium/utils/array_utils.rb +29 -0
- data/lib/aquarium/utils/hash_utils.rb +28 -0
- data/lib/aquarium/utils/html_escaper.rb +17 -0
- data/lib/aquarium/utils/invalid_options.rb +9 -0
- data/lib/aquarium/utils/method_utils.rb +18 -0
- data/lib/aquarium/utils/nil_object.rb +13 -0
- data/lib/aquarium/utils/set_utils.rb +32 -0
- data/lib/aquarium/version.rb +30 -0
- data/rake_tasks/examples.rake +7 -0
- data/rake_tasks/examples_specdoc.rake +8 -0
- data/rake_tasks/examples_with_rcov.rake +8 -0
- data/rake_tasks/verify_rcov.rake +7 -0
- data/spec/aquarium/aspects/advice_chain_node_spec.rb +34 -0
- data/spec/aquarium/aspects/advice_spec.rb +103 -0
- data/spec/aquarium/aspects/aspect_invocation_spec.rb +111 -0
- data/spec/aquarium/aspects/aspect_spec.rb +978 -0
- data/spec/aquarium/aspects/aspect_with_nested_types_spec.rb +129 -0
- data/spec/aquarium/aspects/concurrent_aspects_spec.rb +423 -0
- data/spec/aquarium/aspects/concurrent_aspects_with_objects_and_types_spec.rb +103 -0
- data/spec/aquarium/aspects/concurrently_accessed.rb +21 -0
- data/spec/aquarium/aspects/dsl/aspect_dsl_spec.rb +514 -0
- data/spec/aquarium/aspects/join_point_spec.rb +302 -0
- data/spec/aquarium/aspects/pointcut_and_composition_spec.rb +131 -0
- data/spec/aquarium/aspects/pointcut_or_composition_spec.rb +111 -0
- data/spec/aquarium/aspects/pointcut_spec.rb +800 -0
- data/spec/aquarium/extensions/hash_spec.rb +187 -0
- data/spec/aquarium/extensions/regex_spec.rb +40 -0
- data/spec/aquarium/extensions/set_spec.rb +105 -0
- data/spec/aquarium/extensions/string_spec.rb +25 -0
- data/spec/aquarium/extensions/symbol_spec.rb +37 -0
- data/spec/aquarium/extras/design_by_contract_spec.rb +68 -0
- data/spec/aquarium/finders/finder_result_spec.rb +359 -0
- data/spec/aquarium/finders/method_finder_spec.rb +878 -0
- data/spec/aquarium/finders/method_sorting_spec.rb +16 -0
- data/spec/aquarium/finders/object_finder_spec.rb +230 -0
- data/spec/aquarium/finders/type_finder_spec.rb +210 -0
- data/spec/aquarium/spec_example_classes.rb +117 -0
- data/spec/aquarium/spec_helper.rb +3 -0
- data/spec/aquarium/utils/array_utils_spec.rb +47 -0
- data/spec/aquarium/utils/hash_utils_spec.rb +48 -0
- data/spec/aquarium/utils/html_escaper_spec.rb +18 -0
- data/spec/aquarium/utils/method_utils_spec.rb +50 -0
- data/spec/aquarium/utils/nil_object_spec.rb +19 -0
- data/spec/aquarium/utils/set_utils_spec.rb +60 -0
- 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,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,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,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,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
|
+
|