class Team < Connector # some utility operations include TeamUtil # for logging purposes include Logging::NamedLog # all base proxies # "#{roleclass.name}=>#{baseclass.name}" -> proxy @@baseproxies = Hash.new # All deployment descriptors (array) this connector shall connect. attr :deployments # all role objects, created by this connector # role->bool attr :roles # all compound objects related to this connector # compund->role attr :compounds # remember all types, that play no role in this team => make lifting efficient attr :noroles # indicates activenes of this connector attr :active # indicates, if this connector is suspended attr :suspended # indicates, if this connector is weak => no role shall created implicitely attr_accessor :weak # id of connector - only used in to_s to identify different connector of same type attr :connectorid # a proc can be passed to the constructor, which will be used to create role # objects. This proc can be nil. attr :rolecreator # set alias to the question mark methods (bool) alias_method(:active?, :active) alias_method(:weak?, :weak) alias_method(:suspended?, :suspended) public #-------------------------------------------------------------------------------- # Construct a dynamic connecor. # This connecor is usable during Team::while_active(). # [deployments] all deployments, this connector should bind - nil will take # the deployments, defined by the connector (default way). # [rolecreator] it is possible to pass a block to the constructor, which # will be used to create role objects. def initialize(&rolecreator) init_log(self.class.name) debug("Connector created") @deployments = adjust_deployments(self.class.get_all_deployments()) @compounds = ROTConfig::WEAKREFERENCE ? WeakHash.new : Hash.new @roles = ROTConfig::WEAKREFERENCE ? WeakHash.new : Hash.new @externalizedroles = ROTConfig::WEAKREFERENCE ? WeakHash.new : nil @noroles = Hash.new @active = false @suspended = false @weak = false @connectorid = Team.connectorID(self.class) @rolecreator = rolecreator #sort all deployments, so deployments of superclasses go first @deployments = @deployments.sort{ |a,b| (b.baseclass<=>a.baseclass or 0) } end def to_s() result = self.class.name+"-#{@connectorid}" end # Activates the connector, executes the given block and deactivates # afterwards. # [block] to execute. def while_active() if (block_given?) begin activate() yield ensure deactivate() end end end # Make the connector active. # All deployments will be weaved into the specified classes. def activate() if (@deployments.nil?) if self.class.get_all_deployments().nil? raise ObjectTeam::TeamException, "No deployments given! - what to activate?", caller else raise ObjectTeam::TeamException, "Deployments not initialized! - Did you call super() during initialize??", caller end end @deployments.each { |deployment| info("deploy #{deployment.baseclass.name} -> #{deployment.roleclass.name}") #setup all weavings deploy_callins(deployment.baseclass, deployment.roleclass, deployment.callins) } #set activeness @active = true end # Make te connector inactive. # Remove all weavings, that are done during activate. def deactivate() @deployments.each { |deployment| deployment.baseclass.deactivate(self) deployment.baseclass.remove_role(self, deployment.roleclass) } #set activeness @active = false end # This method is used to obtain an externalized role of the given base object. # This method ensures the existence of the given role and the lifetime of # the role (the role will be garbage collected, if the given base object # gets finzlized. This can't happen, if there is at least one externalized # role of the given baseobject) # [baseobject] the base object the role is requested for # [return] the externalized role object def as_role(baseobject) role = lift(baseobject) raise(ObjectTeam::TeamException, "No role for #{baseobject}", caller) if role.nil? externrole = ObjectTeam::ExternalizedRole.new(self, role) @externalizedroles[externrole] = baseobject if (ROTConfig::WEAKREFERENCE) return externrole end # Lift the given parameter to its related role object, if this is a base # object. If not, the given paramter will be returned. # If an array is given, the whole array is lifted. def lift(param) result = nil if (param.instance_of?(Array)) result = Array.new param.each { |p| result.push(lift_parameter(p)) } elsif (param.instance_of?(ObjectTeam::ExternalizedRole)) #CHECK: if this role is ours => unwrap result = (param.__connector==self ? param.__role : nil) #result = (param.__connector==self ? param.__role : lift_parameter(param.__base)) else result = lift_parameter(param) end return result end # Lift exactly one parameter. # If the parameter's class is known by this connector it is # lifted to the known role-class. If the role class does not # exist, is is created. # [param] the paramter to lift. # [create] indicates, if a new role should be created, if param is player in this team and has no role # [return] the lifted paramter, if lifting is possible. Otherwise the parameter itself. def lift_parameter(param, create=true) result = nil #do we have a role for this baseobject? result = @compounds[param] if (result.nil?) #do we already searched for a role of this type? if (@noroles[param.class]) result = param else #do we have a playedBy clause for the type of given object? roleclass = self.class.get_roleclass_of(param.class) if (!roleclass.nil?) #create role result = create_role(param, roleclass) if (create) else #remember for further lookups @noroles[param.class] = true #no lifting result = param end end end return result end # Lower the given parameter to its related base object, if this is a role # object. If not, the given paramter will be returned. # If an array is given, the whole array is lowered. def lower(param) result = nil if (param.instance_of?(Array)) result = Array.new param.each { |p| result.push((@roles[p] ? p.__baseobject : p)) } elsif (param.instance_of?(ObjectTeam::ExternalizedRole)) result = (param.__connector==self ? param.__base : param) else result = ((@roles[param] ? param.__baseobject : param)) end return result end # This method returns the roleobject of the given compound # related by this connector. This is useful for explicit lifting: # myconnector[base].rolemethod(..) # The roleobject get's created, if it is not existent. # [compound] the base object # [return] the role object def [](compound) lift_parameter(compound, !@weak) end # This method suspends the team (Team freezing). All weaved action is # suspended until resume is called. def suspend() debug("Team suspended"); @suspended = true; end # A suspended Team can be resumed. All weaved action takes place as any # ususal actiavted team. def resume() debug("Team resumed"); @suspended = false; end protected #-------------------------------------------------------------------------------- #Create role object. #Must match one deployment. # [compound] the compound the role wants to live in # [roleclass] the roleclass for the new role, nil if not known # [result] the role object or nil def create_role(compound, roleclass=nil) #initialize return value roleinstance = nil #lookup for roleclass, if not given roleclass = self.class.get_roleclass_of(compound.class) if (roleclass.nil?) #create an instance if (@rolecreator) #block is given roleinstance = @rolecreator.call(compound, roleclass) else #no block -> call default implementation roleinstance = create_roleobject(compound, roleclass) end if (roleinstance) #could be another class, than the roleclass above roleclass = roleinstance.class debug("created role for #{compound.class.name} of type: #{roleclass.name}") #is the Kernel.Role module included? if (!roleclass.included_modules.include?(ObjectTeam::Role)) debug("include ObjectTeam::Role into #{roleclass.name}") roleclass.send("include", ObjectTeam::Role) end #init base call roleinstance.__basecall = nil #set a class-proxy as base proxy = get_base_proxy(roleclass, compound.class) roleinstance.__baseproxy = proxy #set base object roleinstance.__baseobject = compound #save the role object @roles[roleinstance] = true #remember relation compund -> roleinstance @compounds[compound] = roleinstance #call the user created constructor, if exists roleinstance.send("#{Interceptor::PREFIX}_initialize") if roleclass.private_instance_methods(false).include?("#{Interceptor::PREFIX}_initialize") end return roleinstance end # This method is called, whenever a role object must be created # for the given compound. Override this method to fit custom behaviour. # The default implementation simply return roleclass.new # (Perhaps your role objects don't have a default constructor, or it should # be possible to manage different roles to the same type of base ...) # [compound] the base object, this role is created for. # [roleclass] the role class defined by the connector # [return] the role instance that matches the given base object. def create_roleobject(compound, roleclass) return roleclass.new end # Deploys all callins, given for a specific role. # [baseclass] the class all callins take place # [roleclass] the callins point to an instance of this class # [callins] array of callin-objects def deploy_callins(baseclass, roleclass, callins) #iterate over all deployments callins.each { |callin| debug("setup call for #{baseclass.name}.#{callin.source} => #{roleclass.name}.#{callin.sink}") #honor callin renaming: if (callin.mode==Mode::CALLIN and !baseclass.get_callin(callin.source).nil? ) ot_assert(!baseclass.method_defined?(callin.source), "can't deploy callin #{callin.source} as normal callin - use replace!") callin = Callin.new(callin.source, callin.sink, Mode::REPLACE) end deploy_callin(baseclass, roleclass, callin) } #set roles as active in baseclass baseclass.add_role(self, roleclass) #shall we autoconnect?? if (ROTConfig::AUTOCONNECT) #are there any methods left to connect? while (roleclass != Object) expected_methods = Kernel.get_expected_methods(roleclass) #don't deploy expected methods! roleclass.public_instance_methods(false).each { |method| deplcallin = baseclass.get_callin(method) #is there an "unbound" rolemethod (which is not expected)? if ( (deplcallin.nil? or deplcallin.connector!=self) and !expected_methods.include?(method) and !baseclass.method_defined?(method)) #mode of callin mode = nil #is this method a callin, defined by an other connector? if (!deplcallin.nil?) #the method is defined by an other connector! warn("Replace callin method #{baseclass.name}.#{method} from connector: #{baseclass.get_callin(method).connector.class.name}") mode = Mode::REPLACE else mode = Mode::CALLIN end #deploy the method deploy_callin(baseclass, roleclass, Callin.new(method, method, mode), false) end } roleclass = roleclass.superclass end end end #Create a call object with the given callin descriptor. # register the call-object under the given name. def deploy_callin(baseclass, roleclass, callin, warnIfNoMethodFound=true) #is the calling role method valid? ot_assert(roleclass.method_defined?(callin.sink), "The rolemethod #{roleclass.name}.#{callin.sink} does not exist") # if (callin.sink!="initialize") #just a simple callin or a more complex call? if (callin.mode==Mode::CALLIN) #the methodname may not be used before! ot_assert(get_all_method_names(baseclass, callin.source).empty?, "Callin defined: >#{roleclass.name}.#{callin.source}<, but this method exists => use replace!") debug("Deploy callin CALLIN: #{baseclass.name}.#{callin.source} => #{callin.sink}") #create call object call = RoleCall.new(self, callin.sink, callin.source) #make the call available baseclass.set_callin(callin.source, call) else ###set the call-class################################## callclass = nil; case callin.mode when Mode::AFTER callclass = CallAfter when Mode::BEFORE callclass = CallBefore when Mode::REPLACE callclass = CallAround else error("Unknown Mode given in callin :#{callin.mode}") raise ObjectTeam::TeamException, "Unknown Mode given in callin :#{callin.mode}", caller end ###iterate over matching methods####################### methodnames = get_all_method_names(baseclass, callin.source) warn("No method matched by methodname: #{baseclass.name}.\'#{callin.source}\'") if (methodnames.empty? and warnIfNoMethodFound) methodnames.each { |methodname| debug("Deploy callin #{Mode.to_string(callin.mode)}: #{baseclass.name}.#{methodname} => #{callin.sink}") #the base call object base = baseclass.get_callin(methodname, true) call = callclass.new(self, callin.sink, callin.source, callin.parametermapping, base) #make the call available baseclass.set_callin(methodname, call) } end end # Return a baseproxy object, which will be called from roleclass-instances # that will callout to baseclass-instances. The baseproxy gets all the # callout deployments necessary for these connection. Because the baseproxy # has no instance state, each connection is shared by the same proxy object. def get_base_proxy(roleclass, baseclass) debug("#{roleclass.name} => #{baseclass.name}") key = "#{roleclass.name}=>#{baseclass.name}" proxy = @@baseproxies[key] if (proxy.nil?) debug("create BaseProxy for #{key}") #create proxy proxy = BaseProxy.new() klass = baseclass deployment = nil #climb until object while (klass != Object) #get deployment descriptor @deployments.each { |depl| if (depl.baseclass == klass) deployment = depl break; end } #set all callouts if (!deployment.nil?) deployment.callouts.each { |callout| proxy.add_callout_definition(callout) } end klass = klass.superclass end #give warning, if no deployment took place warn("#{baseclass.name}: no deployment found") if (deployment.nil?) #remember proxy @@baseproxies[key] = proxy end #given proxy is only a template proxy = proxy.clone proxy.connector = self return proxy end def adjust_deployments(deployments) result = Array.new deployments.each { |deployment| classname = deployment.roleclass.name.split(NamespaceRE)[-1] if (self.class.const_defined?(classname)) roleclass = self.class.const_get(classname) if (roleclass != deployment.roleclass) #copy the deployment -> do not propagate changes info("deployment declared as #{deployment.roleclass.name} change to actual #{roleclass.name}") deployment = deployment.dup deployment.roleclass = roleclass end end result.push(deployment) } return result end # static stuff ================================================================== @@connectorids = Hash.new def Team.connectorID(classhandle) if (@@connectorids[classhandle].nil?) @@connectorids[classhandle] = 0 else @@connectorids[classhandle] = @@connectorids[classhandle]+1 end return @@connectorids[classhandle] end # Dumb help construct to support static connectors. # FIXME: better idea? def Team.activate_static(teamclass) #team = teamclass.new(teamclass.get_all_deployments) team = teamclass.new() team.activate end # realize automatic implicit inheritance ========================================= def Team.inherited(teamklass) if(teamklass.superclass != Team) #Note: Since 27.09.2002 the ruby interpreter calls this method _AFTER_ #the class has parsed. So the given classes are already defined! (teamklass.constants-Team.constants).each { |stringconst| if teamklass.const_get(stringconst).instance_of?(Class) raise(ObjectTeam::TeamException, "The implicit inheritance feature is not usable in this version of ruby!!\n Please use a patched version or a version prior 27.09.2002\n", caller) if teamklass.const_defined?(stringconst) teamklass.module_eval("class #{stringconst} < #{stringconst} \nend") end } end end # List all roles, defined in a subclass of this class. # [return] array of classes def Team.list_roles() result = [] (self.constants-Team.constants).each { |stringconst| const = self.const_get(stringconst) result.push(const) if const.instance_of?(Class) } return result end end # Team.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.