*** empty log message ***

git-svn-id: file:///Users/arjan/backup/gaphor/trunk/gaphor@37 a8418922-720d-0410-834f-a69b97ada669
This commit is contained in:
Arjan Molenaar 2002-02-07 11:15:15 +00:00
parent b28d7bfba8
commit 4b063063d9
9 changed files with 457 additions and 76 deletions

50
doc/storage.txt Normal file
View File

@ -0,0 +1,50 @@
Saving and loading Gaphor diagrams
The idea is to keep the file format as simple and extensible as possible.
This is the format as used by Gaphor.
Everything interesting is between the `Gaphor' start and end tag.
The file is as flat as possible: UML elements (including Diagram) are at
toplevel, no nesting.
A UML element can have two tags: `Reference' and `Value'. Reference is used to
point to other UML elements, Value has a value inside (an integer or astring).
Diagram is a special case. Since this element contains a diagram canvas inside,
it may become pretty big (with lots of nested elements).
This is handled by the load and save function of the Diagram class.
All elements inside a canvas have a tag `Item'.
<?xml version="1.0" ?>
<Gaphor version="1.0">
<Model id="1">
<Reference name="ownedElement" refid="2"/>
<Reference name="ownedElement" refid="3"/>
<Reference name="ownedElement" refid="4"/>
</Model>
<Diagram id="2">
<Reference name="namespace" refid="1"/>
<Canvas extents="(9.0, 9.0, 189.0, 247.0)" grid_bg="0xFFFFFFFF"
grid_color="0x80ff" grid_int_x="10.0" grid_int_y="10.0"
grid_ofs_x="0.0" grid_ofs_y="0.0" snap_to_grid="0"
static_extents="0">
<CanvasItem affine="(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)" cid="0x8292114"
type="CanvasGroup">
<CanvasItem affine="(1.0, 0.0, 0.0, 1.0, 150.0, 50.0)" cid="0x8293e74"
height="78.0" subject="3" type="Actor" width="38.0"/>
<CanvasItem affine="(1.0, 0.0, 0.0, 1.0, 10.0, 10.0)" cid="0x82e7d74"
height="26.0" subject="5" type="Comment" width="100.0"/>
</CanvasItem>
</Canvas>
</Diagram>
<Actor id="3">
<Value name="name">
<![CDATA[Actor]]> </Value>
<Reference name="namespace" refid="1"/>
</Actor>
<UseCase id="4">
<Reference name="namespace" refid="1"/>
</UseCase>
<Comment id="5"/>
</Gaphor>

View File

@ -32,6 +32,14 @@ Geometry = types.ListType
FALSE = 0
TRUE = not FALSE
elements = { }
def lookup (id):
if elements.has_key(id):
return elements[id]
else:
return None
class Enumeration_:
'''The Enumerration class is an abstract class that can be used to create
enumerated types. One should inherit from Enumeration and define a variable
@ -105,7 +113,7 @@ class Element:
attributes and relations are defined by a <class>._attrdef structure.
A class does not need to define any local variables itself: Element will
retrieve all information from the _attrdef structure.
Element._hash is a table containing all assigned objects as weak references.
elements is a table containing all assigned objects as weak references.
This class has its own __del__() method. This means that it can not be freed
by the garbage collector. Instead you should call Element::unlink() to
remove all relationships with the element and then it will remove itself if
@ -120,37 +128,39 @@ object if references are lehd by the object on the undo stack.
_index = 1
_attrdef = { 'presentation': ( Sequence, types.ObjectType ),
'itemsOnUndoStack': (0, types.IntType ) }
_hash = { }
def __init__(self):
print "New object of type", self.__class__
#print "New object of type", self.__class__
self.__dict__['__id'] = Element._index
self.__dict__['__signals'] = [ ]
#Element._hash[Element._index] = weakref.ref (self)
Element._hash[Element._index] = self
elements[Element._index] = self
Element._index += 1
def unlink(self):
'''Remove all references to the object.'''
#print 'Element.unlink()'
#print 'Element.unlink():', self
self.emit("unlink")
if Element._hash.has_key(self.__dict__['__id']):
del Element._hash[self.__dict__['__id']]
if elements.has_key(self.__dict__['__id']):
del elements[self.__dict__['__id']]
for key in self.__dict__.keys():
# In case of a cyclic reference, we should check if the element
# not yet has been removed.
if self.__dict__.has_key (key) and \
key not in ( 'presentation', '__signals', '__id' ):
key not in ( 'presentation', 'itemsOnUndoStack', \
'__signals', '__id' ):
if isinstance (self.__dict__[key], Sequence):
# Remove each item in the sequence, then remove
# the sequence from __dict__.
for s in self.__dict__[key].list:
del self.__dict__[key][s]
list = self.__dict__[key].list
while len (list) > 0:
del self.__dict__[key][list[0]]
del self.__dict__[key]
else:
# do a 'del self.key'
if isinstance (self.__dict__[key], Element):
self.__delattr__(key)
#if isinstance (self.__dict__[key], Element):
#print '\tunlink:', key
self.__delattr__(key)
return self
# Hooks for presentation elements to add themselves:
@ -167,6 +177,9 @@ object if references are lehd by the object on the undo stack.
def undo_presentation (self, presentation):
if not presentation in self.presentation:
# Add myself to the 'elements' hash
if len (self.presentation) == 0:
elements[self.id] = self
self.presentation = presentation
self.itemsOnUndoStack -= 1
assert self.itemsOnUndoStack >= 0
@ -175,8 +188,14 @@ object if references are lehd by the object on the undo stack.
if presentation in self.presentation:
del self.presentation[presentation]
self.itemsOnUndoStack += 1
# Remove yourself from the 'elements' hash
if len (self.presentation) == 0:
if lookup (self.id):
del elements[self.id]
def remove_undoability (self):
if not self.__dict__.has_key ('itemsOnUndoStack'):
return
self.itemsOnUndoStack -= 1
assert self.itemsOnUndoStack >= 0
if len (self.presentation) == 0 and self.itemsOnUndoStack == 0:
@ -184,7 +203,7 @@ object if references are lehd by the object on the undo stack.
self.unlink()
def __get_attr_info(self, key, klass):
'''Find the record for 'key' in the <class>._attrdef map.'''
'''Find the record for 'key' in the <klass>._attrdef map.'''
done = [ ]
def real_get_attr_info(key, klass):
if klass in done:
@ -211,21 +230,23 @@ object if references are lehd by the object on the undo stack.
self.__dict__[key] = Sequence(self, type)
return self.__dict__[key]
def __del_seq_item(self, seq, val):
try:
index = seq.list.index(val)
del seq.list[index]
except ValueError:
pass
#def __del_seq_item(self, seq, val):
#try:
#index = seq.list.index(val)
#del seq.list[index]
# seq.list.remove (val)
#except ValueError:
# pass
def __getattr__(self, key):
#print 'Element.__getattr__(' + key + ')'
if key == 'id':
return self.__dict__['__id']
elif self.__dict__.has_key(key):
# Key is already in the object
return self.__dict__[key]
else:
#if key[0] != '_':
#print 'Unknown attr: Element.__getattr__(' + key + ')'
rec = self.__get_attr_info (key, self.__class__)
if rec[0] is Sequence:
# We do not have a sequence here... create it and return it.
@ -264,13 +285,16 @@ object if references are lehd by the object on the undo stack.
if rec[0] is not Sequence or xrec[0] is not Sequence:
if rec[0] is Sequence:
#print 'del-seq-item rec'
self.__del_seq_item(self.__dict__[key], xself)
#self.__del_seq_item(self.__dict__[key], xself)
if xself in self.__dict__[key].list:
self.__dict__[key].list.remove(xself)
elif self.__dict__.has_key(key):
#print 'del-item rec'
del self.__dict__[key]
if xrec[0] is Sequence:
#print 'del-seq-item xrec'
xself.__del_seq_item(xself.__dict__[rec[2]], self)
#xself.__del_seq_item(xself.__dict__[rec[2]], self)
xself.__dict__[rec[2]].list.remove (self)
elif xself.__dict__.has_key(rec[2]):
#print 'del-item xrec'
del xself.__dict__[rec[2]]
@ -297,15 +321,18 @@ object if references are lehd by the object on the undo stack.
if not self.__dict__.has_key(key):
return
xval = self.__dict__[key]
del self.__dict__[key]
if len(rec) > 2: # Bi-directional relationship
xrec = xval.__get_attr_info (rec[2], rec[1])
if xrec[0] is Sequence:
xval.__del_seq_item(xval.__dict__[rec[2]], self)
#xval.__del_seq_item(xval.__dict__[rec[2]], self)
#xval.__dict__[rec[2]].list.remove (self)
# Handle it via sequence_remove()
del xval.__dict__[rec[2]][self]
else:
del xval.__dict__[rec[2]]
xval.emit(rec[2])
self.emit (key)
del self.__dict__[key]
self.emit (key)
xval.emit(rec[2])
def sequence_remove(self, seq, obj):
'''Remove an entry. Should only be called by Sequence's implementation.
@ -315,20 +342,24 @@ object if references are lehd by the object on the undo stack.
if self.__dict__[key] is seq:
break
#print 'Element.sequence_remove', key
seq_len = len (seq)
rec = self.__get_attr_info (key, self.__class__)
if rec[0] is not Sequence:
raise AttributeError, 'Element: This should be called from Sequence'
seq.list.remove(obj)
if len(rec) > 2: # Bi-directional relationship
xrec = obj.__get_attr_info (rec[2], rec[1])
xrec = obj.__get_attr_info (rec[2], obj.__class__) #rec[1])
if xrec[0] is Sequence:
obj.__del_seq_item(obj.__dict__[rec[2]], self)
#obj.__del_seq_item(obj.__dict__[rec[2]], self)
#print 'sequence_remove: Sequence'
obj.__dict__[rec[2]].list.remove (self)
else:
try:
del obj.__dict__[rec[2]]
except Exception, e:
print 'ERROR: (Element.sequence_remove)', e
#try:
#print 'sequence_remove: item'
del obj.__dict__[rec[2]]
#except Exception, e:
# print 'ERROR: (Element.sequence_remove)', e
assert len (seq) == seq_len - 1
# Functions used by the signal functions
def connect (self, signal_func, *data):
self.__dict__['__signals'].append ((signal_func,) + data)
@ -338,24 +369,81 @@ object if references are lehd by the object on the undo stack.
self.__dict__['__signals'])
def emit (self, key):
if not self.__dict__.has_key ('__signals'):
print 'No __signals attribute in object', self
return
print 'emit', self, key
#if not self.__dict__.has_key ('__signals'):
# print 'No __signals attribute in object', self
# return
for signal in self.__dict__['__signals']:
signal_func = signal[0]
data = signal[1:]
#print 'signal:', signal_func, 'data:', data
signal_func (key, *data)
#def update_model():
# '''Do a garbage collection on the hash table, also removing
# weak references that do not point to valid objects.'''
# gc.collect()
# for k in Element._hash.keys():
# if Element._hash[k]() is None:
# del Element._hash[k]
def save(self, document, parent):
def save_children (obj):
if isinstance (obj, Element):
#subnode = document.createElement (key)
subnode = document.createElement ('Reference')
node.appendChild (subnode)
subnode.setAttribute ('name', key)
subnode.setAttribute ('refid', str(obj.__dict__['__id']))
elif isinstance (obj, types.IntType) or \
isinstance (obj, types.LongType) or \
isinstance (obj, types.FloatType):
subnode = document.createElement ('Value')
node.appendChild (subnode)
subnode.setAttribute ('name', key)
text = document.createTextNode (str(obj))
subnode.appendChild (text)
elif isinstance (obj, types.StringType):
subnode = document.createElement ('Value')
node.appendChild (subnode)
subnode.setAttribute ('name', key)
cdata = document.createCDATASection (str(obj))
subnode.appendChild (cdata)
#Element_hash_gc = update_model
node = document.createElement (self.__class__.__name__)
parent.appendChild (node)
node.setAttribute ('id', str (self.__dict__['__id']))
for key in self.__dict__.keys():
if key not in ( 'presentation', 'itemsOnUndoStack', \
'__signals', '__id' ):
obj = self.__dict__[key]
if isinstance (obj, Sequence):
for item in obj.list:
save_children (item)
else:
save_children (obj)
return node
def load(self, node):
for child in node.childNodes:
if child.tagName == 'Reference':
name = child.getAttribute ('name')
refid = int (child.getAttribute ('refid'))
refelement = lookup (refid)
attr_info = self.__get_attr_info (name, self.__class__)
if not isinstance (refelement, attr_info[1]):
raise ValueError, 'Referenced item is of the wrong type'
if attr_info[0] is Sequence:
self.__ensure_seq (name, attr_info[1]).list.append (refelement)
else:
self.__dict__[name] = refelement
elif child.tagName == 'Value':
name = child.getAttribute ('name')
subchild = child.firstChild
attr_info = self.__get_attr_info (name, self.__class__)
if issubclass (attr_info[1], types.IntType) or \
issubclass (attr_info[1], types.LongType):
self.__dict__[name] = int (subchild.data)
elif issubclass (attr_info[1], types.FloatType):
self.__dict__[name] = float (subchild.data)
else:
self.__dict__[name] = subchild.data
###################################
# Testing
if __name__ == '__main__':
print '\n============== Starting Element tests... ============\n'
@ -389,7 +477,7 @@ if __name__ == '__main__':
a.unlink()
del a
assert len (Element._hash) == 0
assert len (elements) == 0
print '\tOK ==='
@ -421,7 +509,7 @@ if __name__ == '__main__':
a.unlink()
assert len (Element._hash) == 0
assert len (elements) == 0
print '\tOK ==='
@ -468,7 +556,7 @@ if __name__ == '__main__':
a.unlink()
b.unlink()
assert len (Element._hash) == 0
assert len (elements) == 0
print '\tOK ==='
@ -496,6 +584,18 @@ if __name__ == '__main__':
assert a.ref is a
assert a.seq.list == [ a ]
b = A()
a.seq = b
assert b.ref is a
assert a.seq.list == [ a, b ]
del a.seq[a]
assert a.ref is None
assert b.ref is a
assert a.seq.list == [ b ]
b.unlink()
a.unlink()
a = A()
b = A()
@ -539,8 +639,8 @@ if __name__ == '__main__':
a.unlink()
b.unlink()
#print Element._hash
assert len (Element._hash) == 0
#print elements
assert len (elements) == 0
print '\tOK ==='
@ -603,7 +703,7 @@ if __name__ == '__main__':
a.unlink()
b.unlink()
assert len (Element._hash) == 0
assert len (elements) == 0
print '\tOK ==='
@ -660,7 +760,7 @@ if __name__ == '__main__':
b.unlink()
del b
assert len (Element._hash) == 0
assert len (elements) == 0
print '\tOK ==='
@ -674,14 +774,14 @@ if __name__ == '__main__':
a = A()
b = A()
assert len (Element._hash) == 2
assert len (elements) == 2
a.rel = b
a.unlink()
b.unlink()
assert len (Element._hash) == 0
assert len (elements) == 0
print '\tOK ==='

View File

@ -9,4 +9,4 @@ ModelElements.py: $(GEN_UML) $(METAMODEL_XMI)
grep -v "PresentationElement" genModelElements.py > ModelElements.py
rm -f genModelElements.py
EXTRA_DIST=Element.py ModelElements.py __init__.py
EXTRA_DIST=Element.py ModelElements.py management.py __init__.py

View File

@ -3,6 +3,7 @@
#
from Element import *
from ModelElements import *
from management import *
Attribute._attrdef['rawAttribute'] = ( '', String )
Operation._attrdef['rawOperation'] = ( '', String )

128
gaphor/UML/management.py Normal file
View File

@ -0,0 +1,128 @@
# vim: sw=4
import UML, diagram
from xmllib import XMLParser
from xml.dom.minidom import Document
# Somehow I did not manage it to use the minidom/SAX stuff to create a
# decent parser, do I use this depricated (but workin ;) peace of code...
# The GaphorParser reads a Gaphor file and creates a DOM representation of
# the file.
class GaphorParser (XMLParser):
'''GaphorParser
The GaphorParser examines an inut strream and creates a Document object
using the elements found in the XML file. The only restruction that we
test is the Gaphor tag and the version (currently only 1.0)'''
def __init__ (self, **kw):
self.doit = 0
self.doc = Document()
self.elements = { 'Gaphor': (self.start_Gaphor, self.end_Gaphor) }
self.__stack = [ ]
apply(XMLParser.__init__, (self,), kw)
def syntax_error(self, message):
raise IOError, "XML document contains syntax errors: " + message
def start_Gaphor (self, attrs):
if attrs['version'] != '1.0':
raise Exception, 'Wrong version of Gaphor (' + \
attrs['version'] + ')'
else:
node = self.doc.createElement('Gaphor')
self.doc.appendChild (node)
self.__stack.append (node)
self.doit += 1
def end_Gaphor (self):
self.doit -= 1
def unknown_starttag(self, tag, attrs):
if self.doit:
node = self.doc.createElement(tag)
self.__stack[-1].appendChild (node)
self.__stack.append (node)
for key in attrs.keys():
node.setAttribute (key, attrs[key])
def unknown_endtag(self, tag):
self.__stack = self.__stack[:-1]
def handle_cdata(self, tag):
cdata = self.doc.createCDATASection (tag)
self.__stack[-1].appendChild (cdata)
def unknown_entityref(self, ref):
raise ValueError, tag + " not supported."
def unknown_charref(self, ref):
raise ValueError, tag + " not supported."
def save (filename=None):
document = Document()
rootnode = document.createElement ('Gaphor')
document.appendChild (rootnode)
rootnode.setAttribute ('version', '1.0')
for e in UML.elements.values():
print 'Saving object', e
e.save(document, rootnode)
if not filename:
print document.toxml(indent=' ', newl='\n')
else:
file = open (filename, 'w')
if not file:
raise IOError, 'Could not open file `%s\'' % (filename)
document.writexml (file, indent='', addindent=' ', newl='\n')
file.close()
def load (filename):
'''Load a file and create a model if possible.
Exceptions: IOError, ValueError.'''
parser = GaphorParser()
f = open (filename, 'r')
while 1:
data = f.read(512)
parser.feed (data)
if len(data) != 512:
break;
parser.close()
f.close()
# Now iterate over the tree and create every element in the UML.elements
# table.
rootnode = parser.doc.firstChild
for node in rootnode.childNodes:
try:
if node.tagName == 'Diagram':
cls = getattr (diagram, node.tagName)
else:
cls = getattr (UML, node.tagName)
except:
raise ValueError, 'Invalid field in Gaphor file: ' + node.tagName
id = int (node.getAttribute('id'))
old_index = UML.Element._index
UML.Element._index = id
cls()
if old_index > id:
UML.Element._index = old_index
#print node.tagName, node.getAttribute('id')
# Second step: call Element.load() for every object in the element hash.
# We also provide the XML node, so it can recreate it's state
for node in rootnode.childNodes:
id = int (node.getAttribute('id'))
element = UML.lookup (id)
assert element != None
element.load (node)
def flush():
'''Flush all elements in the UML.elements'''
while 1:
try:
(key, value) = UML.elements.popitem()
except KeyError:
break;
value.unlink()
UML.elements.clear()

