root/gaphor/tags/gaphor-0.3.0/uml2/properties.py

Revision 197, 12.8 kB (checked in by arjanmol, 6 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 """Properties used to create the UML 2.0 data model.
4
5 The logic for creating and destroying connections between UML objects is
6 implemented in Python property classes. These classes are simply instantiated
7 like this:
8     class Class(Element): pass
9     class Comment(Element): pass
10     Class.ownedComment = association('ownedComment', Comment,
11                                      0, '*', 'annotatedElement')
12     Comment.annotatedElement = association('annotatedElement', Element,
13                                            0, '*', 'ownedComment')
14
15 Same for attributes and enumerations.
16
17 Each property type (association, attribute and enumeration) has three specific
18 methods:
19     _get():           return the value
20     _set(value):      set the value or add it to a list
21     _del(value=None): delete the value. 'value' is used to tell which value
22                       is to be removed (in case of associations with
23                       multiplicity > 1).
24     load(value):      load 'value' as the current value for this property
25     save(save_func):  send the value of the property to save_func(name, value)
26     unlink():         remove references to other elements.
27 """
28
29 from collection import Collection
30 import operator
31
32 #infinite = 100000
33
34 class umlproperty(object):
35     """Superclass for attribute, enumeration and association.
36     The subclasses should define a 'name' attribute that contains the name
37     of the property. Derived properties (derivedunion and redefine) can be
38     connected, they will be notified when the value changes.
39     """
40
41     def add_deriviate(self, deriviate):
42         try:
43             self.deriviates.append(deriviate)
44         except AttributeError:
45             self.deriviates = [ deriviate ]
46
47     def __get__(self, obj, clazz=None):
48         if not obj:
49             return self
50         return self._get(obj)
51
52     def __set__(self, obj, value):
53         self._set(obj, value)
54
55     def __delete__(self, obj, value=None):
56         self._del(obj, value)
57
58     def load(self, obj, value):
59         self._set(obj, value)
60
61     def save(self, obj, save_func):
62         if hasattr(obj, '_' + self.name):
63             save_func(self.name, self._get(obj))
64
65     def unlink(self, obj):
66         if hasattr(obj, '_' + self.name):
67             self.__delete__(obj)
68
69     def notify(self, obj):
70         """Notify obj that the property's value has been changed.
71         Deriviates are also triggered to send a notify signal."""
72         try:
73             obj.notify(self.name)
74         except:
75             pass
76
77         # we need to check if a deriviate is part of the current object:
78         # TODO: can we do this faster?
79         mro = obj.__class__.__mro__
80         try:
81             for d in self.deriviates:
82                 for c in mro:
83                     if d in c.__dict__.values():
84                         d.notify(obj)
85         except AttributeError:
86             pass # no derivedunion or redefine attribute
87
88
89 class attribute(umlproperty):
90     """Attribute.
91     Element.attr = attribute('attr', types.StringType, '')"""
92
93     # TODO: check if lower and upper are actually needed for attributes
94     def __init__(self, name, type, default, lower=0, upper=1):
95         self.name = name
96         self.type = type
97         self.default = default
98         self.lower = lower
99         self.upper = upper
100        
101     def load(self, obj, value):
102         if not isinstance(value, self.type):
103             raise AttributeError, 'Value should be of type %s' % self.type.__name__
104         setattr(obj, '_' + self.name, value)
105
106     def __str__(self):
107         if self.lower == self.upper:
108             return '<attribute %s: %s[%s] = %s>' % (self.name, self.type, self.lower, self.default)
109         else:
110             return '<attribute %s: %s[%s..%s] = %s>' % (self.name, self.type, self.lower, self.upper, self.default)
111
112     def _get(self, obj):
113         try:
114             return getattr(obj, '_' + self.name)
115         except AttributeError:
116             return self.default
117
118     def _set(self, obj, value):
119         if not isinstance(value, self.type):
120             raise AttributeError, 'Value should be of type %s' % self.type.__name__
121         if value == self.default:
122             delattr(obj, '_' + self.name)
123         else:
124             setattr(obj, '_' + self.name, value)
125         self.notify(obj)
126
127     def _del(self, obj, value=None):
128         delattr(obj, '_' + self.name)
129         self.notify(obj)
130
131
132 class enumeration(umlproperty):
133     """Enumeration.
134     Element.enum = enumeration('enum', ('one', 'two', 'three'), 'one')"""
135
136     def __init__(self, name, values, default):
137         self.name = name
138         self.values = values
139         self.default = default
140
141     def __str__(self):
142         return '<enumeration %s: %s = %s>' % (self.name, self.values, self.default)
143
144     def _get(self, obj):
145         try:
146             return getattr(obj, '_' + self.name)
147         except AttributeError:
148             return self.default
149
150     def load(self, obj, value):
151         if not value in self.values:
152             raise AttributeError, 'Value should be in %s' % str(self.values)
153         setattr(obj, '_' + self.name, value)
154
155     def _set(self, obj, value):
156         if not value in self.values:
157             raise AttributeError, 'Value should be in %s' % str(self.values)
158         if value != self._get(obj):
159             if value == self.default:
160                 delattr(obj, '_' + self.name)
161             else:
162                 setattr(obj, '_' + self.name, value)
163             self.notify(obj)
164
165     def _del(self, obj, value=None):
166         delattr(obj, '_' + self.name)
167         self.notify(obj)
168
169
170 class association(umlproperty):
171     """Association, both uni- and bi-directional.
172     Element.assoc = association('assoc', Element, opposite='other')"""
173
174     def __init__(self, name, type, lower=0, upper='*', opposite=None):
175         self.name = name
176         self.type = type
177         self.lower = lower
178         self.upper = upper
179         self.opposite = opposite
180
181     def load(self, obj, value):
182         if not isinstance(value, self.type):
183             raise AttributeError, 'Value should be of type %s' % self.type.__name__
184         if self.upper > 1:
185             self._get(obj).items.append(value)
186         else:
187             setattr(obj, '_' + self.name, value)
188
189     def __str__(self):
190         if self.lower == self.upper:
191             s = '<association %s: %s[%s]' % (self.name, self.type.__name__, self.lower)
192         else:
193             s = '<association %s: %s[%s..%s]' % (self.name, self.type.__name__, self.lower, self.upper)
194         if self.opposite:
195             s += ' -> %s' % self.opposite
196         return s + '>'
197
198     def __get__(self, obj, clazz=None):
199         """Retrieve the value of the association. In case this is called
200         directly on the class, return self."""
201         #print '__get__', self, obj, clazz
202         if not obj:
203             return self
204         return self._get(obj)
205
206     def __set__(self, obj, value):
207         """Set a new value for our attribute. If this is a collection, append
208         to the existing collection."""
209         #print '__set__', self, obj, value
210         if not isinstance(value, self.type):
211             raise AttributeError, 'Value should be of type %s' % self.type.__name__
212         # Remove old value only for uni-directional associations
213         if self.upper == 1:
214             old = self._get(obj)
215             if old:
216                 self.__delete__(obj, old)
217         # Set opposite side:
218         if self.opposite:
219             getattr(self.type, self.opposite)._set(value, obj)
220         self.__set(obj, value)
221
222     def __delete__(self, obj, value=None):
223         """Delete is used for element deletion and for removal of elements from
224         a list."""
225         #print '__delete__', self, obj, value
226         if self.upper > 1 and not value:
227             raise Exception, 'Can not delete collections'
228         if self.opposite:
229             getattr(self.type, self.opposite)._del(value or self._get(obj), obj)
230         self._del(obj, value or self._get(obj))
231        
232     def _get(self, obj):
233         #print '_get', self, obj
234         # TODO: Handle lower and add items if lower > 0
235         try:
236             return getattr(obj, '_' + self.name)
237         except AttributeError:
238             if self.upper > 1:
239                 l = Collection(self, obj, self.type)
240                 setattr(obj, '_' + self.name, l)
241                 return l
242             else:
243                 return None
244
245     def _set(self, obj, value):
246         #print '_set', self, obj, value
247         if not isinstance(value, self.type):
248             raise AttributeError, 'Value should be of type %s' % self.type.__name__
249         self.__set(obj, value)
250
251     def __set(self, obj, value):
252         """Real setter, avoid doing the assertion check twice."""
253         if self.upper > 1:
254             if value in self._get(obj):
255                 return
256             self._get(obj).items.append(value)
257         else:
258             setattr(obj, '_' + self.name, value)
259         value.attach(('__unlink__',), self.__on_unlink, obj, value)
260         self.notify(obj)
261
262     def _del(self, obj, value):
263         #print '_del', self, obj, value
264         if self.upper > 1:
265             items = self._get(obj).items
266             items.remove(value)
267             if not items:
268                 delattr(obj, '_' + self.name)
269         else:
270             delattr(obj, '_' + self.name)
271         value.detach(self.__on_unlink, obj, value)
272         self.notify(obj)
273
274     def unlink(self, obj):
275         lst = getattr(obj, '_' + self.name)
276         while lst:
277             self.__delete__(obj, lst[0])
278
279     def __on_unlink(self, name, obj, value):
280         """Disconnect when the element on the other end of the association
281         sends the '__unlink__' signal. This is especially important for
282         uni-directional associations.
283         """
284         #print '__on_unlink:', self, name, obj, value
285         self.__delete__(obj, value)
286
287
288 class derivedunion(umlproperty):
289     """Derived union
290     Element.union = derivedunion('union', subset1, subset2..subsetn)
291     The subsets are the properties that participate in the union (Element.name),
292
293     The derivedunion is added to the subsets deriviates list.
294     """
295
296     def __init__(self, name, lower, upper, *subsets):
297         self.name = name
298         self.lower = lower
299         self.upper = upper
300         self.subsets = subsets
301         self.single = len(subsets) == 1
302         for s in subsets:
303             s.add_deriviate(self)
304
305     def load(self, obj, value):
306         pass
307
308     def save(self, obj, save_func):
309         pass
310
311     def unlink(self, obj):
312         pass
313
314     def __str__(self):
315         return '<derivedunion %s: %s>' % (self.name, str(map(str, self.subsets))[1:-1])
316
317     def _get(self, obj):
318         if self.single:
319             #return getattr(obj, self.subsets[0])
320             return self.subsets[0].__get__(obj)
321         else:
322             u = list()
323             for s in self.subsets:
324                 #tmp = getattr(obj, s)
325                 tmp = s.__get__(obj)
326                 if tmp:
327                     # append or extend tmp (is it a list or not)
328                     try:
329                         for t in tmp:
330                             if t not in u:
331                                 u.append(t)
332                         #u.extend(tmp)
333                     except TypeError:
334                         if tmp not in u:
335                             u.append(tmp)
336             if self.upper > 1:
337                 return u
338             else:
339                 assert len(u) <= 1, 'Derived union %s should have length 1 %s' % (self.name, tuple(u))
340                 return u and u[0] or None
341
342     def _set(self, obj, value):
343         raise AttributeError, 'Can not set values on a union'
344
345     def _del(self, obj, value=None):
346         raise AttributeError, 'Can not delete values on a union'
347
348 class redefine(umlproperty):
349     """Redefined association
350     Element.x = redefine('x', Class, Element.assoc)
351     If the redefine eclipses the original property (it has the same name)
352     it ensures that the original values are saved and restored.
353
354     This property is connected to the originals deriviates list.
355     """
356
357     def __init__(self, name, type, original):
358         self.name = name
359         self.type = type
360         self.original = original
361         original.add_deriviate(self)
362
363     def load(self, obj, value):
364         if self.original.name == self.name:
365             self.original.load(obj, value)
366
367     def save(self, obj, save_func):
368         if self.original.name == self.name:
369             self.original.save(obj, save_func)
370
371     def unlink(self, obj):
372         self.original.unlink(obj)
373
374     def __str__(self):
375         return '<redefine %s: %s = %s>' % (self.name, self.type.__name__, str(self.original))
376
377     def __get__(self, obj, clazz=None):
378         if not obj:
379             return self
380         return self.original.__get__(obj, clazz)
381
382     def __set__(self, obj, value):
383         if not isinstance(value, self.type):
384             raise AttributeError, 'Value should be of type %s' % self.type.__name__
385         self.original.__set__(obj, value)
386
387     def __delete__(self, obj, value=None):
388         self.original.__delete__(obj, value)
389
Note: See TracBrowser for help on using the browser.