
# This class represent a kind of method. Registered under a certain
# name, it is possible to call Call#call with a receiving object
# optional parameter(list) and a optional block.
# An instance of Call (a call-object) can be registered by an Intercepted
# class (see: Interceptor).
class Call
  # we will log our activity
  include Logging::NamedLog

  # The methodname, this call object sends to the receiver-object.
  attr :methodname

  # ctor. Construct a simple call object.
  # [methodname] means the method to call, not the methodname this 
  #              call object is registered to.
  def initialize(methodname)
    @methodname = methodname
    init_log("Call:#{self.class.name}")
  end

  # This is the abstract class, the dispatcher of an intercepted class
  # will call. Inherit and override this method.
  # [object] the receiver of this call
  # [parameter] the parameter to be passed to this call (may be empty)
  # [block] a optional block passed to the method
  # [return] the returnvalue of the called method
  def call(object, *parameter, &block)
    #abstract
    raise ObjectTeam::AbstractMethodCall, "abstract method called!", caller
  end

  # This method is implemented for convinience and usable in a 
  # overriden call-method.
  # see call()
  def call_method(object, *parameter, &block)
    #debug("#{object.class.name}.#{@methodname}")
    return object.send(@methodname, *parameter, &block)
  end

  # Since call-objects can be nested, this method returns the base of this
  # call-object. Implement this method in overriden classes.
  # Since this is an atomos call-object we return self.
  # [connector] the connector of the current call-object.
  # [return] the base call object or nil.
  def get_base_of(connector)
    return self
  end
  
  # String representation for a call.
  def to_s
    return " [(normal) #{@methodname}]"
  end

  def to_string(level=1)
    return ("    "*level)+"->#{methodname} [NormalCall]\n"
  end
    
end

# The simplest usable Call-object are Method wrappers.
# They only wrap a method and invoke call_method directly.
class MethodCall < Call
  # see Call::call
  def call(object, *parameter, &block)
    call_method(object, *parameter, &block)
  end
  
  # String representation for a call.
  def to_s
    return " [(method) #{@methodname}]"
  end

  def to_string(level=1)
    return ("    "*level)+"->#{methodname} [MethodCall]\n"
  end
end

# This Call-object simply does nothing.
# This could be important, if unordered deactivation is done.
# Example:
#   conn1 = Connector.new
#   conn2 = Connector.new
#   conn1.activate
#   conn2.activate
#   conn1.deactivate
#   conn2.deactivate
# Deactivation should be in reverse order than activation - this
# is not the case. If connector 2 replaces a callin from connector 1
# the replacement is not valid any longer, if conn1 is deactivated
# before conn2. In this case conn2 gets an instance of NoCall as
# replacement.
class NoCall < Call

  #no method to call  
  def initialize
    super(nil)
  end
  
  # see Call::call
  def call(object, *parameter, &block)
    #do nothing
  end

  # String representation for a call.
  def to_s
    return " [(null) #{@methodname}]"
  end

  def to_string(level=1)
    return ("    "*level)+"->#{methodname} [NullOp]\n"
  end

  def ==(call)
    @methodname==call.methodname
  end
end

# A Role call is more sophisticated.
# It calls not the object passed by call_role - it found a related 
# role object via the connector. Does lifting/lowering.
class RoleCall < Call
  
  #responsible connector
  attr :connector

  #pattern of base methods matched by this Call-Object
  #(Just for debugging purposes)
  attr :source_pattern
  

  # Intialize this role call.
  # [methodname] the name of the method in the role object.
  # [connector] the connector, that establishes the call
  def initialize(connector, methodname, source_pattern)
    super(methodname)
    @connector = connector
    @source_pattern = source_pattern
  end

  # Convinience method for overriding classes.
  # The role object of the passed object is fetched, all parameters are lifted
  # and the result is lowered.
  # paramters and return value are the same as Call::call.
  def call_role(object, *parameter, &block)
    #return value
    retval = nil
    #ensure there is a role to call
    role = @connector[object]
    if (role)
      #debug("#{role.class.name}.#{@methodname}")
      #get method to call
      method = role.method(@methodname)
      #arity of method
      nparms = nil
      if (method.arity==0)
        nparms = Array.new
      else
        #if arity<0:  arity = (arity.abs-1) .. (arity.abs+1)
        #The arity in this range is valid!
        #
        #example: -3 => (2,3,4)  parameter.length==[2|3|4] => valid!!
        varargs = (method.arity<0) ? 1 : 0
        arity = method.arity.abs
        if (parameter.length<(arity-varargs) or parameter.length>(arity+varargs))
          raise(ObjectTeam::TeamException, "method arity does not match: #{@methodname} wants #{method.arity} got #{parameter.length}", caller)
        else
          #lift parameter
          nparms=@connector.lift(parameter)
        end
      end
      #call role object
      retval = method.call(*nparms, &block)
      #lower return value
      retval = @connector.lower(retval)
    else
      #warn("No role for #{object.class.name}")
    end
    return retval
  end

  # Overriden call method: replace method to base, use method to role object.
  def call(object, *parameter, &block)
    return call_role(object, *parameter, &block)
  end

  # The base-call of this rolecall is nil for the related (creating)
  # connector (a RoleCall can't have a base). For all other connectors
  # the base of this Call-Object is this call object.
  def get_base_of(connector)
    if (connector==@connector)
      return nil
    else
      return self
    end
  end

  # String representation for a call.
  def to_s
    return " [(rolecall) #{@methodname}]"
  end

  def to_string(level=1)
    return ("    "*level)+"->#{methodname} [RoleCall from #{connector.class.name}]\n"
  end
