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 # # 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.