aquarium 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/CHANGES +26 -5
  2. data/README +8 -8
  3. data/RELEASE-PLAN +20 -2
  4. data/TODO.rb +26 -0
  5. data/UPGRADE +5 -5
  6. data/examples/aspect_design_example.rb +1 -1
  7. data/examples/aspect_design_example_spec.rb +1 -1
  8. data/examples/design_by_contract_example.rb +4 -9
  9. data/examples/design_by_contract_example_spec.rb +7 -9
  10. data/examples/exception_wrapping_example.rb +48 -0
  11. data/examples/exception_wrapping_example_spec.rb +49 -0
  12. data/examples/reusable_aspect_hack_example.rb +56 -0
  13. data/examples/reusable_aspect_hack_example_spec.rb +80 -0
  14. data/lib/aquarium.rb +1 -0
  15. data/lib/aquarium/aspects.rb +1 -1
  16. data/lib/aquarium/aspects/advice.rb +16 -13
  17. data/lib/aquarium/aspects/aspect.rb +81 -56
  18. data/lib/aquarium/aspects/join_point.rb +4 -4
  19. data/lib/aquarium/aspects/pointcut.rb +49 -73
  20. data/lib/aquarium/dsl.rb +2 -0
  21. data/lib/aquarium/dsl/aspect_dsl.rb +77 -0
  22. data/lib/aquarium/{aspects/dsl → dsl}/object_dsl.rb +2 -2
  23. data/lib/aquarium/extras/design_by_contract.rb +1 -1
  24. data/lib/aquarium/finders.rb +1 -1
  25. data/lib/aquarium/finders/method_finder.rb +26 -26
  26. data/lib/aquarium/finders/type_finder.rb +45 -39
  27. data/lib/aquarium/utils/array_utils.rb +6 -5
  28. data/lib/aquarium/utils/default_logger.rb +2 -1
  29. data/lib/aquarium/utils/options_utils.rb +178 -67
  30. data/lib/aquarium/utils/set_utils.rb +8 -3
  31. data/lib/aquarium/version.rb +1 -1
  32. data/spec/aquarium/aspects/aspect_invocation_spec.rb +111 -14
  33. data/spec/aquarium/aspects/aspect_spec.rb +91 -7
  34. data/spec/aquarium/aspects/pointcut_spec.rb +61 -0
  35. data/spec/aquarium/{aspects/dsl → dsl}/aspect_dsl_spec.rb +76 -32
  36. data/spec/aquarium/finders/method_finder_spec.rb +80 -80
  37. data/spec/aquarium/finders/type_finder_spec.rb +57 -52
  38. data/spec/aquarium/finders/type_finder_with_descendents_and_ancestors_spec.rb +12 -12
  39. data/spec/aquarium/spec_example_types.rb +4 -3
  40. data/spec/aquarium/utils/array_utils_spec.rb +9 -7
  41. data/spec/aquarium/utils/options_utils_spec.rb +106 -5
  42. data/spec/aquarium/utils/set_utils_spec.rb +14 -0
  43. metadata +12 -7
  44. data/lib/aquarium/aspects/dsl.rb +0 -2
  45. data/lib/aquarium/aspects/dsl/aspect_dsl.rb +0 -64
data/CHANGES CHANGED
@@ -1,3 +1,24 @@
1
+ == Version 0.4.1
2
+
3
+ V0.4.1 adds a few bug fixes, a few more user examples, internal refactoring and some performance
4
+ improvements.
5
+
6
+ Bug fixes:
7
+ 19116 When an exception is thrown during advice execution, the error message always reports the advice type is :before!
8
+ 19261 after_raising DSL method provides no way to specify exceptions
9
+
10
+ Enhancements:
11
+ 18705 Remove duplication and complexity in options-handling code
12
+ 19320 Move the Aquarium::...::AspectDSL file to Aquarium::DSL for convenience
13
+ 19399 Improve the Design by Contract example
14
+
15
+ I added a new :exceptions argument (synonym :exception) that takes a single exception or list thereof.
16
+ You can only use this argument with :after_raising. If you specify exceptions with the latter and use
17
+ the :exceptions argument, the values will be merged.
18
+
19
+ I thought it was ugly to have to type "include Aquarium::Aspects::DSL::AspectDSL", so I moved the code so
20
+ now it's "include Aquarium::DSL". However, for backwards compatibility, the old module still works.
21
+
1
22
  == Version 0.4.0
