# AspectR - simple Aspect-Oriented Programming (AOP) in Ruby.
# Version 0.3.3, 2001-01-29. NOTE! API has changed somewhat from 0.2 so beware!
#
# Copyright (c) 2001 Avi Bryant (avi@beta4.com) and
# Robert Feldt (feldt@ce.chalmers.se).
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
module AspectR
class AspectRException < Exception; end
class Aspect
PRE = :PRE
POST = :POST
def initialize(never_wrap = "^$ ")
@never_wrap = /^__|^send$|^id$|^class$|#{never_wrap}/
end
def wrap(target, pre, post, *args)
get_methods(target, args).each do |method_to_wrap|
add_advice(target, PRE, method_to_wrap, pre)
add_advice(target, POST, method_to_wrap, post)
end
end
def unwrap(target, pre, post, *args)
get_methods(target, args).each do |method_to_unwrap|
remove_advice(target, PRE, method_to_unwrap, pre)
remove_advice(target, POST, method_to_unwrap , post)
end
end
# Sticky and faster wrap (can't be unwrapped).
def wrap_with_code(target, preCode, postCode, *args)
prepare(target)
get_methods(target, args).each do |method_to_wrap|
target.__aop_wrap_with_code(method_to_wrap, preCode, postCode)
end
end
def add_advice(target, joinpoint, method, advice)
prepare(target)
if advice
target.__aop_install_dispatcher(method)
target.__aop_add_advice(joinpoint, method, self, advice)
end
end
def remove_advice(target, joinpoint, method, advice)
target.__aop_remove_advice(joinpoint, method, self, advice) if advice
end
@@__aop_dispatch = true
def Aspect.dispatch?
@@__aop_dispatch
end
def disable_advice_dispatching
begin
@@__aop_dispatch = false
yield
ensure
@@__aop_dispatch = true
end
end
def get_methods(target, args)
if args.first.is_a? Regexp
if target.kind_of?(Class)
methods = target.instance_methods(true)
else
methods = target.methods
end
methods = methods.grep(args.first).collect{|e| e.intern}
else
methods = args
end
methods.select {|method| wrappable?(method)}
end
def wrappable?(method)
method.to_s !~ @never_wrap
end
def prepare(target)
unless target.respond_to?("__aop_init")
target.extend AspectSupport
target.__aop_init
end
end
module AspectSupport
def __aop_init
if self.is_a? Class
extend ClassSupport
else
extend InstanceSupport
end
@__aop_advice_methods = {}
end
def __aop_advice_list(joinpoint, method)
method = method.to_s
unless (method_hash = @__aop_advice_methods[joinpoint])
method_hash = @__aop_advice_methods[joinpoint] = {}
end
unless (advice_list = method_hash[method])
advice_list = method_hash[method] = []
end
advice_list
end
def __aop_add_advice(joinpoint, method, aspect, advice)
__aop_advice_list(joinpoint, method) << [aspect, advice]
end
def __aop_remove_advice(joinpoint, method, aspect, advice)
__aop_advice_list(joinpoint, method).delete_if do |asp, adv|
asp == aspect && adv == advice
end
# Reinstall original method if there are no advices left for this meth!
# - except that then we could have problems with singleton instances
# of this class? see InstanceSupport#aop_alias... /AB
end
def __aop_call_advice(joinpoint, method, *args)
__aop_advice_list(joinpoint, method).each do |aspect, advice|
begin
aspect.send(advice, method, *args)
rescue Exception
a = $!
raise AspectRException, "#{a.type} '#{a}' in advice #{advice}"
end
end
end
def __aop_generate_args(method)
arity = __aop_class.instance_method(method).arity
if arity < 0
args = (0...(-1-arity)).to_a.collect{|i| "a#{i}"}.join(",")
args += "," if arity < -1
args + "*args,&block"
elsif arity != 0
((0...arity).to_a.collect{|i| "a#{i}"} + ["&block"]).join(",")
else
"&block" # could be a yield in there...
end
end
def __aop_generate_syntax(method)
args = __aop_generate_args(method)
mangled_method = __aop_mangle(method)
call = "#{mangled_method}(#{args})"
return args, call, mangled_method
end
def __aop_advice_call_syntax(joinpoint, method, args)
"#{__aop_target}.__aop_call_advice(:#{joinpoint}, '#{method}', self, exit_status#{args.length>0 ? ',' + args : ''})"
end
def __aop_install_dispatcher(method)
args, call, mangled_method = __aop_generate_syntax(method)
return if __aop_private_methods.include? mangled_method
new_method = """
def #{method}(#{args})
return (#{call}) unless Aspect.dispatch?
begin
exit_status = nil
#{__aop_advice_call_syntax(PRE, method, args)}
exit_status = []
return (exit_status.push(#{call}).last)
rescue Exception
exit_status = true
raise
ensure
#{__aop_advice_call_syntax(POST, method, args)}
end
end
"""
__aop_alias(mangled_method, method)
__aop_eval(new_method)
end
def __aop_wrap_with_code(method, preCode, postCode)
args, call, mangled_method = __aop_generate_syntax(method)
return if __aop_private_methods.include? mangled_method
comma = args != "" ? ", " : ""
preCode.gsub!('INSERT_ARGS', comma + args)
postCode.gsub!('INSERT_ARGS', comma + args)
new_method = """
def #{method}(#{args})
#{preCode}
begin
#{call}
ensure
#{postCode}
end
end
"""
__aop_alias(mangled_method, method)
__aop_eval(new_method)
end
module ClassSupport
def __aop_target
"self.class"
end
def __aop_class
self
end
def __aop_mangle(method)
"__aop__#{self.id}_#{method.id}"
end
def __aop_alias(new, old, private = true)
alias_method new, old
private new if private
end
def __aop_private_methods
private_instance_methods
end
def __aop_eval(text)
begin
class_eval text
rescue Exception
puts "class_eval '#{text}'"
end
end
end
module InstanceSupport
def __aop_target
"self"
end
def __aop_class
self.class
end
def __aop_mangle(method)
"__aop__singleton_#{method}"
end
def __aop_alias(new, old, private = true)
# Install in class since otherwise the non-dispatcher version of the class version of the method
# gets locked away, and so if we wrap a singleton before wrapping its class,
# later wrapping the class has no effect on that singleton /AB
# of course, this depends on exactly what behavior we want for inheritance... Decide for future release...
unless self.class.respond_to?("__aop_init")
self.class.extend AspectSupport
self.class.__aop_init
end
self.class.__aop_install_dispatcher(old)
eval "class << self; alias_method '#{new}', '#{old}'; end;"
eval "class << self; private '#{new}'; end" if private
end
def __aop_private_methods
private_methods
end
def __aop_eval(text)
instance_eval text
end
end
end
end
# NOTE! Somewhat experimental so API will likely change on this method!
def wrap_classes(aspect, pre, post, classes, *methods)
classes = all_classes(classes) if classes.kind_of?(Regexp)
classes.each {|klass| aspect.wrap(klass, pre, post, *methods)}
end
module_function :wrap_classes
# TODO: Speed this up by recursing from Object.constants instead of sifting
# through all object in the ObjectSpace (might be slow if many objects).
# Is there a still faster/better way?
# NOTE! Somewhat experimental so API will likely change on this method!
def all_classes(regexp = /^.+$/)
classes = []
ObjectSpace.each_object(Class) do |c|
classes.push c if c.inspect =~ regexp
end
classes
end
end
syntax highlighted by Code2HTML, v. 0.9.1