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 <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