class Interceptor
include TeamUtil
include Logging::NamedLog
# The prefix all intercepted methods get.
PREFIX="_intercepted_"
# Not allowed to rename Operators.
OPERATOR = ["&", "+", "-", "*", "|", "-", "<<", "<=>", "==", "===", "[]", "[]="]
#list of intercepted classes
attr :intercepted
attr :ctors
# ctor of the Interceptor.
def initialize
@intercepted = Hash.new
@ctors = Hash.new
init_log("Interceptor")
end
# Intercept all methods of class classhandle, that match the given methoddefinition.
# [classhandle] the class (or superclass) to intercept.
# [methoddefinition] String, Array, Symbol or Regexp that match a set of methods.
def intercept(classhandle, methoddefinition)
get_all_method_names(classhandle, methoddefinition).each { |methodname|
intercept_method(classhandle, methodname)
}
end
# Intercept a given method on a given class.
# The method is intercepted in the _defining_ class, which must not
# be the given class. The method is aliased to #{PREFIX}#{methodname}.
# A new method is implemented instead of the old one, which will lookup
# for a specific context. If no context is found, the old method gets
# called.
# [classhandle] the class which implements a given method inside the class hierarchie
# [methodname] the method to intercept.
def intercept_method(classhandle, methodname)
if (!is_operator?(methodname))
#walk until implementing klass
while(!classhandle.method_defined?(methodname) and classhandle!=Object)
classhandle = classhandle.superclass
end
#get all methods already intercepted
imethods = intercepted_methods_of(classhandle)
#is this methodname intercepted?
if (imethods[methodname].nil?)
info("intercept #{classhandle.name}.#{methodname}")
#intercept the methodname
classhandle.module_eval(generate_new_method(classhandle, methodname))
#remember interception
imethods[methodname] = true
end
else
warn("can't intercept: #{classhandle.name}.#{methodname}")
end
end
# Rename ctor of given class to #{PREFIX}_initialize.
# The old ctor is replaced by an empty ctor.
# A call to classhandle.new will not call the old ctor! - this has to be done manually.
# [classhandle] the classhandle to generate a default empty ctor.
# raise exception, if the implemented ctor is not a default ctor.
def generate_ctor(classhandle)
if (@ctors[classhandle].nil?)
if (classhandle.private_instance_methods(false).include?("initialize"))
info("rename ctor of #{classhandle.name}")
if (classhandle.instance_method("initialize").arity != 0)
raise ObjectTeam::TeamException, "#{classhandle.name} must implement default Constructor (arity = 0)", caller
end
classhandle.module_eval("alias_method(:#{PREFIX}_initialize, :initialize)\ndef initialize()\nend")
end
@ctors[classhandle] = true
end
end
# Transparently access a hash of all intercepted methods of a given classhandle
# [classhandle] the class to lookup
# [return] an hash: {methodname -> bool}
def intercepted_methods_of(classhandle)
result = @intercepted[classhandle]
if (result.nil?)
result = Hash.new
@intercepted[classhandle] = result
end
return result
end
# Create a string, which represents the new code of the method
# [classhandle] the class this method shall be introduced
# [methodname] the name of the method to implement
# [return] the new method as string
def generate_new_method(classhandle, methodname)
setter_re = /=$/
nmethod = <<-EOM
alias_method(:#{PREFIX}#{classhandle.name}_#{methodname}, :#{methodname})
def #{methodname}(*params, &block)
retval = nil
begin
#do we have a callin registered?
acallin = self.class.get_callin("#{methodname}")
if (!acallin.nil?)
JoinPoint.instance.source = "#{methodname}"
JoinPoint.instance.call = acallin
JoinPoint.instance.valid = true
retval = acallin.call(self, *params, &block)
JoinPoint.instance.valid = false
else
EOM
#test if the given methodname is a setter - in this case we must use send.
if (setter_re.match(methodname))
nmethod += " retval = self.send(\"#{PREFIX}#{classhandle.name}_#{methodname}\",*params, &block)\n"
else
nmethod += " retval = #{PREFIX}#{classhandle.name}_#{methodname}(*params, &block)\n"
end
nmethod += <<-EOM
end
rescue Exception => ex
if ($DEBUG)
#throw original backtrace
raise
else
#set new backtrace, without annoying weaved trace
newtrace = Array.new
ex.backtrace.each { |trace|
if (!ObjectTeam::Caller_RE.match(trace))
newtrace.push(trace)
end
}
ex.set_backtrace(newtrace)
raise ex.class, ex.message, newtrace
end
end
return retval
end
EOM
return nmethod
end
# Intercept all public methods of the given class
# The methodnames will be renamed (aliased) to {PREFIX}methodname.
# [classhandle] the reference to a class.
# [return] void
def intercept_all_methods(classhandle)
#climb until object
while(classhandle!=Object)
#instmethods = (classhandle.private_instance_methods(false).include?("initialize") ? classhandle.instance_methods.push("initialize") : classhandle.instance_methods)
classhandle.public_instance_methods(false).each { |method|
intercept_method(classhandle, method)
}
#climb up
classhandle = classhandle.superclass
end
end
# Indicates, if the given is an operator
# [return] true, if operator otherwise false
def is_operator?(method)
return OPERATOR.include?(method)
end
# Declare Interceptor as singleton. Prohibit further instantiation of Interceptor.
@@singleton = Interceptor.new
def Interceptor.new
if (@@singleton.nil?)
super
else
raise ObjectTeam::TeamException, "Interceptor is a singleton class, and can not be instantiated.\n_use Interceptor.intercept(aclass)!", caller
end
end
# -> intercept
def Interceptor.intercept(classhandle, method_definition)
@@singleton.intercept(classhandle, method_definition)
end
# -> generate_ctor
def Interceptor.generate_ctor(classhandle)
@@singleton.generate_ctor(classhandle)
end
# -> intercept_all_methods
def Interceptor.intercept_all_methods(classhandle)
@@singleton.intercept_all_methods(classhandle)
end
end
# Interceptor.rb April 2002
#
# Copyright (c) 2002 by Matthias Veit <matthias_veit@yahoo.de>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
# 02111-1307, USA.
syntax highlighted by Code2HTML, v. 0.9.1