View File

@ -10,9 +10,12 @@ import diacanvas
import UML
import tree
uc = getattr (UML, 'UseCase')
print 'getattr (UML, "UseCase") ->', uc
def mainquit(*args):
for k in UML.Element._hash.keys():
print "Element", k, ":", UML.Element._hash[k].__dict__
for k in UML.elements.keys():
print "Element", k, ":", UML.elements[k].__dict__
print "Forcing Garbage collection:"
gtk.main_quit()
@ -30,21 +33,21 @@ treemodel = tree.NamespaceModel(model)
#item.move (30, 50)
#item = canvas.root.add (diagram.Actor)
#item.move (150, 50)
item = dia.create_item (diagram.UseCase, (50, 150))
usecase = item.get_subject()
#dia.create_item (diagram.UseCase, (50, 200), subject=usecase)
item = dia.create_item (diagram.Actor, (150, 50))
actor = item.get_subject()
actor.name = "Actor"
item = dia.create_item (diagram.UseCase, (50, 150))
usecase = item.get_subject()
dia.create_item (diagram.UseCase, (50, 200), subject=usecase)
item = dia.create_item (diagram.Comment, (10,10))
comment = item.get_subject()
del item
del item#, actor, usecase, comment
#print "Comment.presentation:", comment.presentation.list
#print "Actor.presentation:", actor.presentation.list
print "Comment.presentation:", comment.presentation.list
print "Actor.presentation:", actor.presentation.list
print "UseCase.presentation:", usecase.presentation.list
#view = diacanvas.CanvasView().set_canvas (dia.canvas)
#display_diagram (dia)
@ -61,16 +64,15 @@ treemodel.dump()
print 'Going into main'
gtk.main()
UML.save('x.xml')
del win
treemodel.dump()
#print "Comment.ann.Elem.:", comment.annotatedElement.list
#print "Actor.comment:", actor.comment.list
#print "UseCase.comment:", usecase.comment.list
print "Comment.presentation:", comment.presentation.list
print "Actor.presentation:", actor.presentation.list
print "UseCase.presentation:", usecase.presentation.list
#print "Comment.presentation:", comment.presentation.list
#print "Actor.presentation:", actor.presentation.list
#print "UseCase.presentation:", usecase.presentation.list
print "removing diagram..."
dia.unlink()
del dia
@ -78,17 +80,15 @@ del dia
print "Comment.presentation:", comment.presentation.list
print "Actor.presentation:", actor.presentation.list
print "UseCase.presentation:", usecase.presentation.list
actor.unlink()
del actor
usecase.unlink()
del usecase
comment.unlink()
del comment
#del dia
print "Garbage collection after gtk.main() has finished:"
UML.flush()
print "Garbage collection after gtk.main() has finished (should be empty):",
#UML.update_model()
for k in UML.Element._hash.keys():
print "Element", k, ":", UML.Element._hash[k].__dict__
for k in UML.elements.keys():
print "Element", k, ":", UML.elements[k].__dict__
print "Program ended normally..."

