# Copyright (c) 2000-2004 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # 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 of the License, 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. """ convert the class hierarchy describing the project to XMI 1.0 UML 1.3 DOM representation please notices that it is based on file generated by ArgoUML """ from __future__ import generators __revision__ = "$Id: xmi_uml.py,v 1.16 2005/04/15 11:00:50 syt Exp $" import sys import os USE_DOCTYPE = os.environ.get('USE_DOCTYPE') DEBUG = os.environ.get('DEBUG') ## try: ## # try to use 4Suite cDomlette if available ## from Ft.Xml.cDomlette import implementation ## if DEBUG: ## print >> sys.stderr, ':: using cDomlette' ## def Document(namespace, q_name, pub_id, sys_id): ## """xml document factory, cDomlette based""" ## doctype = None ## if USE_DOCTYPE: ## doctype = implementation.createDocumentType(q_name, pub_id, sys_id) ## doc = implementation.createDocument(namespace, q_name, doctype) ## return doc ## except ImportError: # default to minidom from xml.dom.minidom import DOMImplementation implementation = DOMImplementation() ## if DEBUG: ## print >> sys.stderr, ':: using minidom' def Document(namespace, q_name, pub_id, sys_id): """xml document factory, minidom based""" doctype = None if USE_DOCTYPE: doctype = implementation.createDocumentType(q_name, pub_id, sys_id) doc = implementation.createDocument(namespace, q_name, doctype) root = doc.documentElement doc.appendChild(root) return doc from logilab.common import astng from logilab.common.astng.inspector import IdGeneratorMixIn from logilab.common.configuration import OptionsProviderMixIn from pyreverse.utils import FilterMixIn, \ is_abstract, is_final, is_interface, is_exception from pyreverse.extensions.xmiutils import * # FIXME: shouldn't use XMI_* constantes here from pyreverse.extensions.xmiutils import NO_NS, XMI_ABSTRACTION, \ XMI_BE_SIGNAL_CONT, XMI_BF, XMI_BF_PARAM, XMI_DEPENDENCY, XMI_FEATURE, \ XMI_GENERALIZATION, XMI_G_ELMT_GENE, XMI_G_ELMT_SPEC, XMI_MODEL_ELMT_SD MAX_CHAR = 20 class XMIUMLVisitor(FilterMixIn, IdGeneratorMixIn, OptionsProviderMixIn): """generate XMI-1.0 UML-1.3 xml tree from the project description """ name = "xmi-uml" def __init__(self): IdGeneratorMixIn.__init__(self) FilterMixIn.__init__(self) OptionsProviderMixIn.__init__(self) def filter_nodes(self, nodes): """filter nodes by removing node which don't belong to the project""" project = self.project for node in nodes: try: project.get_module(node.root().name) yield node except KeyError: continue def visit(self, node, idstart, doc_factory=Document): """initialize the XMI-UML visitor and launch the visit on the given project node """ self._uuid = '127-0-0-1-unbelid:entifiant:' self.init_counter(idstart) self.doc_factory = doc_factory self.project = node self._xref_tab = {} self._signals = {} self._signal_objects = {} self._modules = {} self._attributes = {} # initialize an xmi dom tree doc = self._doc = xmi_document(self.doc_factory) project = xmi_content(doc, 0, self.UUID_format(0), node.name) # modules for module in node.modules: project.appendChild(self.handle_module(module, doc)) # modules dependences for module, dom_n in self._modules.items(): for dep in module.depends: try: dep = node.get_module(dep) except KeyError: continue n_id = self.generate_id() d = xmi_dependency(doc, n_id, self.UUID_format(n_id), module.uid, dep.uid, project) before = dom_n.lastChild while before.previousSibling.nodeName == XMI_MODEL_ELMT_SD: before = before.previousSibling d = add_me_dependency(dom_n, client=1, before=before) add_idref(d, XMI_DEPENDENCY, n_id) d = add_me_dependency(self._modules[dep], before=self._modules[dep].lastChild, client=0) add_idref(d, XMI_DEPENDENCY, n_id) # process generalization for c, p in self._xref_tab.values(): p.appendChild(c) # process exceptions for parent, children in self._signals.items(): for child in children: dom_node = self.handle_class(child, doc) if hasattr(child, 'context'): for c in child.context: me = doc.createElementNS(NO_NS, XMI_BE_SIGNAL_CONT) dom_node.appendChild(me) add_idref(me, XMI_BF, c.uid) parent.appendChild(dom_node) # process associations done = {} for (from_node, to_node) in self._attributes.keys(): if done.has_key((from_node, to_node)): continue done[(to_node, from_node)] = 1 nav = self._attributes.has_key((to_node, from_node)) dom_node = self.handle_association(from_node, to_node, nav, doc) project.appendChild(dom_node) return doc def handle_module(self, node, doc): """return the xmi package dom for a module astng """ package, content = xmi_package(doc, node.uid, self.UUID_format(node.uid), node.name) self._modules[node] = package # push modules self._module = content # split classes between exception and regular classes, only visit # regular classes self._signals[content] = [] for klass in self.filter_nodes(node.locals.values()): if not (isinstance(klass, astng.Class) and self.filter(klass)): continue if is_exception(klass): self._signals[content].append(klass) continue content.appendChild(self.handle_class(klass, doc)) return package def handle_class(self, node, doc): """return the xmi class dom for a class astng """ n = self._create_class_dom(node, doc) methods = [f for f in node.locals.values() if isinstance(f, astng.Function) and self.filter(f)] attributes = [(name, value) for name, value in node.instance_attrs_type.items() if self.filter(name)] # do we need a children container ? if not (attributes or methods): return n content = doc.createElementNS(NO_NS, XMI_FEATURE) n.appendChild(content) # class member attributes project = self.project for name, value in attributes: # is the value of this attribute a link to a known instance if isinstance(value, astng.Class): try: project.get_module(value.root().name) except KeyError: continue self._attributes[(node, value)] = name continue content.appendChild(self.handle_attribute(node, name, value, doc)) # class methods for method in methods: content.appendChild(self.handle_method(method, doc)) return n def handle_method(self, node, doc): """return the xmi operation dom for a method astng """ function = xmi_operation(doc, node.uid, self.UUID_format(node.uid), node.name, is_final(node), node.is_class_method()) # function arguments if node.argnames[1:]: content = doc.createElementNS(NO_NS, XMI_BF_PARAM) for name in node.argnames: content.appendChild(xmi_parameter(doc, self.generate_id(), name)) function.appendChild(content) return function def handle_attribute(self, node, name, value, doc): """return the xmi attribute dom for a class member attribute """ if isinstance(value, astng.Node): value = '' else: value = str(value)[:MAX_CHAR] n_id = self.generate_id() return xmi_attribute(doc, self.generate_id, n_id, self.UUID_format(n_id), name, value) def handle_association(self, from_node, to_node, nav, doc): """return the xmi association dom for an association between two classes """ a = [] for klass in (from_node, to_node): if is_interface(klass): a.append(('interface', klass.uid)) elif is_exception(klass): a.append(('signal', klass.uid)) else: a.append(('class', klass.uid)) n_id = self.generate_id() return xmi_association(doc, self.generate_id, n_id, self.UUID_format(n_id), from_node.name, a[0][0], a[0][1], to_node.name, a[1][0], a[1][1], nav) # protected methods ######################################################## def _create_class_dom(self, node, doc): """create the xmi dom for a klass node, which may be a regular class, an interface or a signal """ interface = is_interface(node) if interface: el = xmi_interface(doc, node.uid, self.UUID_format(node.uid), node.name, is_final(node), is_abstract(node)) elif is_exception(node): el = xmi_signal(doc, node.uid, self.UUID_format(node.uid), node.name, is_final(node), is_abstract(node)) else: el = xmi_class(doc, node.uid, self.UUID_format(node.uid), node.name, is_final(node), is_abstract(node)) spec = None # are there specializations of this classes specializations = getattr(node, 'specializations', []) if specializations: if is_interface(node): pe = add_me_dependency(el, client=0) a_name = XMI_ABSTRACTION else: # delay attachment to be conform to the dtd spec = pe = doc.createElementNS(NO_NS, XMI_G_ELMT_SPEC) a_name = XMI_GENERALIZATION for p in specializations: add_idref(pe, a_name, self._get_x_ref(node, interface)) # are we a specialization of some classes if node.baseobjects: pe = doc.createElementNS(NO_NS, XMI_G_ELMT_GENE) el.appendChild(pe) for p in self.filter_nodes(node.baseobjects): add_idref(pe, XMI_GENERALIZATION, self._get_x_ref(p, 0, node)) # implemented interfaces for p in self.filter_nodes(node.implements): pe = add_me_dependency(el, client=1) add_idref(pe, XMI_ABSTRACTION, self._get_x_ref(p, 1, node)) # is there a delayed node to attach ? if spec is not None: el.appendChild(spec) return el def _get_x_ref(self, root, interface=None, dependency=None): """manage cross links between two objects (generalization, implements) root is the ancestor node. This function is called two time for each link: _ one with dependency=None _ one with dependency references the specialization object. We don't know the call order :( """ doc = self._doc if not hasattr(root, '_tmp_xmi_ref'): root._xmi_ref = {} root._last_xmi_ref, root._tmp_xmi_ref = [], [] if dependency is not None and root._tmp_xmi_ref: root._xmi_ref[dependency] = e = root._tmp_xmi_ref[-1] xmi_X_ref(doc, self._xref_tab[e][0], interface, None, dependency.uid) del root._tmp_xmi_ref[-1] return e elif dependency is None and root._last_xmi_ref: e = root._xmi_ref[root._last_xmi_ref[-1]] xmi_X_ref(doc, self._xref_tab[e][0], interface, root.uid, None) del root._last_xmi_ref[-1] return e # create dependency base_id = self.generate_id() if dependency: root._xmi_ref[dependency] = base_id root._last_xmi_ref.append(dependency) else: root._tmp_xmi_ref.append(base_id) if interface: xref = xmi_abstraction(doc, base_id, self.UUID_format(base_id)) # extension ext_id = self.generate_id() ext = xmi_stereotype(doc, base_id, ext_id) else: xref = xmi_generalization(doc, base_id, self.UUID_format(base_id)) ext = None if ext is not None: self._xref_tab[ext_id] = (ext, self._module) xref.appendChild(xmi_stereotype_ref(doc, ext_id)) xmi_X_ref(doc, xref, interface, root and root.uid, dependency and dependency.uid) self._xref_tab[base_id] = (xref, self._module) return base_id def UUID_format(self, id): """format XMI UUID """ return '%s-%x' % (self._uuid, 0x8000 - id)