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