Commit 3108bc83 authored by Maxime Perrotin's avatar Maxime Perrotin

MSC Core API

parents
install:
@echo Installing the Python MSC libraray...
@python setup.py install --record install.record
.PHONY: install
API for manipulating and editing MSC files
(Message Sequence Charts)
Part of the TASTE project
(c) European Space Agency
http://taste.tuxfamily.org
Contact: maxime.perrotin@esa.int
#******************************************************************************
#
# 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
# <http://www.gnu.org/licenses/>.
#
# Author: Angel Esquinas <aesquina@datsi.fi.upm.es>
#
# Copyright (c) 2012 European Space Agency
#
#******************************************************************************
u"""MsCore contains all the principals class to manage "Message Sequence \
Chart" files and elements.
.. moduleauthor:: Angel Esquinas Fernandez <aesquina@datsi.fi.upm.es>
"""
from mscelement import MscElement
from mscevent import MscEvent
from mscinstance import MscInstance
from basicmsc import BasicMsc
from mscmessage import MscMessage
from mscdocument import MscDocument
from mscvisitor import MscVisitor
from msctimer import MscTimer
from mscinstancekind import MscInstanceKind
from mscmessagedecl import MscMessageDecl
import mscregexp as MscRegExp
__version__ = 1.0
__all__ = ['MscElement', 'MscEvent', 'MscTimer', 'MscInstanceKind',
'MscMessageDecl', 'MscRegExp', 'MscInstance', 'MscMessage',
'BasicMsc', 'MscDocument', 'MscVisitor']
#******************************************************************************
#
# 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
# <http://www.gnu.org/licenses/>.
#
# Author: Angel Esquinas <aesquina@datsi.fi.upm.es>
#
# Copyright (c) 2012 European Space Agency
#
#******************************************************************************
u"""
This class represent a **Basic Message Sequence Chart** *(BMSC)* element. It
have all the instances contains in the BMSC.
A Basic MSC must have all the elements used by itself as child.
The Basic MSC is a type of MSC that can be part of
:class:`~msccore.MscDocument`.
The Basic MSC can have events, but unlike :class:`~msccore.MscInstances` only
can have messages events. This message events are associated with *gates* in
the ITU-T Z.120 recommendation.
The messages (:class:`msccore.MscMessage`) added with
:meth:`BasicMsc.addMessage` only are setup as children or this BMSC. This is
neccesary because the messages can not be children of instances because one
message can be associated with the **sender** and ther **receiver**. The
message can be part of the BMSC as event, but in this case the message event
is added with :meth:`BasicMsc.addEvent`.
"""
import logging
from msc import Msc
from mscevent import MscEvent
logger = logging.getLogger(__name__)
class BasicMsc(Msc):
u"""
Constructs a new *Basic Message Sequence Chart* object.
:param unicode name: Name of BMSC
:param parent:
:type parent: PySide.QtCore.QObject
The `name` and `parent` parameters given as arguments are passed to the
parent constructor.
"""
TYPE = 'BMsc'
def __init__(self, name, parent=None):
u"""
Construct
"""
self.TYPE = self.TYPE
super(BasicMsc, self).__init__(name, parent)
self._instances = []
self._events = []
# Auxiliar Variable to save numbers of events when load
self.eventsOnLoad = None
def addInstance(self, inst):
u"""
Add instance `inst` as instance of this BMSC.
:param inst: Instance to add
:type inst: msccore.MscInstance
:rtype: Boolean
The :meth:mscelement._contentsChanged function is called to indicate
that the content of this basic MSC are changed.
"""
# Check if the instance is child of parent
if self._instances.count(inst) > 0:
logger.warning("Trying to add existing instance to basic msc")
return True
inst.setParent(self)
self._instances.append(inst)
# When instance will be deleted then removed it
inst.deleted.connect(self.removeInstance)
inst.contentsChanged.connect(self._contentsChanged)
logger.debug("Added instance '%(iname)s' to msc '%(mscname)s",
{'iname': inst.name(), 'mscname': self.name()})
self._contentsChanged()
return True
def removeInstance(self, inst):
u"""
Remove instance `inst` from this basic MSC.
:param inst: Instance to remove
:type inst: msccore.MscInstance
:rtype: Boolean
If `inst`, given as argument, is not include within this basic MSC
then `False` is returned.
The :meth:mscelement._contentsChanged function is called to indicate
that the content of this basic MSC are changed.
"""
if self._instances.count(inst) == 0:
logger.warning("Trying to remove not associated instance to this "
"basic msc")
return False
# Check if instance is child of me
inst.deleted.disconnect(self.removeInstance)
inst.contentsChanged.disconnect(self._contentsChanged)
self._instances.remove(inst)
inst.setParent(None)
logger.debug("Removed instance '%(iname)s' to msc '%(mscname)s",
{'iname': inst.name(), 'mscname': self.name()})
# Content Change
self._contentsChanged()
return True
def instances(self):
u"""
Return a list with all the instances within objects contain in
this basic MSC.
:rtype: list of msccore.MscInstance
"""
return self._instances[:]
def deleteInstances(self):
u"""
Remove all instances included within this :class:`~msccore.BasicMsc`.
"""
logger.debug("Deleting all instances of msc '%s'", self.name())
for i in self.instances():
self.removeInstance(i)
i.delete()
def addMessage(self, msg):
u"""
Add a `msg` :class:`~msccore.MscMessage` to this basic MSC.
:param msg: Message to add to this basic MSC.
:type msg: msccore.MscMessage
:rtype: Boolean
The message is added as child of this Basic Msc. Because of this it
can be used as event within this MSC.
The :meth:mscelement._contentsChanged function is called to indicate
that the content of this basic MSC are changed.
"""
if msg.parent() == self:
return True
msg.setParent(self)
# self.elementAdded(msg)
msg.deleted.connect(self.removeMessage)
msg.contentsChanged.connect(self._contentsChanged)
logger.debug("Added message '%(mname)s' to msc '%(mscname)s",
{'mname': msg.name(), 'mscname': self.name()})
# Change Content
self._contentsChanged()
return True
def removeMessage(self, msg):
u"""
Remove message `msg` from this basic MSC.
:param msg: Message to remove
:type msg: msccore.MscMessage
:rtype: Boolean
If `msg`, given as argument, is not include within this basic MSC
then `False` is returned.
The :meth:mscelement._contentsChanged function is called to indicate
that the content of this basic MSC are changed.
"""
if msg.parent() != self:
logger.warning("Trying to remove message '%(mname)s' from basic "
"msc '%(mscname)s' that is not associated",
{'mname': msg.name(), 'mscname': self.name()})
return False
msg.setParent(None)
# Disconnect deleted message
msg.deleted.disconnect(self.removeMessage)
msg.contentsChanged.disconnect(self._contentsChanged)
logger.debug("Removed message '%(mname)s' from msc '%(mscname)s",
{'mname': msg.name(), 'mscname': self.name()})
# Change content
self._contentsChanged()
return True
def instanceName(self):
u"""
Return the name of this Basic MSC.
:rtype: unicode
This functions is only a wrapper of :meth:`name` to be compatible
with the way to control **events** within instances. It is necessary
because basics MSCs can send/receive messages.
"""
return self.name()
#*************************************************************************
# Messages or Events
#*************************************************************************
def addEvent(self, elem, pos=None):
u"""
Add new :class:`~msccore.MscEvent` to this MSC. 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, within MSC,
this elements are:
* :class:`~msccore.MscMessage`
The created event is returned.
The :meth:`MscElement._contentsChanged` function is called to indicate
that the content of this basic MSC are changed.
.. warning::
Event only must be in one basic MSC or instance at the same time.
"""
# TODO: Allow positional creation
event = MscEvent(elem, self)
# Connect 'deleted' signal
event.deleted.connect(self._eventDeleted)
event.contentsChanged.connect(self._contentsChanged)
self._events.append(event) # Put event in the last position
logger.debug("Added event to basic Msc with element '%s'",
elem.name())
self._contentsChanged()
return event
def removeEvent(self, event):
u"""
Remove event `event` from this basic MSC.
:param event: Event to remove
:type event: msccore.MscEvent
:rtype: Boolean
The event is only removed from this MSC, but it is not destroyed. The
`parent` of the event is set to `None`.
If `event`, given as argument, is not include within this basic MSC
then `False` is returned.
The :meth:`mscelement._contentsChanged` function is called to indicate
that the content of this basic MSC 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 basic msc "
"'%(mname)' which are not associated",
{'ename': event.name(), 'mname': self.name()})
return False
self._events.remove(event)
# Disconnect 'deleted' signal and event haven't parent yet
event.deleted.disconnect(self._eventDeleted)
event.contentsChanged.disconnect(self._contentsChanged())
event.setParent(None)
logger.debug("Removed event '%(ename)s' from msc '%(mscname)s",
{'ename': event.name(), 'mscname': 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`
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 basic MSC.
:rtype: list of msccore.MscEvents
The list returned are ordered. The :meth:`BasicMsc.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 _eventDeleted(self, event):
"""
Remove the event 'event'. It will be deleted. This function is the
handler for 'deleted' signal of 'event'
"""
self.removeEvent(event)
###########################################################################
# Delete
###########################################################################
def delete(self):
u"""
Delete this basic MSC.
The events, instances and messages included within this basic MSC are
deleted before it will be destroy.
The :meth:`MscElement.delete` method is called at begining of this
method.
.. note::
After this basic MSC is delete, its references is not valid. It can
not be used.
"""
# Tell that I will be deleted
self.deleted.emit(self)
self.deleteInstances()
for event in self._events:
self.removeEvent(event)
event.delete()
# When remove instances, messages are deleted
self.deleteLater()
#**************************************************************************
# Visitor Pattern
#**************************************************************************
def accept(self, visitor):
u"""
Implementation of visitor pattern for :class:`msccore.BasicMsc`.
:param visitor:
:type visitor: :class:`msccore.MscVisitor`
This function call :meth:`msccore.MscVisitor.visitorBMSC`.
"""
visitor.visitorBMSC(self)
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)
for i in self._instances:
i.show(n + 1)
#******************************************************************************
#
# 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
# <http://www.gnu.org/licenses/>.
#
# Author: Angel Esquinas <aesquina@datsi.fi.upm.es>
#
# Copyright (c) 2012 European Space Agency
#
#******************************************************************************
'''
Created on Jun 11, 2012
@author: angel
'''
class Virtuality():
Virtual, Redefined, Finalized = range(3)
#******************************************************************************
#
# 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
# <http://www.gnu.org/licenses/>.
#
# Author: Angel Esquinas <aesquina@datsi.fi.upm.es>
#
# Copyright (c) 2012 European Space Agency
#
#******************************************************************************
from mscio import *
\ No newline at end of file
import logging
from msccore.mscvisitor import MscVisitor
logger = logging.getLogger(__name__)
class CoreToText(MscVisitor):
u'''
Export MscCore Document to Msc Textual File according ITU T.Z120
Recommendation (02 / 2011)
'''
INDENT = u" "
def __init__(self, path):
#Open the file in write mode
self._file = open(path, 'w')
def write(self):
self._file.write(self._result)
self._file.close()
def _declInstances(self, instances):
u""" Create decl line """
self.instDeclDict = {}
for i in instances:
name = i.getName()
# Create line of text
if name == u"":
try:
text = u"{}".format(i.instanceKind().kindString())
except:
logger.error("Instance with no name and no kind")
else:
text = u"{}".format(name)
if i.instanceKind():
# If instanceKind is declared it must not be empty
text += u": {}".format(i.instanceKind().kindString())
if not (text in self.instDeclDict):
self.instDeclDict[text] = text
# Create text of all declarations
instDecl = u""
for i in self.instDeclDict.values():
instDecl += u"{}inst {};\n".format(self.INDENT, i)
return instDecl
def _declMessages(self, msgDecl):
u""" Return text for all messages declared with parameters """
# The messages when parameters must be declared within msc document,
# because of this only read msgdecl from msc document.
msgs = u""
for m in msgDecl:
text = m.textual()
text = "{ind}msg {msg};\n".format(ind=self.INDENT,
msg=text)
msgs += text
return msgs
def visitorMscDocument(self, element):
instancesUsed = []
mscs = u''
for m in element.mscs():
m.accept(self)
text = self.obtainResult()
text = self.indent(text)
mscs = mscs + text
# Ask for instances inside msc
instancesUsed.extend(m.instances())
# Data definition clause
dataDef = u""
lang = element.language()
if lang != None:
dataDef += u"{ind}language {lang};\n".format(ind=self.INDENT,
lang=lang)
data = element.dataDefinition()
if data != None:
dataDef += u"{ind}data {data};\n".format(ind=self.INDENT,
data=data)
declInstances = self._declInstances(instancesUsed)
declMsgs = self._declMessages(element.messageDecl())
head = 'mscdocument {0};\n'.format(element.getName())
head += dataDef
head += declInstances
head += declMsgs
end = 'endmscdocument;\n'
self._result = head + mscs + end
def visitorBMSC(self, element):
instances = u""
events = u""
self.msgTS = {} # To know if exist messages with the same name
self.instance = element
for e in element.events():
e.element.accept(self)
text = self.obtainResult()
text = u"gate {0}".format(text)
text = self.indent(text)
events = events + text
for i in element.instances():
i.accept(self)
text = self.obtainResult()
text = self.indent(text)
instances = instances + text
head = u'msc {0}{1}\n'.format(element.getName(), self.end(element))
end = u'endmsc;\n'
self._result = head + events + instances + end
def visitorMscInstance(self, element):
if element.instanceKind() == None:
head = u"{} : instance {}\n".format(element.getName(),
self.end(element))
else:
# If instance have kind, then is posible that have name
if element.getName() != u"":
head = (u"{name} : instance {kind}{end}\n"
.format(name=element.getName(),
kind=element.instanceKind().kindString(),
end=self.end(element))
)
else:
head = (u"instance {kind}{end}\n"
.format(kind=element.instanceKind().kindString(),
end=self.end(element))
)
end = u"endinstance;\n"
self.instance = element
events = u''
for e in element.events():
e.element.accept(self)
text = self.obtainResult()