root/gaphor/tags/gaphor-0.3.0/utils/genUML2.py

Revision 265, 15.9 kB (checked in by arjanmol, 5 years ago)

*** empty log message ***

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 #!/usr/bin/env python
2 # vim:sw=4:et
3 #
4 # Create a UML 2.0 datamodel from the Gaphor 0.2.0 model file.
5 #
6 # To do this we do the following:
7 # 1. read the model file with the gaphor parser
8 # 2. Create a object herarcy by ordering elements based on generalizations
9
10 # Recreate the model using some very dynamic class, so we can set all
11 # attributes and traverse them to generate the data model.
12
13 from gaphor.parser import parse, base, element, canvas, canvasitem
14 import sys, string, operator
15 import override
16
17 header = """# This file is generated by genUML2.py. DO NOT EDIT!
18
19 from properties import association, attribute, enumeration, derivedunion, redefine
20 """
21
22 # Make getitem behave more politely
23 base.__real_getitem__ = base.__getitem__
24
25 def base__getitem__(self, key):
26     try:
27         return self.__real_getitem__(key)
28     except KeyError:
29         return None
30
31 base.__getitem__ = base__getitem__
32
33 # redefine 'bool' for Python version < 2.3
34 if map(int, sys.version[:3].split('.')) < [2, 3]:
35     header = header + "bool = int\n"
36
37 def msg(s):
38     sys.stderr.write('  ')
39     sys.stderr.write(s)
40     sys.stderr.write('\n')
41     sys.stderr.flush()
42
43 class Writer:
44
45     def __init__(self, filename, overridesfile=None):
46         self.overrides = overridesfile and override.Overrides(overridesfile) or None
47         if filename:
48             self.out = open(filename, 'w')
49         else:
50             self.out = sys.stdout
51
52     def write(self, data):
53         self.out.write(data)
54
55     def close(self):
56         self.out.close()
57
58     def write_classdef(self, clazz):
59         """Write a class definition (class xx(x): pass).
60         First the parent classes are examined. After that its own definition
61         is written. It is ensured that class definitions are only written
62         once.
63         """
64         if not clazz.written:
65             s = ''
66             for g in clazz.generalization:
67                 self.write_classdef(g)
68                 if s: s += ', '
69                 s = s + g['name']
70             if not s: s = 'object'
71             if not self.overrides.write_override(self, clazz['name']):
72                 self.write('class %s(%s): pass\n' % (clazz['name'], s))
73         clazz.written = True
74
75     def write_property(self, full_name, value):
76         """Write a property to the file. If the property is overridden, use the
77         overridden value. full_name should be like Class.attribute. value is
78         free format text.
79         """
80         if not self.overrides.write_override(self, full_name):
81             self.write('%s = %s\n' % (full_name, value))
82
83     def write_attribute(self, a, enumerations={}):
84         """Write a definition for attribute a. Enumerations may be a dict
85         of enumerations, indexed by ID. These are used to identify enums.
86         """
87         params = { }
88         type = a.typeValue and a.typeValue.get('value')
89         if type.lower() == 'boolean':
90             type = 'bool'
91         elif type.lower() in ('integer', 'unlimitednatural'):
92             type = 'int'
93         elif type.lower() == 'string':
94             # Change to basestr for Python 2.3
95             type = 'str'
96             #type = '(str, unicode)'
97
98         default = a.defaultValue and a.defaultValue.value
99         # Make sure types are represented the Python way:
100         if default and default.lower() in ('true', 'false'):
101             default = default.title() # True or False...
102         if default is not None:
103             params['default'] = str(default)
104
105         lower = a.lowerValue and a.lowerValue.value
106         if lower and lower != '0':
107             params['lower'] = lower
108         upper = a.upperValue and a.upperValue.value
109         if upper and upper != '1':
110             params['upper'] = upper
111
112         #kind, derived, a.name, type, default, lower, upper = parse_attribute(a)
113
114         full_name = "%s.%s" % (a.class_name, a.name)
115         if self.overrides.has_override(full_name):
116             self.overrides.write_override(self, full_name)
117         elif int(a.isDerived or 0):
118             msg('Ignoring derived attribute %s.%s: no definition' % (a.class_name, a.name))
119         elif type.endswith('Kind'):
120             e = filter(lambda e: e['name'] == type, enumerations.values())[0]
121             self.write_property("%s.%s" % (a.class_name, a.name),
122                                 "enumeration('%s', %s, '%s')" % (a.name, e.enumerates, default or e.enumerates[0]))
123         else:
124             if params:
125                 attribute = "attribute('%s', %s, %s)" % (a.name, type, ', '.join(map('='.join, params.items())))
126             else:
127                 attribute = "attribute('%s', %s)" % (a.name, type)
128             self.write_property("%s.%s" % (a.class_name, a.name), attribute)
129
130     def write_association(self, head, tail):
131         """Write an association for head. False is returned if the association
132         is derived.
133         The head association end is enriched with the following attributes:
134             derived - association is a derived union or not
135             name - name of the association end (name of head is found on tail)
136             class_name - name of the class this association belongs to
137             opposite_class_name - name of the class at the other end of the assoc.
138             lower - lower multiplicity
139             upper - upper multiplicity
140             subsets - derived unions that use the association
141             redefines - redefines existing associations
142         Returns True if the association is written. False is returned if
143         the association should be handled by write_derivedunion() or
144         write_redefine().
145         """
146         navigable = head.get('class_')
147         if not navigable:
148             # from this side, the association is not navigable
149             return True
150         try:
151             #derived, name = parse_association_name(head['name'])
152             name = head.name
153             derived = int(head.isDerived or 0)
154         except KeyError:
155             msg('ERROR! no name, but navigable: %s (%s.%s)' %
156                 (head.id, head.class_name, head.name))
157             return True
158
159         #lower, upper, subsets, redefines = parse_association_multiplicity(head.lowerValue)
160         #print head.id, head.lowerValue
161         upper = head.upperValue and head.upperValue.value or '*'
162         lower = head.lowerValue and head.lowerValue.value or upper
163         if lower == '*':
164             lower = 0
165         subsets, redefines = parse_association_tags(head.taggedValue and head.taggedValue.value or '')
166
167         # Add the values found. These are used later to generate derived unions.
168         head.derived = derived
169         #head.name = name
170         head.class_name = head.class_['name']
171         head.opposite_class_name = head.type['name']
172         head.lower = lower
173         head.upper = upper
174         head.subsets = subsets
175         head.redefines = redefines
176
177         # Derived unions and redefines are handled separately
178         if derived or redefines:
179             return False
180
181         a = "association('%s', %s" % (name, head.opposite_class_name)
182         if lower not in ('0', 0):
183             a += ', lower=%s' % lower
184         if upper != '*':
185             a += ', upper=%s' % upper
186         if tail.get('aggregation') == 'composite':
187             a += ', composite=True'
188
189         # Add the opposite property if the head itself is navigable:
190         navigable = tail.get('class_')
191         if navigable:
192             try:
193                 #o_derived, o_name = parse_association_name(tail['name'])
194                 o_name = tail.name
195                 o_derived = int(tail.isDerived or 0)
196             except KeyError:
197                 msg('ERROR! no name, but navigable: %s (%s.%s)' %
198                     (tail.id, tail.class_name, tail.name))
199             else:
200                 assert not (derived and not o_derived), 'One end is derived, the other end not ???'
201                 a += ", opposite='%s'" % o_name
202
203         self.write_property("%s.%s" % (head.class_name, name), a + ')')
204         return True
205
206     def write_derivedunion(self, d):
207         """Write a derived union. If there are no subsets a warning
208         is issued. The derivedunion is still created though.
209         Derived unions may be created for associations that were returned
210         False by write_association()."""
211         subs = ''
212         for u in d.union:
213             if u.derived and not u.written:
214                 self.write_derivedunion(u)
215             if subs: subs += ', '
216             subs += '%s.%s' % (u.class_name, u.name)
217         if subs:
218             self.write_property("%s.%s" % (d.class_name, d.name),
219                                 "derivedunion('%s', %s, %s, %s)" % (d.name, d.lower, d.upper == '*' and "'*'" or d.upper, subs))
220         else:
221             if not self.overrides.has_override('%s.%s' % (d.class_name, d.name)):
222                 msg('no subsets for derived union: %s.%s[%s..%s]' % (d.class_name, d.name, d.lower, d.upper))
223             self.write_property("%s.%s" % (d.class_name, d.name),
224                                 "derivedunion('%s', %s, %s)" % (d.name, d.lower, d.upper == '*' and "'*'" or d.upper))
225         d.written = True
226
227     def write_redefine(self, r):
228         """Redefines may be created for associations that were returned
229         False by write_association()."""
230         self.write_property("%s.%s" % (r.class_name, r.name),
231                             "redefine('%s', %s, %s)" % (r.name, r.opposite_class_name, r.redefines))
232
233
234 def parse_association_name(name):
235     # First remove spaces
236     name = name.replace(' ','')
237     derived = False
238     # Check if this is a derived union
239     while name and not name[0].isalpha():
240         if name[0] == '/':
241             derived = True
242         name = name[1:]
243     return derived, name
244
245 def parse_association_multiplicity(mult):
246     subsets = []
247     redefines = None
248     tag = None
249     if '{' in mult:
250         # we have tagged values
251         mult, tag = map(string.strip, mult.split('{'))
252         if tag[-1] == '}':
253             tag = tag[:-1]
254     else:
255         mult = mult.strip()
256    
257     mult = mult.split('.')
258     lower = mult[0]
259     upper = mult[-1]
260     if lower == '*':
261         lower = 0
262     #if upper == '*':
263     #    upper = "'*'"
264
265     if tag and tag.find('subsets') != -1:
266         # find the text after 'subsets':
267         subsets = tag[tag.find('subsets') + len('subsets'):]
268         # remove all whitespaces and stuff
269         subsets = subsets.replace(' ', '').replace('\n', '').replace('\r', '')
270         subsets = subsets.split(',')
271     if tag and tag.find('redefines') != -1:
272         # find the text after 'redefines':
273         redefines = tag[tag.find('redefines') + len('redefines'):]
274         # remove all whitespaces and stuff
275         redefines = redefines.replace(' ', '').replace('\n', '').replace('\r', '')
276         l = redefines.split(',')
277         assert len(l) == 1
278         redefines = l[0]
279
280     return lower, upper, subsets, redefines
281
282 def parse_association_tags(tag):
283     subsets = []
284     redefines = None
285
286     if tag and tag.find('subsets') != -1:
287         # find the text after 'subsets':
288         subsets = tag[tag.find('subsets') + len('subsets'):]
289         # remove all whitespaces and stuff
290         subsets = subsets.replace(' ', '').replace('\n', '').replace('\r', '')
291         subsets = subsets.split(',')
292     if tag and tag.find('redefines') != -1:
293         # find the text after 'redefines':
294         redefines = tag[tag.find('redefines') + len('redefines'):]
295         # remove all whitespaces and stuff
296         redefines = redefines.replace(' ', '').replace('\n', '').replace('\r', '')
297         l = redefines.split(',')
298         assert len(l) == 1
299         redefines = l[0]
300
301     return subsets, redefines
302
303 def generate(filename, outfile=None, overridesfile=None):
304     # parse the file
305     all_elements = parse(filename)
306
307     def unref(val, attr):
308         """Resolve references.
309         """
310         try:
311             refs = val.references[attr]
312         except KeyError:
313             val.references[attr] = None
314             return
315
316         if type(refs) is type([]):
317             unrefs = []
318             for r in refs:
319                 unrefs.append(all_elements[r])
320             val.references[attr] = unrefs
321         else:
322             val.references[attr] = all_elements[refs]
323
324     writer = Writer(outfile, overridesfile)
325
326     # extract usable elements from all_elements. Some elements are given
327     # some extra attributes.
328     classes = { }
329     enumerations = { }
330     generalizations = { }
331     associations = { }
332     properties = { }
333     for key, val in all_elements.items():
334         # Find classes, *Kind (enumerations) are given special treatment
335         if isinstance(val, element):
336             if val.type == 'Class' and val.get('name'):
337                 if val['name'].endswith('Kind'):
338                     enumerations[key] = val
339                 else:
340                     classes[key] = val
341                     # Add extra properties for easy code generation:
342                     val.specialization = []
343                     val.generalization = []
344                     val.written = False
345             elif val.type == 'Generalization':
346                 generalizations[key] = val
347             elif val.type == 'Association':
348                 associations[key] = val
349             elif val.type == 'Property':
350                 properties[key] = val
351                 unref(val, 'typeValue')
352                 unref(val, 'defaultValue')
353                 unref(val, 'lowerValue')
354                 unref(val, 'upperValue')
355                 unref(val, 'taggedValue')
356
357     # find inheritance relationships
358     for g in generalizations.values():
359         #assert g.specific and g.general
360         specific = g['specific']
361         general = g['general']
362         classes[specific].generalization.append(classes[general])
363         classes[general].specialization.append(classes[specific])
364
365     # add values to enumerations:
366     for e in enumerations.values():
367         values = []
368         for key in e['ownedAttribute']:
369             values.append(str(properties[key]['name']))
370         e.enumerates = tuple(values)
371
372     # create file header
373     writer.write(header)
374
375     # create class definitions
376     for c in classes.values():
377         writer.write_classdef(c)
378
379     # create attributes and enumerations
380     for c in classes.values():
381         for p in c.get('ownedAttribute') or []:
382             a = properties.get(p)
383             if a:
384                 # set class_name, since write_attribute depends on it
385                 a.class_name = c['name']
386                 if not a.get('association'):
387                     writer.write_attribute(a, enumerations)
388
389     # create associations, derivedunions are held back
390     derivedunions = { } # indexed by name in stead of id
391     redefines = [ ]
392     for a in associations.values():
393         ends = []
394         for end in a['memberEnd']:
395             end = properties[end]
396             end.type = classes[end['type']]
397             end.class_ = end.get('class_') and classes[end['class_']] or None
398             #assert end.type is not end.class_
399             #if not end.get('lowerValue'):
400                 #end.lowerValue = end.lowerValue.get('value') or ''
401             #else:
402                 #end.lowerValue = ''
403             ends.append(end)
404
405         for e1, e2 in ((ends[0], ends[1]), (ends[1], ends[0])):
406             if not writer.write_association(e1, e2):
407                 # At this point the association is parsed, but not written.
408                 # assure that derived unions do not get overwritten
409                 if e1.redefines:
410                     redefines.append(e1)
411                 else:
412                     assert not derivedunions.get(e1.name)
413                     derivedunions[e1.name] = e1
414                     e1.union = [ ]
415                     e1.written = False
416
417
418     # create derived unions, first link the association ends to the d
419     for a in [v for v in properties.values() if v.subsets]:
420         for s in a.subsets or ():
421             try:
422                 derivedunions[s].union.append(a)
423             except KeyError:
424                 msg('Not a derived union: %s.%s' % (a.class_name, s))
425
426     for d in derivedunions.values():
427         writer.write_derivedunion(d)
428
429     for r in redefines or ():
430         msg('redefining %s -> %s.%s' % (r.redefines, r.class_name, r.name))
431         writer.write_redefine(r)
432
433     writer.close()
434
435 if __name__ == '__main__':
436<