2
23
 
3
24
  V0.4.0 adds specs to characterize and test advising Java classes when running on JRuby and adds several
@@ -300,7 +321,7 @@ Fixing 13650 required an API change, which is why I've tagged this release "0.1.
300
321
  something like "0.1.1" (and the changes don't seem big enough to warrant "0.2.0"...).
301
322
 
302
323
  Previously, requiring "aquarium.rb" in the top-level "lib" directory would implicitly require
303
- lib/aquarium/aspects/dsl/aspect_dsl.rb, which
324
+ lib/aquarium/dsl/aspect_dsl.rb, which
304
325
  has Object include the AspectDSL module. This module adds methods like :before and :after to Object.
305
326
  Unfortunately, those methods collide with methods of the same name that Rails adds to Object. It was
306
327
  also a bit presumptuous of me to assume that everyone wanted those methods on Object ;)
@@ -314,22 +335,22 @@ then do the following:
314
335
 
315
336
  class MyClass # reopen "MyClass"
316
337
  # Add the methods as _class_ methods
317
- include Aquarium::Aspects::DSL::AspectDSL
338
+ include Aquarium::DSL
318
339
  end
319
340
 
320
341
  or, use (class|module)_eval:
321
342
 
322
- require 'aquarium/aspects/dsl/aspect_dsl'
343
+ require 'aquarium/dsl/aspect_dsl'
323
344
 
324
345
  MyClass.class_eval do
325
346
  # Add the methods as _class_ methods
326
- include Aquarium::Aspects::DSL::AspectDSL
347
+ include Aquarium::DSL
327
348
  end
328
349
 
329
350
  To add the methods as _instance_ methods on individual objects:
330
351
 
331
352
  object = MyClass.new
332
- object.extend(Aquarium::Aspects::DSL::AspectDSL)
353
+ object.extend(Aquarium::DSL)
333
354
 
334
355
 
335
356
  Note: as discussed at http://practicalruby.blogspot.com/2007/02/reopen-with-moduleeval.html,
