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,74 @@
1
+ require 'set'
2
+ require File.dirname(__FILE__) + '/../utils/array_utils'
3
+ require File.dirname(__FILE__) + '/type_finder'
4
+
5
+
6
+ # Queries the ObjectSpace, so "immediate" objects are never returned.
7
+ # Uses Aquarium::Finders::TypeFinder to map type name regular expressions to types.
8
+
9
+ module Aquarium
10
+ module Finders
11
+ class ObjectFinder
12
+ include Aquarium::Utils::ArrayUtils
13
+
14
+ # finder_result = ObjectFinder.new.find [:types => [type_names_and_regexps] | :type => type_name_or_regexp]
15
+ # where the input types are regular expressions, there may be 0 to
16
+ # many matching types that appear in the returned hash.
17
+ # Use #find_all_by_types to find objects matching actual types, not just
18
+ # names or regular expressions.
19
+ # <tt>:types => type_names_and_regexps</tt>::
20
+ # One or an array of type names and regular expessions to match.
21
+ #
22
+ # <tt>:type => type_name_or_regexp</tt>::
23
+ # A type name or regular expession to match.
24
+ #
25
+ # Actually, there is effectively no difference between <tt>:types</tt> and
26
+ # <tt>:type</tt>. The singular form is "sugar"...
27
+ def find options = {}
28
+ type_regexpes_or_names = make_array options[:types]
29
+ type_regexpes_or_names += make_array options[:type]
30
+ if type_regexpes_or_names.empty?
31
+ return Aquarium::Finders::FinderResult.new
32
+ end
33
+ self.find_all_by type_regexpes_or_names
34
+ end
35
+
36
+ # Input is a list or array object with names and/or regular expressions.
37
+ def find_all_by *possible_type_regexpes_or_names
38
+ raise "Input name or name array can't be nil!" if possible_type_regexpes_or_names.nil?
39
+ result = Aquarium::Finders::FinderResult.new
40
+ make_array(*possible_type_regexpes_or_names).each do |expression|
41
+ found_types = Aquarium::Finders::TypeFinder.new.find :types => expression
42
+ found_types.matched_keys.each do |type|
43
+ result << find_all_by_types(type)
44
+ end
45
+ found_types.not_matched_keys.each do |type|
46
+ result.append_not_matched({type => Set.new([])})
47
+ end
48
+ end
49
+ result
50
+ end
51
+
52
+ # Return the objects in ObjectSpace for the input classes and modules.
53
+ def find_all_by_types *types
54
+ result = Aquarium::Finders::FinderResult.new
55
+ make_array(*types).each do |type|
56
+ object_space.each_object(type) do |obj|
57
+ result.append_matched type => obj
58
+ end
59
+ result.append_not_matched type => [] unless result.matched[type]
60
+ end
61
+ result
62
+ end
63
+
64
+ def initialize object_space = ObjectSpace
65
+ @object_space = object_space
66
+ end
67
+
68
+ protected
69
+
70
+ attr_reader :object_space
71
+
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,127 @@
1
+ require 'set'
2
+ require File.dirname(__FILE__) + '/../utils/array_utils'
3
+ require File.dirname(__FILE__) + '/../utils/invalid_options'
4
+ require File.dirname(__FILE__) + '/../extensions/regexp'
5
+ require File.dirname(__FILE__) + '/../extensions/symbol'
6
+ require File.dirname(__FILE__) + '/finder_result'
7
+
8
+ # Finds types known to the runtime environment.
9
+ module Aquarium
10
+ module Finders
11
+ class TypeFinder
12
+ include Aquarium::Utils::ArrayUtils
13
+
14
+ # Usage:
15
+ # finder_result = TypeFinder.new.find [ :types => ... | :names => ... ], [ :options => [...] ]
16
+ # where
17
+ # <tt>:types => types_and_type_names_and_regexps</tt>::
18
+ # The types or type names/regular expessions to match.
19
+ # Specify one or an array of values.
20
+ #
21
+ # <tt>:names => types_and_type_names_and_regexps</tt>::
22
+ # A synonym for <tt>:types</tt>. (Sugar)
23
+ #
24
+ # <tt>:type => type_or_type_name_or_regexp</tt>::
25
+ # Sugar for specifying one type
26
+ #
27
+ # <tt>:name => type_or_type_name_or_regexp</tt>::
28
+ # Sugar for specifying one type name.
29
+ #
30
+ # Actually, there is actually no difference between <tt>:types</tt>,
31
+ # <tt>:type</tt>, <tt>:names</tt>, and <tt>:name</tt>. The extra forms are "sugar"...
32
+ def find options = {}
33
+ result = Aquarium::Finders::FinderResult.new
34
+ unknown_options = []
35
+ options.each do |option, value|
36
+ case option.to_s
37
+ when "names", "types", "name", "type"
38
+ result << find_all_by(value)
39
+ else
40
+ unknown_options << option
41
+ end
42
+ end
43
+ raise Aquarium::Utils::InvalidOptions.new("Unknown options: #{unknown_options.inspect}.") if unknown_options.size > 0
44
+ return result
45
+ end
46
+
47
+ # For a name (not a regular expression), return the corresponding type.
48
+ # (Adapted from the RubyQuiz #113 solution by James Edward Gray II)
49
+ def find_by_name type_name
50
+ name = type_name.to_s # in case it's a symbol...
51
+ return nil if name.nil? || name.strip.empty?
52
+ name.strip!
53
+ found = []
54
+ begin
55
+ found << name.split("::").inject(Object) { |parent, const| parent.const_get(const) }
56
+ Aquarium::Finders::FinderResult.new(make_return_hash(found, []))
57
+ rescue NameError => ne
58
+ Aquarium::Finders::FinderResult.new(make_return_hash([], [type_name]))
59
+ end
60
+ end
61
+
62
+ alias :find_by_type :find_by_name
63
+
64
+ def find_all_by regexpes_or_names
65
+ raise Aquarium::Utils::InvalidOptions.new("Input type(s) can't be nil!") if regexpes_or_names.nil?
66
+ result = Aquarium::Finders::FinderResult.new
67
+ expressions = make_array regexpes_or_names
68
+ expressions.each do |expression|
69
+ expr = strip expression
70
+ next if empty expr
71
+ result_for_expression = find_namespace_matched expr
72
+ if result_for_expression.size > 0
73
+ result.append_matched result_for_expression
74
+ else
75
+ result.append_not_matched({expression => Set.new([])})
76
+ end
77
+ end
78
+ result
79
+ end
80
+
81
+ def self.is_recognized_option option_or_symbol
82
+ %w[name names type types].include? option_or_symbol.to_s
83
+ end
84
+
85
+ private
86
+
87
+ def strip expression
88
+ return nil if expression.nil?
89
+ expression.respond_to?(:strip) ? expression.strip : expression
90
+ end
91
+
92
+ def empty expression
93
+ expression.nil? || (expression.respond_to?(:empty?) && expression.empty?)
94
+ end
95
+
96
+ def find_namespace_matched expression
97
+ return {} if expression.nil?
98
+ found_types = [Module]
99
+ expr = expression.class.eql?(Regexp) ? expression.source : expression.to_s
100
+ return {} if expr.empty?
101
+ expr.split("::").each do |subexp|
102
+ found_types = find_next_types found_types, subexp
103
+ break if found_types.size == 0
104
+ end
105
+ make_return_hash found_types, []
106
+ end
107
+
108
+ def find_next_types parent_types, subname
109
+ # grep <parent>.constants because "subname" may be a regexp string!.
110
+ # Then use const_get to get the type itself.
111
+ found_types = []
112
+ parent_types.each do |parent|
113
+ matched = parent.constants.grep(/^#{subname}$/)
114
+ matched.each {|m| found_types << parent.const_get(m)}
115
+ end
116
+ found_types
117
+ end
118
+
119
+ def make_return_hash found, unmatched
120
+ h={}
121
+ h[:not_matched] = unmatched if unmatched.size > 0
122
+ found.each {|x| h[x] = Set.new([])}
123
+ h
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,9 @@
1
+ require 'aquarium/utils/array_utils'
2
+ require 'aquarium/utils/hash_utils'
3
+ require 'aquarium/utils/set_utils'
4
+
5
+ require 'aquarium/utils/method_utils'
6
+
7
+ require 'aquarium/utils/html_escaper'
8
+ require 'aquarium/utils/invalid_options'
9
+ require 'aquarium/utils/nil_object'
@@ -0,0 +1,29 @@
1
+ require 'aquarium/extensions/symbol'
2
+ require 'aquarium/utils/html_escaper'
3
+
4
+ module Aquarium
5
+ module Utils
6
+ module ArrayUtils
7
+
8
+ # Return an array containing the input item or list of items. If the input
9
+ # is an array, it is returned. In all cases, the constructed array is a
10
+ # flattened version of the input and any nil elements are removed by #strip_nils.
11
+ # Note that this behavior effectively converts +nil+ to +[]+.
12
+ def make_array *value_or_enum
13
+ strip_nils do_make_array(value_or_enum)
14
+ end
15
+
16
+ # Return a copy of the input array with all nils removed.
17
+ def strip_nils array
18
+ array.compact
19
+ end
20
+
21
+ private
22
+ def do_make_array value_or_enum
23
+ v = value_or_enum.flatten
24
+ v = v[0].to_a if (v.empty? == false && v[0].kind_of?(Set))
25
+ v
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,28 @@
1
+ require 'aquarium/utils/array_utils'
2
+
3
+ module Aquarium
4
+ module Utils
5
+ module HashUtils
6
+ include Aquarium::Utils::ArrayUtils
7
+
8
+ # Convert the input item or array into a hash with a nil value or the result
9
+ # of evaluating the optional input block, which takes a single argument for the item.
10
+ # If the input is already a hash, it is returned unmodified.
11
+ def make_hash item_or_array_or_hash
12
+ return {} if item_or_array_or_hash.nil?
13
+ return strip_nil_keys(item_or_array_or_hash) if item_or_array_or_hash.kind_of?(Hash)
14
+ hash = {}
15
+ [item_or_array_or_hash].flatten.each do |element|
16
+ unless element.nil?
17
+ hash[element] = block_given? ? yield(element) : nil
18
+ end
19
+ end
20
+ hash
21
+ end
22
+
23
+ def strip_nil_keys hash
24
+ hash.reject {|k,v| k.nil?}
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ module Aquarium
2
+ module Utils
3
+ module HtmlEscaper
4
+ def self.escape message
5
+ do_escape message
6
+ end
7
+ def escape message
8
+ HtmlEscaper.do_escape message
9
+ end
10
+
11
+ private
12
+ def self.do_escape message
13
+ message.gsub(/\</, "&lt;").gsub(/\>/, "&gt;")
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,9 @@
1
+ module Aquarium
2
+ module Utils
3
+ class InvalidOptions < Exception
4
+ def initialize *args
5
+ super
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ module Aquarium
2
+ module Utils
3
+ module MethodUtils
4
+ def self.method_args_to_hash *args
5
+ return {} if args.empty? || (args.size == 1 && args[0].nil?)
6
+ hash = (args[-1] and args[-1].kind_of? Hash) ? args.pop : {}
7
+ args.each do |arg|
8
+ if block_given?
9
+ hash[arg] = yield arg
10
+ else
11
+ hash[arg] = nil
12
+ end
13
+ end
14
+ hash
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ # One implementation of the Null Object Pattern (renamed "Nil" for Ruby).
2
+ # All methods not defined by Object simply return the Aquarium::Utils::NilObject itself.
3
+ # Users can subclass or add methods to instances to customize the behavior.
4
+
5
+ module Aquarium
6
+ module Utils
7
+ class Aquarium::Utils::NilObject
8
+ def method_missing method_sym, *args
9
+ self
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,32 @@
1
+ require 'set'
2
+
3
+ module Aquarium
4
+ module Utils
5
+ module SetUtils
6
+
7
+ # Return a set containing the input item or list of items. If the input
8
+ # is a set or an array, it is returned. In all cases, the constructed set is a
9
+ # flattened version of the input and any nil elements are removed by #strip_nils.
10
+ # Note that this behavior effectively converts +nil+ to +[]+.
11
+ def make_set *value_or_set_or_array
12
+ strip_nils(convert_to_set(*value_or_set_or_array))
13
+ end
14
+
15
+ # Return a new set that is a copy of the input set with all nils removed.
16
+ def strip_nils set
17
+ set.delete_if {|x| x.nil?}
18
+ end
19
+
20
+ protected
21
+ def convert_to_set *value_or_set_or_array
22
+ if value_or_set_or_array.nil? or value_or_set_or_array.empty?
23
+ Set.new
24
+ elsif value_or_set_or_array[0].kind_of?(Set)
25
+ value_or_set_or_array[0]
26
+ else
27
+ Set.new value_or_set_or_array.flatten
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,30 @@
1
+ module Aquarium
2
+ module VERSION
3
+ def self.build_tag
4
+ tag = "REL_" + [MAJOR, MINOR, TINY].join('_')
5
+ if defined?(RELEASE_CANDIDATE)
6
+ tag << "_" << RELEASE_CANDIDATE
7
+ end
8
+ tag
9
+ end
10
+
11
+ unless defined? MAJOR
12
+ MAJOR = 0
13
+ MINOR = 1
14
+ TINY = 0
15
+ RELEASE_CANDIDATE = ""
16
+
17
+ # RANDOM_TOKEN: 0.598704893979657
18
+ REV = "$LastChangedRevision: 7 $".match(/LastChangedRevision: (\d+)/)[1]
19
+
20
+ STRING = [MAJOR, MINOR, TINY].join('.')
21
+ FULL_VERSION = "#{STRING} (r#{REV})"
22
+ TAG = build_tag
23
+
24
+ NAME = "Aquarium"
25
+ URL = "http://aquarium.rubyforge.org"
26
+
27
+ DESCRIPTION = "#{NAME}-#{FULL_VERSION} - Aspect-Oriented Programming toolkit for Ruby\n#{URL}"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,7 @@
1
+ require 'rake'
2
+ require 'spec/rake/spectask'
3
+
4
+ desc "Run all examples"
5
+ Spec::Rake::SpecTask.new('examples') do |t|
6
+ t.spec_files = FileList['examples/**/*.rb']
7
+ end
@@ -0,0 +1,8 @@
1
+ require 'rake'
2
+ require 'spec/rake/spectask'
3
+
4
+ desc "Generate specdocs for examples for inclusion in RDoc"
5
+ Spec::Rake::SpecTask.new('examples_specdoc') do |t|
6
+ t.spec_files = FileList['examples/**/*.rb']
7
+ t.spec_opts = ["--format", "rdoc:EXAMPLES.rd"]
8
+ end
@@ -0,0 +1,8 @@
1
+ require 'rake'
2
+ require 'spec/rake/spectask'
3
+
4
+ desc "Run all examples with RCov"
5
+ Spec::Rake::SpecTask.new('examples_with_rcov') do |t|
6
+ t.spec_files = FileList['examples/**/*.rb']
7
+ t.rcov = true
8
+ end
@@ -0,0 +1,7 @@
1
+ require 'rake'
2
+ require 'spec/rake/verify_rcov'
3
+
4
+ RCov::VerifyTask.new(:verify_rcov => :spec) do |t|
5
+ t.threshold = 100.0 # Make sure you have rcov 0.7 or higher!
6
+ t.index_html = '../doc/output/coverage/index.html'
7
+ end
@@ -0,0 +1,34 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+ require File.dirname(__FILE__) + '/../spec_example_classes'
3
+ require 'aquarium'
4
+
5
+ # Some of AdviceChainNode and related classes are tested through advice_spec.rb. We rely on rcov to
6
+ # tell us otherwise...
7
+ describe Aquarium::Aspects::AdviceChainNode, "#each" do
8
+ it "should return each node in succession" do
9
+ static_join_point = :static_join_point
10
+ advice = lambda {|jp, *args| p "none advice"}
11
+ options = {
12
+ :advice_kind => :none,
13
+ :advice => advice,
14
+ :next_node => nil,
15
+ :static_join_point => static_join_point}
16
+ advice_chain = Aquarium::Aspects::AdviceChainNodeFactory.make_node options
17
+
18
+ Aquarium::Aspects::Advice.kinds_in_priority_order.each do |advice_kind|
19
+ advice = lambda {|jp, *args| p "#{advice_kind} advice"}
20
+ options[:advice_kind] = advice_kind
21
+ options[:advice] = advice,
22
+ options[:next_node] = advice_chain
23
+ advice_chain = Aquarium::Aspects::AdviceChainNodeFactory.make_node options
24
+ end
25
+
26
+ advice_chain.size.should == 6
27
+ expected_advice_kinds = Aquarium::Aspects::Advice.kinds_in_priority_order.reverse + [:none]
28
+ count = 0
29
+ advice_chain.each do |node|
30
+ node.advice_kind.should == expected_advice_kinds[count]
31
+ count += 1
32
+ end
33
+ end
34
+ end