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.
- 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
|
+
|