mscinstance.py 12.4 KB
Newer Older
Maxime Perrotin's avatar
Maxime Perrotin committed
1 2 3 4 5 6 7 8
#******************************************************************************
#
# 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
9
#  modify it under the terms of the GNU Lesser General Public License as published
Maxime Perrotin's avatar
Maxime Perrotin committed
10 11 12 13 14 15
#  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
16
#  GNU Lesser General Public License for more details.
Maxime Perrotin's avatar
Maxime Perrotin committed
17
#
18
#  You should have received a copy of the GNU Lesser General Public License
Maxime Perrotin's avatar
Maxime Perrotin committed
19 20 21 22 23
#  along with TASTE Msc Diagram Editor.  If not, see
#  <http://www.gnu.org/licenses/>.
#
#  Author: Angel Esquinas <aesquina@datsi.fi.upm.es>
#
24
#  Copyright (c) 2012 UPM and European Space Agency
Maxime Perrotin's avatar
Maxime Perrotin committed
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
#
#******************************************************************************
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 <instance name> 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`
178
        * :class:`~msccore.MscCondition`
Maxime Perrotin's avatar
Maxime Perrotin committed
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254

        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`
255
        * :class:`~msccore.MscCondition`
Maxime Perrotin's avatar
Maxime Perrotin committed
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365

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