63
tests/diagram-destroy.py Normal file
View File

@ -0,0 +1,63 @@
#
# Test the behavior of a UML tree with a Diagram as leaf. The whole tree
# should be freed...
#
import UML
import diagram as dia
import gc
model = UML.Model()
model.name = "MyModel"
package = UML.Package()
package.name = "Package"
model.ownedElement = package
assert len(model.ownedElement.list) == 1
assert model.ownedElement.list[0] is package
assert package.namespace is model
actor = UML.Actor()
actor.namespace = package
assert len(package.ownedElement.list) == 1
assert package.ownedElement.list[0] is actor
assert actor.namespace is package
usecase = UML.UseCase()
usecase.namespace = package
assert len(package.ownedElement.list) == 2
assert package.ownedElement.list[0] is actor
assert package.ownedElement.list[1] is usecase
assert usecase.namespace is package
diagram = dia.Diagram()
diagram.namespace = package
assert len(package.ownedElement.list) == 3
assert package.ownedElement.list[0] is actor
assert package.ownedElement.list[1] is usecase
assert package.ownedElement.list[2] is diagram
assert diagram.namespace is package
diagram.create_item (dia.Actor, pos=(0, 0), subject=actor)
diagram.create_item (dia.UseCase, pos=(100, 100), subject=usecase)
#dia.destroy_diagrams()
model.unlink()
del model
usecase.unlink()
del usecase
actor.unlink()
del actor
package.unlink()
del package
diagram.unlink()
del diagram
gc.collect()
gc.set_debug (gc.DEBUG_LEAK)
print "Uncollectable objects found:", gc.garbage

31
tests/test-treemodel.py Normal file
View File

@ -0,0 +1,31 @@
import UML, gtk
from tree.namespace import *
import CreateModel
window = gtk.Window()
window.connect('destroy', lambda win: gtk.main_quit())
window.set_title('TreeView test')
window.set_default_size(250, 400)
scrolled_window = gtk.ScrolledWindow()
scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
window.add(scrolled_window)
tree_model = NamespaceModel(CreateModel.model)
tree_view = gtk.TreeView(tree_model)
cell = gtk.CellRendererText()
# the text in the column comes from column 0
column = gtk.TreeViewColumn('', cell, text=0)
tree_view.append_column(column)
scrolled_window.add(tree_view)
window.show_all()
tree_model.dump()
gtk.main()
tree_model.dump()

8
tests/test-xml.py Normal file
View File

@ -0,0 +1,8 @@
#
# A simple parser
#
import UML
UML.load ('../gaphor/x.xml')
UML.save ('aa.xml')