#****************************************************************************** # # TASTE Msc Diagram Editor # http://taste.tuxfamily.org/ # # This file is part of TASTE Msc Editor. # # TASTE Msc Diagram Editor 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 3 of the License, or # (at your option) any later version. # # TASTE Msc Diagram Editor 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 TASTE Msc Diagram Editor. If not, see # . # # Author: Angel Esquinas # # Copyright (c) 2012 European Space Agency # #****************************************************************************** u""" The :class:`msccore.MscInstance` represents the interacting instances that compound a Message Sequence Chart. MSC are represented by :class:`msccore.BasicMsc`. An instance of an instance kind has the properties of this kind. Within the instance the ordering of events is specified. """ import logging from mscelement import MscElement from mscevent import MscEvent logger = logging.getLogger(__name__) class MscInstance(MscElement): u""" Constructs a new *Instance* object. :param unicode name: Name of instance. :param kind: Instance kind of instance. :type kind: msccore.MscInstanceKind :param parent: :type parent: Pyside.QtCore.QObject The `name` and `parent` parameters given as arguments are passed to the parent constructor. """ TYPE = 'Instance' def __init__(self, name=u"", kind=None, parent=None): ''' Constructor ''' self._kind = None self._events = [] # Have the events in order super(MscInstance, self).__init__(name, parent, self.TYPE) if kind != None: self.setInstanceKind(kind) def setName(self, name): u""" Instances can have no name if kind is set :param unicode name: Name of instance. Is possible to put an empty string as name when the instance is instance of a instance kind. In other case the :class:`MscElement.setName` method is called within this method. """ if self.instanceKind() != None and name == "": self._name = u'' self.dataChanged() else: super(MscInstance, self).setName(name) #************************************************************************** # Get/Set Variables #************************************************************************** def setInstanceKind(self, kind=None): u"""Set `kind` as instance kind this instance. :param kind: instance kind of instance. :type kind: msccore.MscInstanceKind """ logger.debug("Setting kind '%(kname)s in instance '%(iname)s", {'kname': kind, 'iname': self}) if self._kind != None: self._kind.dataHasChanged.disconnect(self.dataChanged) # Disconnect 'deleted' signal self._kind.deleted.disconnect(self._deleteKind) # Set the new Kind self._kind = kind if kind != None: kind.dataHasChanged.connect(self.dataChanged) # Connect 'deleted' signal self._kind.deleted.connect(self._deleteKind) self.dataChanged() # This emit contents changed def instanceKind(self): u""" Return the instance kind associated with this instance. :rtype: msccore.MscInstanceKind or None """ return self._kind def instanceName(self): u""" Return the conform to ITU-T Z.120 recommendation (Message Sequence Chart). :rtype: unicode The ITU-T Z.120 declared the instance name as the textual representation of the kind associated, if any, plus the name of the instance. This function return this expression. .. example:: Instance with name "Inst_1", return `Inst_1`. Instance with no name and instance kind "process gui", return `process gui`. Instance with name and instance kind, return `process gui Inst This function is to help when instance is declared with only kind identifier and no name. """ if super(MscInstance, self).getName() == u"": if self._kind != None: if len(self._kind.kindString().split()) != 1: logger.error("""mscinstance: instanceName: Instance with no name and complex kind""") return self._kind.identifier() else: return self._kind.kindString() else: logger.error(u"Instance with no name and no kind") return u"" else: return super(MscInstance, self).name() #************************************************************************* # Messages or Events #************************************************************************* def addEvent(self, elem, pos=None): u""" Add new :class:`~msccore.MscEvent` to this Instance. New event is created with `elem` as content. :param elem: Element to create the new event. :type elem: msccore.MscElement :rtype: msccore.MscEvent Although `elem` can be any :class:`~msccore.MscElement` object only "event" elements must be used as argument. Until now, this elements are: * :class:`~msccore.MscMessage` * :class:`~msccore.MscTimer` * :class:`~msccore.MscCondition` The created event is returned. The :meth:`MscElement._contentsChanged` function is called to indicate that the content of this instance are changed. .. warning:: Event only must be in one basic MSC or instance at the same time. """ event = MscEvent(elem, parent=self) # Connect Signals event.deleted.connect(self._deleteEvent) event.contentsChanged.connect(self._contentsChanged) self._events.append(event) # Put event in the last position logger.debug("Added event to instance '%s' with element '%s'", self.instanceName(), elem.name()) self._contentsChanged() # content has changed return event def removeEvent(self, event): u""" Remove event `event` from this instance. :param event: Event to remove :type event: msccore.MscEvent :rtype: Boolean The event is only removed from this instance, but it is not destroyed. The `parent` of the event is set to `None`. If `event`, given as argument, is not include as event within this instance then `False` is returned. The :meth:`MscElement._contentsChanged` function is called to indicate that the content of this Instance are changed. .. warning:: Event only must be in one basic MSC or instance at the same time. """ if self._events.count(event) == 0: logger.error("Trying to remove event '%(ename)s' from instance " "'%(iname)' which are not associated", {'ename': event.name(), 'iname': self.name()}) return False self._events.remove(event) # Disconnect 'deleted' signal and event haven't parent yet event.deleted.disconnect(self._deleteEvent) event.contentsChanged.disconnect(self._contentsChanged) event.setParent(None) logger.debug("Removed event '%(ename)s' from instance '%(iname)s", {'ename': event.name(), 'iname': self.name()}) self._contentsChanged() return True def delEvent(self, elem): u""" Remove and destroy the `event` associated with the element `elem`. :param elem: Msc element that are associated with the event. :type elem: msccore.MscElement :rtype: Boolean Return `True` if exist event associated with the `elem` given as argument, and it has been destroyed. In other case return `False`. .. note:: The element associated with the event, if any, is destroyed. Although `elem` can be any :class:`~msccore.MscElement` object only "event" elements must be used as argument. Until now, this elements are: * :class:`~msccore.MscMessage` * :class:`~msccore.MscTimer` * :class:`~msccore.MscCondition` The :meth:`MscElement._contentsChanged` function is called to indicate that the content of this basic MSC are changed. """ for event in self._events: if event.isElement(elem): self.removeEvent(event) # Delete the Event event.delete() return True return False def events(self): u""" Return a list with all events included in this instance. :rtype: list of msccore.MscEvents The list returned are ordered. The :meth:`MscInstance.reorderEvents` method is used to order the events. """ self.reorderEvents() return self._events def reorderEvents(self): u""" Reorder the list of events, depending of the absolute position of the element associated with the event. """ self._events.sort(key=lambda event: event.pos()) def delete(self): u""" Delete this instance. The events included within this basic instance are deleted before it will be destroy. The :meth:`MscElement.delete` method is called at begining of this method. .. note:: After this instance is delete, its references is not valid. It can not be used. """ logger.debug("Deleting instance '%s", self.name()) # I will be deleted self.deleted.emit(self) logger.debug("Events of instance '%s' are '%s'", self.name(), self._events) for event in self._events: self.removeEvent(event) event.delete() self.deleteLater() ########################################################################### # Signals handlers ########################################################################### def _deleteKind(self, kind): """ Set kind to 'None'. This function is the handler for 'deleted' signal of kind associated """ if kind != self._kind: logger.error("Trying to delete Kind %(kname)s from Instance " "%(iname)s which are not associated", {'kname': kind.name(), 'iname': self.name()}) self.setInstanceKind(None) def _deleteEvent(self, event): """ Remove the event 'event'. It will be deleted. This function is the handler for 'deleted' signal of 'event' """ self.removeEvent(event) ########################################################################### # VISITOR PATTERN ########################################################################### def accept(self, visitor): u""" Implementation of visitor pattern for :class:`msccore.MscInstance`. :param visitor: :type visitor: :class:`msccore.MscVisitor` This function call :meth:`msccore.MscVisitor.visitorMscInstance`. """ visitor.visitorMscInstance(self) ########################################################################### # OUTPUT ########################################################################### def show(self, n=1): tab = '' i = 1 while (i < n): tab = tab + ' ' i = i + 1 print tab + '{0}'.format(self) self.reorderEvents() for i in self._events: i.show(n + 1) def __str__(self): if self.instanceKind(): return u"<<{}: {} => {}>>".format(self.TYPE, self.getName(), self.instanceKind()) else: return u"<<{}: {}>>".format(self.TYPE, self.getName())