
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.