data/README CHANGED
@@ -85,9 +85,9 @@ Here is an example that traces invocations of all public instance methods (inclu
85
85
  result # block needs to return the result of the "proceed"!
86
86
  end
87
87
 
88
- The advice to execute at each join point is the block. The pointcut is the set of all public instance methods in Foo and Bar. (There are additional options available for specifying class methods, protected methods, excluding inherited (ancestor) methods, etc.) Here is the same example using the convenience DSL that adds aspect methods to Object (available only if you require aquarium/aspects/dsl/object_dsl', since other toolkits, like Rails, define similar methods on Object!).
88
+ The advice to execute at each join point is the block. The pointcut is the set of all public instance methods in Foo and Bar. (There are additional options available for specifying class methods, protected methods, excluding inherited (ancestor) methods, etc.) Here is the same example using the convenience DSL that adds aspect methods to Object (available only if you require aquarium/dsl/object_dsl', since other toolkits, like Rails, define similar methods on Object!).
89
89
 
90
- require 'aquarium/aspects/dsl/object_dsl'
90
+ require 'aquarium/dsl/object_dsl'
91
91
  around :calls_to => :all_methods, :on_types => [Foo, Bar] do |join_point, object, *args|
92
92
  p "Entering: #{join_point.target_type.name}##{join_point.method_name} for object #{object}"
93
93
  result = join_point.proceed
@@ -99,23 +99,23 @@ See "examples/method_tracing_example.rb" for a more detailed version of this exa
99
99
 
100
100
  If you don't want to add these methods to Object, you can also add them individually to modules, classes, or objects:
101
101
 
102
- require 'aquarium/aspects/dsl/aspect_dsl' # NOT object_dsl
102
+ require 'aquarium'
103
103
  ...
104
104
  module MyModule
105
- include Aquarium::Aspects::DSL::AspectDSL
105
+ include Aquarium::DSL
106
106
  end
107
107
 
108
108
  class MyClass
109
- include Aquarium::Aspects::DSL::AspectDSL
109
+ include Aquarium::DSL
110
110
  end
111
111
 
112
112
  my_object = MyOtherClass.new
113
- my_object.extend (Aquarium::Aspects::DSL::AspectDSL)
113
+ my_object.extend (Aquarium::DSL)
114
114
 
115
115
  If you use the DSL inside a class and omit the :type(s) and :object(s) options, "self" is assumed.
116
116
 
117
117
  class Foo
118
- include Aquarium::Aspects::DSL::AspectDSL
118
+ include Aquarium::DSL
119
119
  ...
120
120
  def critical_operation *args
121
121
  ...
@@ -199,7 +199,7 @@ Some of the synonyms:
199
199
  If no types or objects are specified, the object defaults to "self". However, this default is only supported when using the DSL to create an aspect, e.g.,
200
200
 
201
201
  class MyClass
202
- include Aquarium::Aspects::DSL::AspectDSL
202
+ include Aquarium::DSL
203
203
  def doit; ...; end
204
204
 
205
205
  around :method => doit, ... # Implicit :object => self, i.e., MyClass
@@ -4,7 +4,7 @@
4
4
 
5
5
  Mostly bug fixes and enhancements. Only "minor" breakages of backwards compatibility.
6
6
 
7
- === Versions 0.2.0
7
+ === Versions 0.2
8
8
 
9
9
  Change the argument list for advice to |join_point, object, *args| from |join_point, *args|,
10
10
  where "*args" are the arguments passed to the invoked join point (method). Since nontrivial
@@ -13,7 +13,7 @@ advice usually needs the object being advised, it became clear that having to ca
13
13
 
14
14
  This change will obvious break existing aspects.
15
15
 
16
- === Versions 0.3+.0
16
+ === Versions 0.3+
17
17
 
18
18
  More refinements and simplifications to the API and functionality, including much-needed
19
19
  redundancy reduction. I haven't used mocks much in the specs, but they would help improve the
@@ -43,6 +43,24 @@ composition of complex pointcuts easier.
43
43
 
44
44
  I also want to ensure full support for running in JRuby. In particular, you should be able to
45
45
  advise Java types!
46
+
47
+ === Version 0.5
48
+
49
+ My goals for this release are performance improvements (#19321) and investigating some ideas
50
+ for a real DSL, meaning declarative statements in blocks, rather than method arguments. It will be
51
+ redundant somewhat with the existing "method-argument form", as it exists today, but it will set
52
+ the stage for much more complex aspect definitions than would be convenient with the current form.
53
+
54
+ I'll maintain the current form for backwards compatibility and also because it is convenient for
55
+ simpler aspects.
56
+
57
+ === Version 0.6+
58
+
59
+ I have been thinking about higher-order abstractions that work above the "Pointcut + Advice
60
+ Model" of Aquarium (and AspectJ...) today. I consider the pointcut + advice model to be an
61
+ important, maybe essential, building block of AOP, but if that's all AOP is, then we've probably
62
+ already hit the limit of what we can expect AOP to do. That doesn't seem right to me, but it's
63
+ not at all clear what the higher-order abstractions should be.
46
64
 
47
65
  === Version 1.0.0
48
66
 
data/TODO.rb ADDED
@@ -0,0 +1,26 @@
1
+
2
+ # New DSL ideas.
3
+ Aspect.new do
4
+
5
+ pointcut do
6
+ named_pointcuts :matching => /^STATE/, :within_types_and_descendents => /Foo|Bar/
7
+ or {
8
+ calls_to /index/
9
+ in_types_and_ancestors_of [String, Fixnum]
10
+ } and {
11
+ calls_to /outdex/
12
+ in_types_and_ancestors_of /^Foo/
13
+ }
14
+ end
15
+ within_pointcuts do
16
+ calls_to main
17
+ in_type TopLevel
18
+ end
19
+ but_not_within pointcuts do
20
+ calls_from /callback$/
21
+ in_types_and_descendents_of /Active/
22
+ end
23
+ after do |join_point, object, *args|
24
+ p join_point.inspect
25
+ end
26
+ end
data/UPGRADE CHANGED
@@ -53,21 +53,21 @@ As an alternative, if you just want these methods added selectively in certain t
53
53
  following:
54
54
 
55
55
  <ruby>
56
- require 'aquarium/aspects/dsl/aspect_dsl'
56
+ require 'aquarium/dsl/aspect_dsl'
57
57
 
58
58
  class MyClass # reopen "MyClass"
59
59
  # Add the methods as _class_ methods
60
- include Aquarium::Aspects::DSL::AspectDSL
60
+ include Aquarium::DSL
61
61
  end
62
62
  </ruby>
63
63
 
64
64
  or, use (class|module)_eval:
65
65
  <ruby>
66
- require 'aquarium/aspects/dsl/aspect_dsl'
66
+ require 'aquarium/dsl/aspect_dsl'
67
67
 
68
68
  MyClass.class_eval do
69
69
  # Add the methods as _class_ methods
70
- include Aquarium::Aspects::DSL::AspectDSL
70
+ include Aquarium::DSL
71
71
  end
72
72
  </ruby>
73
73
 
@@ -75,7 +75,7 @@ To add the methods as _instance_ methods on individual objects:
75
75
 
76
76
  <ruby>
77
77
  object = MyClass.new
78
- object.extend(Aquarium::Aspects::DSL::AspectDSL)
78
+ object.extend(Aquarium::DSL)
79
79
  </ruby>
80
80
 
81
81
  See the CHANGES for more details.
@@ -12,7 +12,7 @@ require 'aquarium'
12
12
 
13
13
  module Aquarium
14
14
  class ClassWithStateAndBehavior
15
- include Aquarium::Aspects::DSL::AspectDSL
15
+ include Aquarium::DSL
16
16
  def initialize *args
17
17
  @state = args
18
18
  p "Initializing: #{args.inspect}"
@@ -11,7 +11,7 @@ require 'aquarium'
11
11
 
12
12
  module Aquarium
13
13
  class ClassWithStateAndBehavior
14
- include Aquarium::Aspects::DSL::AspectDSL
14
+ include Aquarium::DSL
15
15
  def initialize *args
16
16
  @state = args
17
17
  end
@@ -33,12 +33,12 @@ Aquarium::PreCond.new.action :a1
33
33
  module Aquarium
34
34
  class PostCond
35
35
  def action *args
36
- p "inside :action"
36
+ args.empty? ? args.dup : args + [:a]
37
37
  end
38
38
 
39
39
  postcondition :calls_to => :action,
40
- :message => "Must pass more than one argument and first argument must be non-empty." do |jp, obj, *args|
41
- args.size > 0 && ! args[0].empty?
40
+ :message => "Must return a copy of the input args with :a appended to it." do |jp, obj, *args|
41
+ jp.context.returned_value.size == args.size + 1 && jp.context.returned_value[-1] == :a
42
42
  end
43
43
  end
44
44
  end
@@ -49,13 +49,8 @@ begin
49
49
  rescue Aquarium::Extras::DesignByContract::ContractError => e
50
50
  p e.inspect
51
51
  end
52
- begin
53
- Aquarium::PostCond.new.action ""
54
- rescue Aquarium::Extras::DesignByContract::ContractError => e
55
- p e.inspect
56
- end
57
52
  p "This call will pass because the postcondition is satisfied:"
58
- Aquarium::PostCond.new.action :a1
53
+ Aquarium::PostCond.new.action :x1, :x2
59
54
 
60
55
  module Aquarium
61
56
  class InvarCond
@@ -37,27 +37,25 @@ end
37
37
  module Aquarium
38
38
  class PostCondExample
39
39
  def action *args
40
- @state = *args
40
+ args.empty? ? args.dup : args + [:a]
41
41
  end
42
- attr_reader :state
43
-
42
+
44
43
  postcondition :calls_to => :action,
45
- :message => "Must pass more than one argument and first argument must be non-empty." do |jp, obj, *args|
46
- args.size > 0 && ! args[0].empty?
44
+ :message => "Must return a copy of the input args with :a appended to it." do |jp, obj, *args|
45
+ jp.context.returned_value.size == args.size + 1 && jp.context.returned_value[-1] == :a
47
46
  end
48
47
  end
49
48
  end
50
49
 
51
50
  describe "An example using a postcondition" do
52
51
  it "should fail at the call exit point if the postcondition is not satisfied." do
53
- lambda { Aquarium::PostCondExample.new.action }.should raise_error(Aquarium::Extras::DesignByContract::ContractError)
54
- lambda { Aquarium::PostCondExample.new.action "" }.should raise_error(Aquarium::Extras::DesignByContract::ContractError)
52
+ lambda { Aquarium::PostCondExample.new.action }.should raise_error(Aquarium::Extras::DesignByContract::ContractError)
55
53
  end
56
54
  end
57
55
 
58
56
  describe "An example using a postcondition" do
59
- it "should not fail at the call entry point if the postcondition is satisfied." do
60
- Aquarium::PostCondExample.new.action :a1
57
+ it "should not fail at the call exit point if the postcondition is satisfied." do
58
+ Aquarium::PostCondExample.new.action :x1, :x2
61
59
  end
62
60
  end
63
61
 
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env ruby
2
+ # Example demonstrating "wrapping" an exception; rescuing an exception and
3
+ # throwing a different one. A common use for this is to map exceptions across
4
+ # "domain" boundaries, e.g., persistence and application logic domains.
5
+ # Note that you must use :around advice, since :after_raising cannot change
6
+ # the control flow.
7
+ # (However, see feature request #19119)
8
+
9
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
10
+ require 'aquarium'
11
+
12
+ module Aquarium
13
+ class Exception1 < Exception; end
14
+ class Exception2 < Exception; end
15
+ class NewException < Exception; end
16
+
17
+ class Raiser
18
+ def raise_exception1
19
+ raise Exception1.new("one")
20
+ end
21
+ def raise_exception2
22
+ raise Exception2.new("two")
23
+ end
24
+ end
25
+ end
26
+
27
+ Aquarium::Aspects::Aspect.new :around,
28
+ :calls_to => /^raise_exception/, :in_type => Aquarium::Raiser do |jp, obj, *args|
29
+ begin
30
+ jp.proceed
31
+ rescue Aquarium::Exception1 => e
32
+ raise Aquarium::NewException.new("Old exception message was \"#{e.message}\"")
33
+ end
34
+ end
35
+
36
+ p "The raised Aquarium::Exception2 raised here won't be intercepted:"
37
+ begin
38
+ Aquarium::Raiser.new.raise_exception2
39
+ rescue Aquarium::Exception2 => e
40
+ p "Rescued exception: #{e.class} with message: #{e}"
41
+ end
42
+
43
+ p "The raised Aquarium::Exception1 raised here will be intercepted and Aquarium::NewException will be raised:"
44
+ begin
45
+ Aquarium::Raiser.new.raise_exception1
46
+ rescue Aquarium::NewException => e
47
+ p "Rescued exception: #{e.class} with message: #{e}"
48
+ end
@@ -0,0 +1,49 @@
1
+ require File.dirname(__FILE__) + '/../spec/aquarium/spec_helper'
2
+ require 'aquarium'
3
+
4
+ # Example demonstrating "wrapping" an exception; rescuing an exception and
5
+ # throwing a different one. A common use for this is to map exceptions across
6
+ # "domain" boundaries, e.g., persistence and application logic domains.
7
+ # Note that you must use :around advice, since :after_raising cannot change
8
+ # the control flow.
9
+ # (However, see feature request #19119)
10
+
11
+ module Aquarium
12
+ class Exception1 < Exception; end
13
+ class Exception2 < Exception; end
14
+ class NewException < Exception; end
15
+
16
+ class Raiser
17
+ def raise_exception1
18
+ raise Exception1.new("one")
19
+ end
20
+ def raise_exception2
21
+ raise Exception2.new("two")
22
+ end
23
+ end
24
+ end
25
+
26
+ describe "Rescuing one exception type and raising a second type" do
27
+ before :all do
28
+ @aspect = Aquarium::Aspects::Aspect.new :around,
29
+ :calls_to => /^raise_exception/, :in_type => Aquarium::Raiser do |jp, obj, *args|
30
+ begin
31
+ jp.proceed
32
+ rescue Aquarium::Exception1 => e
33
+ raise Aquarium::NewException.new("New Exception: old exception message was: #{e.message}")
34
+ end
35
+ end
36
+ @raiser = Aquarium::Raiser.new
37
+ end
38
+
39
+ after :all do
40
+ @aspect.unadvise
41
+ end
42
+
43
+ it "should intercept the specified type of exception" do
44
+ lambda { @raiser.raise_exception1 }.should raise_error(Aquarium::NewException)
45
+ end
46
+ it "should not intercept other types of exceptions" do
47
+ lambda { @raiser.raise_exception2 }.should raise_error(Aquarium::Exception2)
48
+ end
49
+ end
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env ruby
2
+ # Example demonstrating a hack for defining a reusable aspect in a module
3
+ # so that the aspect only gets created when the module is included by another
4
+ # module or class.
5
+ # Hacking like this defies the spirit of Aquarium's goal of being "intuitive",
6
+ # so I created a feature request #19122 to address this problem.
7
+ #
8
+ # WARNING: put the "include ..." statement at the END of the class declaration,
9
+ # as shown below. If you put the include statement at the beginning, as you
10
+ # normally wouuld for including a module, it won't advice any join points,
11
+ # because no methods will have been defined at that point!!
12
+
13
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
14
+ require 'aquarium'
15
+
16
+ module Aquarium
17
+ module Reusables
18
+ module TraceMethods
19
+ def self.append_features mod
20
+ Aquarium::Aspects::Aspect.new :around,
21
+ :type => mod, :methods => :all, :method_options => [:exclude_ancestor_methods] do |jp, object, *args|
22
+ p "Entering: "+jp.target_type.name+"#"+jp.method_name.to_s+": args = "+args.inspect
23
+ jp.proceed
24
+ p "Leaving: "+jp.target_type.name+"#"+jp.method_name.to_s+": args = "+args.inspect
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ class NotTraced1
32
+ def doit; p "NotTraced1#doit"; end
33
+ end
34
+ p "You will be warned that no join points in NotTraced2 were matched."
35
+ p "This happens because the include statement and hence the aspect evaluation happen BEFORE any methods are defined!"
36
+ class NotTraced2
37
+ include Aquarium::Reusables::TraceMethods
38
+ def doit; p "NotTraced2#doit"; end
39
+ end
40
+ class Traced1
41
+ def doit; p "Traced1#doit"; end
42
+ include Aquarium::Reusables::TraceMethods
43
+ end
44
+ class Traced2
45
+ def doit; p "Traced1#doit"; end
46
+ include Aquarium::Reusables::TraceMethods
47
+ end
48
+
49
+ p ""
50
+ p "No method tracing:"
51
+ NotTraced1.new.doit
52
+ NotTraced1.new.doit
53
+ p ""
54
+ p "Method tracing:"
55
+ Traced1.new.doit
56
+ Traced2.new.doit