#******************************************************************************
#
# 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())