aspectr 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,309 @@
1
+ # AspectR - simple Aspect-Oriented Programming (AOP) in Ruby.
2
+ # Version 0.3.6, 2006-03-11. NOTE! API has changed somewhat from 0.2 so beware!
3
+ # Last modification: Benjamin Alterauge Email: benjamin_alterauge@web.de
4
+ #
5
+ # Copyright (c) 2001 Avi Bryant (avi@beta4.com) and
6
+ # Robert Feldt (feldt@ce.chalmers.se).
7
+ #
8
+ # This library is free software; you can redistribute it and/or
9
+ # modify it under the terms of the GNU Library General Public
10
+ # License as published by the Free Software Foundation; either
11
+ # version 2 of the License, or (at your option) any later version.
12
+ #
13
+ # This library is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
+ # Library General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU Library General Public
19
+ # License along with this library; if not, write to the Free Software
20
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
+ #
22
+ module AspectR
23
+ class AspectRException < Exception; end
24
+
25
+ class Aspect
26
+ PRE = :PRE
27
+ POST = :POST
28
+
29
+ def initialize(never_wrap = "^$ ")
30
+ @never_wrap = /^__|^send$|^object_id$|^class$|#{never_wrap}/
31
+ end
32
+
33
+ def wrap(target, pre, post, *args)
34
+ get_methods(target, args).each do |method_to_wrap|
35
+ add_advice(target, PRE, method_to_wrap, pre)
36
+ add_advice(target, POST, method_to_wrap, post)
37
+ end
38
+ end
39
+
40
+ def unwrap(target, pre, post, *args)
41
+ get_methods(target, args).each do |method_to_unwrap|
42
+ remove_advice(target, PRE, method_to_unwrap, pre)
43
+ remove_advice(target, POST, method_to_unwrap , post)
44
+ end
45
+ end
46
+
47
+ # Sticky and faster wrap (can't be unwrapped).
48
+ def wrap_with_code(target, preCode, postCode, *args)
49
+ prepare(target)
50
+ get_methods(target, args).each do |method_to_wrap|
51
+ target.__aop_wrap_with_code(method_to_wrap, preCode, postCode)
52
+ end
53
+ end
54
+
55
+ def add_advice(target, joinpoint, method, advice)
56
+ prepare(target)
57
+ if advice
58
+ target.__aop_install_dispatcher(method)
59
+ target.__aop_add_advice(joinpoint, method, self, advice)
60
+ end
61
+ end
62
+
63
+ def remove_advice(target, joinpoint, method, advice)
64
+ target.__aop_remove_advice(joinpoint, method, self, advice) if advice
65
+ end
66
+
67
+ @@__aop_dispatch = true
68
+
69
+ def Aspect.dispatch?
70
+ @@__aop_dispatch
71
+ end
72
+
73
+ def disable_advice_dispatching
74
+ begin
75
+ @@__aop_dispatch = false
76
+ yield
77
+ ensure
78
+ @@__aop_dispatch = true
79
+ end
80
+ end
81
+
82
+ def get_methods(target, args)
83
+ if args.first.is_a? Regexp
84
+ if target.kind_of?(Class)
85
+ methods = target.instance_methods(true)
86
+ else
87
+ methods = target.methods
88
+ end
89
+ methods = methods.grep(args.first).collect{|e| e.intern}
90
+ else
91
+ methods = args
92
+ end
93
+ methods.select {|method| wrappable?(method)}
94
+ end
95
+
96
+ def wrappable?(method)
97
+ method.to_s !~ @never_wrap
98
+ end
99
+
100
+ def prepare(target)
101
+ unless target.respond_to?("__aop_init")
102
+ target.extend AspectSupport
103
+ target.__aop_init
104
+ end
105
+ end
106
+
107
+ module AspectSupport
108
+
109
+ def __aop_init
110
+ if self.is_a? Class
111
+ extend ClassSupport
112
+ else
113
+ extend InstanceSupport
114
+ end
115
+ @__aop_advice_methods = {}
116
+ end
117
+
118
+ def __aop_advice_list(joinpoint, method)
119
+ method = method.to_s
120
+ unless (method_hash = @__aop_advice_methods[joinpoint])
121
+ method_hash = @__aop_advice_methods[joinpoint] = {}
122
+ end
123
+ unless (advice_list = method_hash[method])
124
+ advice_list = method_hash[method] = []
125
+ end
126
+ advice_list
127
+ end
128
+
129
+ def __aop_add_advice(joinpoint, method, aspect, advice)
130
+ __aop_advice_list(joinpoint, method) << [aspect, advice]
131
+ end
132
+
133
+ def __aop_remove_advice(joinpoint, method, aspect, advice)
134
+ __aop_advice_list(joinpoint, method).delete_if do |asp, adv|
135
+ asp == aspect && adv == advice
136
+ end
137
+ # Reinstall original method if there are no advices left for this meth!
138
+ # - except that then we could have problems with singleton instances
139
+ # of this class? see InstanceSupport#aop_alias... /AB
140
+ end
141
+
142
+ def __aop_call_advice(joinpoint, method, *args)
143
+ __aop_advice_list(joinpoint, method).each do |aspect, advice|
144
+ begin
145
+ aspect.send(advice, method, *args)
146
+ rescue Exception
147
+ a = $!
148
+ raise AspectRException, "#{a.class} '#{a}' in advice #{advice}"
149
+ end
150
+ end
151
+ end
152
+
153
+ def __aop_generate_args(method)
154
+ arity = __aop_class.instance_method(method).arity
155
+ if arity < 0
156
+ args = (0...(-1-arity)).to_a.collect{|i| "a#{i}"}.join(",")
157
+ args += "," if arity < -1
158
+ args + "*args,&block"
159
+ elsif arity != 0
160
+ ((0...arity).to_a.collect{|i| "a#{i}"} + ["&block"]).join(",")
161
+ else
162
+ "&block" # could be a yield in there...
163
+ end
164
+ end
165
+
166
+ def __aop_generate_syntax(method)
167
+ args = __aop_generate_args(method)
168
+ mangled_method = __aop_mangle(method)
169
+ call = "send(\"#{mangled_method}\".to_sym, #{args})"
170
+ return args, call, mangled_method
171
+ end
172
+
173
+ def __aop_advice_call_syntax(joinpoint, method, args)
174
+ "#{__aop_target}.__aop_call_advice(:#{joinpoint}, '#{method}', self, exit_status#{args.length>0 ? ',' + args : ''})"
175
+ end
176
+
177
+ def __aop_install_dispatcher(method)
178
+ args, call, mangled_method = __aop_generate_syntax(method)
179
+ return if __aop_private_methods.include? mangled_method
180
+ new_method = """
181
+ def #{method}(#{args})
182
+ return (#{call}) unless Aspect.dispatch?
183
+ begin
184
+ exit_status = nil
185
+ #{__aop_advice_call_syntax(PRE, method, args)}
186
+ exit_status = #{call}
187
+ return exit_status
188
+ rescue Exception
189
+ exit_status = true
190
+ raise
191
+ ensure
192
+ #{__aop_advice_call_syntax(POST, method, args)}
193
+ end
194
+ end
195
+ """
196
+ __aop_alias(mangled_method, method)
197
+ __aop_eval(new_method)
198
+ end
199
+
200
+ def __aop_wrap_with_code(method, preCode, postCode)
201
+ args, call, mangled_method = __aop_generate_syntax(method)
202
+ return if __aop_private_methods.include? mangled_method
203
+ comma = args != "" ? ", " : ""
204
+ preCode.gsub!('INSERT_ARGS', comma + args)
205
+ postCode.gsub!('INSERT_ARGS', comma + args)
206
+ new_method = """
207
+ def #{method}(#{args})
208
+ #{preCode}
209
+ begin
210
+ #{call}
211
+ ensure
212
+ #{postCode}
213
+ end
214
+ end
215
+ """
216
+ __aop_alias(mangled_method, method)
217
+ __aop_eval(new_method)
218
+ end
219
+
220
+ module ClassSupport
221
+ def __aop_target
222
+ "self.class"
223
+ end
224
+
225
+ def __aop_class
226
+ self
227
+ end
228
+
229
+ def __aop_mangle(method)
230
+ "__aop__#{self.object_id}_#{method.object_id}"
231
+ end
232
+
233
+ def __aop_alias(new, old, private = true)
234
+ alias_method new, old
235
+ private new if private
236
+ end
237
+
238
+ def __aop_private_methods
239
+ private_instance_methods
240
+ end
241
+
242
+ def __aop_eval(text)
243
+ begin
244
+ class_eval text
245
+ rescue Exception
246
+ puts "class_eval '#{text}'"
247
+ end
248
+ end
249
+ end
250
+
251
+ module InstanceSupport
252
+ def __aop_target
253
+ "self"
254
+ end
255
+
256
+ def __aop_class
257
+ self.class
258
+ end
259
+
260
+ def __aop_mangle(method)
261
+ "__aop__singleton_#{method}"
262
+ end
263
+
264
+ def __aop_alias(new, old, private = true)
265
+ # Install in class since otherwise the non-dispatcher version of the class version of the method
266
+ # gets locked away, and so if we wrap a singleton before wrapping its class,
267
+ # later wrapping the class has no effect on that singleton /AB
268
+ # of course, this depends on exactly what behavior we want for inheritance... Decide for future release...
269
+ unless self.class.respond_to?("__aop_init")
270
+ self.class.extend AspectSupport
271
+ self.class.__aop_init
272
+ end
273
+ self.class.__aop_install_dispatcher(old)
274
+ eval "class << self; alias_method '#{new}', '#{old}'; end;"
275
+ eval "class << self; private '#{new}'; end" if private
276
+ end
277
+
278
+ def __aop_private_methods
279
+ private_methods
280
+ end
281
+
282
+ def __aop_eval(text)
283
+ instance_eval text
284
+ end
285
+ end
286
+ end
287
+ end
288
+
289
+ # NOTE! Somewhat experimental so API will likely change on this method!
290
+ def wrap_classes(aspect, pre, post, classes, *methods)
291
+ classes = all_classes(classes) if classes.kind_of?(Regexp)
292
+ classes.each {|klass| aspect.wrap(klass, pre, post, *methods)}
293
+ end
294
+ module_function :wrap_classes
295
+
296
+ # TODO: Speed this up by recursing from Object.constants instead of sifting
297
+ # through all object in the ObjectSpace (might be slow if many objects).
298
+ # Is there a still faster/better way?
299
+ # NOTE! Somewhat experimental so API will likely change on this method!
300
+ def all_classes(regexp = /^.+$/)
301
+ classes = []
302
+ ObjectSpace.each_object(Class) do |c|
303
+ classes.push c if c.inspect =~ regexp
304
+ end
305
+ classes
306
+ end
307
+ end
308
+
309
+
@@ -0,0 +1,249 @@
1
+ class Logger < Aspect
2
+ def initialize(io = STDOUT)
3
+ @io = io
4
+ end
5
+
6
+ def _tick; "#{Time.now.strftime('%Y-%m-%d %X')}"; end
7
+
8
+ def enter(method, object, exitstatus, *args)
9
+ @io.puts "enter: #{_tick} #{object.class}##{method}: called with #{args.inspect}"
10
+ end
11
+
12
+ def exit(method, object, exitstatus, *args)
13
+ @io.print "exit: #{_tick} #{object.class}##{method}: exited "
14
+ if exitstatus.kind_of?(Array)
15
+ @io.puts "normally returning #{exitstatus[0].inspect}"
16
+ elsif exitstatus == true
17
+ @io.puts "with exception '#{$!}'"
18
+ else
19
+ @io.puts 'normally'
20
+ end
21
+ end
22
+
23
+ def senter(method, object, exitstatus, *args)
24
+ @io.puts "senter: #{_tick} Entering #{object.object_id}##{method}"
25
+ end
26
+
27
+ def sexit(method, object, exitstatus, *args)
28
+ @io.puts "sexit: #{_tick} Exiting #{object.object_id}##{method}"
29
+ end
30
+ end
31
+
32
+ class Counter < Aspect
33
+ attr_reader :incrementer_cnt
34
+
35
+ def initialize
36
+ @counters = {}
37
+ end
38
+
39
+ def wrap(target, *methods)
40
+ super(target, :inc, nil, *methods)
41
+ end
42
+
43
+ def unwrap(target, *methods)
44
+ super(target, :inc, nil, *methods)
45
+ end
46
+
47
+ def [](object, method)
48
+ method = method.id2name if method.kind_of?(Symbol)
49
+ @counters[object][method]
50
+ end
51
+
52
+ private
53
+ def inc(method, object, *args)
54
+ begin
55
+ @counters[object][method] += 1
56
+ rescue NameError
57
+ @counters[object] = {} unless @counters[object]
58
+ @counters[object][method] = 1
59
+ end
60
+ end
61
+ end
62
+
63
+
64
+
65
+
66
+ class TestHello
67
+ attr_reader :tt
68
+
69
+ def say(hello)
70
+ return hello + " world"
71
+ end
72
+
73
+ def inspect; object_id.inspect; end
74
+
75
+ def sayz(*args)
76
+ raise NotImplementedError, 'NYI!'
77
+ end
78
+ end
79
+
80
+ class HistoryStdOut
81
+ attr_reader :history
82
+ def initialize
83
+ @history = Array.new
84
+ end
85
+ def puts(str)
86
+ if @last_was_print
87
+ @history[-1] += str
88
+ else
89
+ @history.push str
90
+ end
91
+ @last_was_print = false
92
+ end
93
+ def print(str)
94
+ @history.push str
95
+ @last_was_print = true
96
+ end
97
+ def last; @history.last; end
98
+ end
99
+
100
+ class CodeWrapper < Aspect
101
+ alias old_wrap wrap
102
+ def wrap(target, *methods)
103
+ wrap_with_code(target, "p 'pre'", "p 'post'", *methods)
104
+ end
105
+ def code_wrap(target, *methods)
106
+ wrap_with_code(target,"CodeWrapper.enter","CodeWrapper.leave", *methods)
107
+ end
108
+ @@counter = 0
109
+ def CodeWrapper.enter; @@counter += 1; end
110
+ def CodeWrapper.leave; @@counter += 1; end
111
+ def CodeWrapper.counter; @@counter; end
112
+ end
113
+
114
+ class T2; def m; end; end
115
+
116
+ class TestAspectR < Test::Unit::TestCase
117
+ # Just so that we can use previously written tests. Clean up when there is
118
+ # time!
119
+ @@logger = Logger.new(@@hso = HistoryStdOut.new)
120
+ @@counter = Counter.new
121
+ @@t, @@u = TestHello.new, TestHello.new
122
+
123
+ def setup
124
+ end
125
+
126
+ def test_01_wrap
127
+ @@counter.wrap(TestHello, :say) # Wrap counter on TestHello#say
128
+ assert_equal('hello world', @@t.say('hello'))
129
+ assert_equal('hello world', @@u.say('hello'))
130
+ assert_equal(1, @@counter[@@t, :say])
131
+ assert_equal(1, @@counter[@@u, :say])
132
+ @@t.say("my")
133
+ assert_equal(2, @@counter[@@t, :say])
134
+ assert_equal(1, @@counter[@@u, :say])
135
+ end
136
+
137
+ def test_02_class_wrapping
138
+ @@logger.wrap(TestHello, :enter, :exit, /sa/)
139
+ @@t.say('t: hello')
140
+ assert_match(/enter.*TestHello#say: called with \[.*\]/, @@hso.history[-2] )
141
+ assert_match(/exit.*TestHello#say: exited/, @@hso.history[-1] )
142
+ @@u.say('u: hello')
143
+ assert_match(/enter.*TestHello#say: called with \[.*\]/,@@hso.history[-2])
144
+ assert_match(/exit.*TestHello#say: exited/,@@hso.history[-1])
145
+ end
146
+
147
+ def test_03_singleton_wrapping
148
+ @@logger.wrap(@@t, :senter, :sexit, :say)
149
+ @@t.say('t: hello')
150
+ assert_match( /senter/, @@hso.history[-4])
151
+ assert_match( /enter/, @@hso.history[-3])
152
+ assert_match( /exit/, @@hso.history[-2])
153
+ assert_match(/sexit/, @@hso.history[-1])
154
+ @@u.say('u: hello')
155
+ assert_match( /enter/, @@hso.history[-2])
156
+ assert_match(/exit/, @@hso.history[-1])
157
+ end
158
+
159
+ def test_04_unwrapping
160
+ @@logger.unwrap(TestHello, :enter, :exit, /sa/)
161
+ @@t.say('t: hello') # senter and sexit should still be there...
162
+ assert_match(/senter/, @@hso.history[-2])
163
+ assert_match(/sexit/, @@hso.history[-1])
164
+ l = @@hso.history.length
165
+ @@u.say('u: hello')
166
+ assert_equal(l, @@hso.history.length) # Nothing printed!
167
+ end
168
+
169
+ def test_05_dynamically_changing_advice
170
+ Logger.class_eval <<-'EOC'
171
+ def senter(*args)
172
+ @io.puts "senter version 2.0"
173
+ end
174
+ EOC
175
+ @@t.say('t: hello')
176
+ assert_match(/senter version 2\.0/, @@hso.history[-2])
177
+ end
178
+
179
+ def test_06_partial_unwrap
180
+ @@logger.unwrap(@@t, :senter, nil, :say)
181
+ l = @@hso.history.length
182
+ @@t.say('t: hello') # Should use sexit but not senter
183
+ assert_equal(l+1, @@hso.history.length)
184
+ assert_match(/sexit/, @@hso.history[-1])
185
+ end
186
+
187
+ def test_07_exception_in_method
188
+ @@logger.wrap(@@t, nil, :exit, :sayz)
189
+ l = @@hso.history.length
190
+ begin
191
+ @@t.sayz 1
192
+ rescue Exception; end
193
+ assert_equal(l+1, @@hso.history.length)
194
+ assert_match(/exit/, @@hso.history[-1])
195
+ end
196
+
197
+ def test_08_counter
198
+ assert_equal(7, @@counter[@@t, :say])
199
+ @@counter.unwrap(TestHello, :say)
200
+ @@t.say('t: hello')
201
+ assert_equal(7, @@counter[@@t, :say])
202
+ end
203
+
204
+ def test_09_transparency
205
+ assert_equal(1, TestHello.instance_method(:say).arity)
206
+ assert_equal(-1, TestHello.instance_method(:sayz).arity)
207
+
208
+ @@counter.wrap(TestHello, :tt)
209
+ assert_equal(0, TestHello.instance_method(:tt).arity)
210
+ end
211
+
212
+ def test_10_special_methods
213
+ @@logger.wrap(Array, :enter, nil, :[], :[]=, :initialize)
214
+ a = Array.new(2)
215
+ assert_match(/enter/, @@hso.history.last); l = @@hso.history.length
216
+ a[1]
217
+ assert_equal(l+1, @@hso.history.length)
218
+ assert_match(/enter/, @@hso.history.last); l = @@hso.history.length
219
+ a[1] = 10
220
+ assert_equal(l+1, @@hso.history.length)
221
+ assert_match(/enter/, @@hso.history.last); l = @@hso.history.length
222
+ @@logger.unwrap(Array, :[], :[]=, :initialize)
223
+ end
224
+
225
+ def test_11_code_wrapping
226
+ assert_equal(0, CodeWrapper.counter)
227
+ CodeWrapper.new.code_wrap(T2, :m)
228
+ T2.new.m
229
+ assert_equal(2, CodeWrapper.counter)
230
+ end
231
+ end
232
+
233
+
234
+ class T3; def m; end; def m2; end; end
235
+
236
+ def time(n = 100_000, &block)
237
+ start = Process.times.utime
238
+ n.times{block.call}
239
+ Process.times.utime - start
240
+ end
241
+
242
+ def test_overhead
243
+ puts "\nOverhead"
244
+ t = T3.new
245
+ puts "Time for unwrapped: #{tuw = time(50_000) {t.m}}"
246
+ CodeWrapper.new.code_wrap(T3, :m)
247
+ puts "Time for code-wrapped: #{tw = time(50_000) {t.m}}"
248
+ puts "A slowdown of #{tw/tuw}"
249
+ end
@@ -0,0 +1,6 @@
1
+ require 'test/unit'
2
+
3
+ require "#{__FILE__[/^tests/]?'.' : '..'}/lib/aspectr.rb"
4
+ include AspectR
5
+
6
+ Dir["#{__FILE__[/^tests/]?'./tests' : '.'}/atest*_*.rb"].each { |f| require f }
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.2
3
+ specification_version: 1
4
+ name: aspectr
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.3.7
7
+ date: 2007-03-19 00:00:00 +01:00
8
+ summary: Aspect-oriented programming concepts to Ruby.
9
+ require_paths:
10
+ - lib
11
+ email: bunny@kultkiste.de
12
+ homepage: http://aspectr.rubyforge.org/
13
+ rubyforge_project: aspectr
14
+ description: Essentially it allows you to wrap code around existing methods in your classes.
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors: []
30
+
31
+ files:
32
+ - lib/aspectr.rb
33
+ test_files:
34
+ - tests/atest01_aspectr.rb
35
+ - tests/runtests.rb
36
+ rdoc_options: []
37
+
38
+ extra_rdoc_files: []
39
+
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ requirements: []
45
+
46
+ dependencies: []
47
+