end

# A weaved Call is more than a simple call: two simple calls.
# The weaved call-object take's a further call-object.
# Overriding classes just have to decide, when to call the other one.
# There are (at least) 3 possibilities:
#  * CallBefore: 
#        - first call the role
#        - than call the aggregated call-object (referred as basecall-object)
#  * CallAfter:
#        - first call the aggregated call-object (referred as basecall-object)
#        - then call the role
#  * CallAround:
#        - only call the role
#        - let the role implementation decide, if and when to call the
#          replaced version
class CallWeaved < RoleCall
  attr_accessor :basecall
  attr :parametermapping

  def initialize(connector, methodname, source_pattern, parametermapping, basecall)
    super(connector, methodname, source_pattern)
    @parametermapping=parametermapping
    @basecall = basecall
  end

  def match_parameter(parameter)
    result = nil
    if (!parametermapping.nil?)
      result = parametermapping.call(*parameter)
    else
      result = parameter
    end
    return result
  end

  def get_base_of(connector)
    if (connector==@connector)
      return @basecall.get_base_of(connector)
    else
      return self
    end
  end
end

# 1.: Call the role
# 2.: Call the base-call-object
class CallBefore < CallWeaved
  def call(object, *parameter, &block)
    #debug(self)
    #call weaved 
    if (not @connector.suspended?)
      nparams = match_parameter(parameter)
      call_role(object, *nparams, &block)
    end
    #call base
    return @basecall.call(object, *parameter, &block)
  end

  def to_s
    return " [(before) #{@methodname} -> "+@basecall.to_s+"]"
  end

  def to_string(level=1)
    return ("    "*level)+"[BeforeCall from #{connector.class.name}]\n"+("    "*level)+"->#{methodname}\n"+@basecall.to_string(level+1)
  end
end

# 1.: Call the base-call-object
# 2.: Call the role
class CallAfter < CallWeaved
  def call(object, *parameter, &block)
    #debug(self)
    #call base
    retval = @basecall.call(object, *parameter, &block)
    #call role
    if (not @connector.suspended?)
      nparams = match_parameter(parameter)
      call_role(object, *nparams, &block)
    end
    return retval
  end
  def to_s
    return " [(after) "+@basecall.to_s+" -> #{@methodname}]"
  end
  def to_string(level=1)
    return ("    "*level)+"[AfterCall from #{connector.class.name}]\n"+@basecall.to_string(level+1)+("    "*level)+"->#{methodname}\n"
  end
end

# 1.: Pass base-call-object to receiving role class.
# 2.: Call the role
class CallAround < CallWeaved
  attr :object
  attr :parameter
  attr :block

  def call(object, *parameter, &block)
    #debug(self)
    retval = nil
    if (not @connector.suspended?)
      #save parameters of call
      @object = object
      @parameter = parameter
      @block = block
      #adjust parameter
      nparams = match_parameter(parameter)
      #get role
      role = @connector[object]
      #set base method
      role.__basecall = self
      #call role
      retval = call_role(object, *nparams, &block)
      #unset base method
      role.__basecall = nil
    else
      #connector is suspended: call base directly
      retval = @basecall.call(object, *parameter, &block)
    end
    return retval
  end
  
  def call_base(*parameter, &block)
    # two possibilities: 
    #    -parameter given => take new arguments
    #    -no parameter given => take original arguments
    if (parameter.length>0)
      parameter = match_parameter(parameter)
    else
      parameter = @parameter
      block = @block
    end
    # call replaced method 
    return @basecall.call(@object, *parameter, &block)
  end
  
  def to_s
    return " [(replace) #{@methodname} ?>#{basecall.to_s} <?]"
  end
  def to_string(level=1)
    return ("    "*level)+"[ReplaceCall from #{connector.class.name}]\n"+("    "*level)+"->#{methodname}\n"+("    "*(level+1))+"[---------??---------]\n"+@basecall.to_string(level+1)+("    "*(level+1))+"[--------------------]\n"
  end
end

class JoinPoint
    attr_accessor :source
    attr_accessor :call
    attr_accessor :valid

    def initialize(source, call)
        @source=source
        @call=call
        @valid=false
    end

    def source_method
        @source
    end

    def sink_method
        @call.methodname
    end

    def source_pattern
        @call.source_pattern
    end

    def to_s
        "#{@call.class.name}: #{@source_method} => #{@call.methodname}"
    end

    @@singleton = JoinPoint.new("", nil)
    def JoinPoint.instance
        @@singleton
    end

    def JoinPoint.actualJoinPoint
        jp = JoinPoint.new(@@singleton.source, @@singleton.call)
        return jp
    end
end


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