# 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.
syntax highlighted by Code2HTML, v. 0.9.1