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

Revision 192, 8.7 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 Element(BaseElement): pass
9     class Comment(BaseElement): pass
10     Element.ownedComment = association('ownedComment', Comment,
11                                        0, infinite, 'annotatedElement')
12     Element.annotatedElement = association('annotatedElement', Element,
13                                            0, infinite, '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
31 infinite = 100000
32
33 class umlproperty(object):
34     """Superclass for attribute, enumeration and association."""
35     name='umlproperty'
36
37     def __get__(self, obj, clazz=None):
38         if not obj:
39             return self
40         return self._get(obj)
41
42     def __set__(self, obj, value):
43         self._set(obj, value)
44
45     def __delete__(self, obj, value=None):
46         self._del(obj)
47
48     def load(self, obj, value):
49         self._set(obj, value)
50
51     def save(self, obj, save_func):
52         if hasattr(obj, '_' + self.name):
53             save_func(self.name, self._get(obj))
54
55     def unlink(self, obj):
56         if hasattr(obj, '_' + self.name)
57             self.__delete__(obj)
58
59     def notify(self, obj):
60         """Notify obj that the property's value has been changed"""
61         try:
62             obj.notify(self.name)
63         except:
64             pass
65
66
67 class attribute(umlproperty):
68     """Attribute.
69     Element.attr = attribute('attr', types.StringType, '')"""
70
71     # TODO: check if lower and upper are actually needed for attributes
72     def __init__(self, name, type, default, lower=0, upper=1):
73         self.name = name
74         self.type = type
75         self.default = default
76         self.lower = lower
77         self.upper = upper
78        
79     def _get(self, obj):
80         try:
81             return getattr(obj, '_' + self.name)
82         except AttributeError:
83             return self.default
84
85     def _set(self, obj, value):
86         if not isinstance(value, self.type):
87             raise AttributeError, 'Value should be of type %s' % self.type.__name__
88         setattr(obj, '_' + self.name, value)
89         self.notify(obj)
90
91     def _del(self, obj, value=None):
92         delattr(obj, '_' + self.name)
93         self.notify(obj)
94
95
96 class enumeration(umlproperty):
97     """Enumeration.
98     Element.enum = enumeration('enum', ('one', 'two', 'three'), 'one')"""
99
100     def __init__(self, name, values, default):
101         self.name = name
102         self.values = values
103         self.default = default
104
105     def _get(self, obj):
106         try:
107             return getattr(obj, '_' + self.name)
108         except AttributeError:
109             return self.default
110
111     def _set(self, obj, value):
112         if not value in self.values:
113             raise AttributeError, 'Value should be in %s' % str(self.values)
114         if value != self._get(obj):
115             setattr(obj, '_' + self.name, value)
116             self.notify(obj)
117
118     def _del(self, obj, value=None):
119         delattr(obj, '_' + self.name)
120         self.notify(obj)
121
122
123 class association(umlproperty):
124     """Association, both uni- and bi-directional.
125     Element.assoc = association('assoc', Element, opposite='other')"""
126
127     def __init__(self, name, type, lower=0, upper=infinite, opposite=None):
128         self.name = name
129         self.type = type
130         self.lower = lower
131         self.upper = upper
132         self.opposite = opposite
133
134     def __get__(self, obj, clazz=None):
135         """Retrieve the value of the association. In case this is called
136         directly on the class, return self."""
137         #print '__get__', self, obj, clazz
138         if not obj:
139             return self
140         return self._get(obj)
141
142     def __set__(self, obj, value):
143         """Set a new value for our attribute. If this is a collection, append
144         to the existing collection."""
145         #print '__set__', self, obj, value
146         if not isinstance(value, self.type):
147             raise AttributeError, 'Value should be of type %s' % self.type.__name__
148         # Remove old value only for uni-directional associations
149         if self.upper == 1:
150             old = self._get(obj)
151             if old and self.opposite:
152                 getattr(self.type, self.opposite)._del(old, obj)
153         # Set opposite side:
154         if self.opposite:
155             getattr(self.type, self.opposite)._set(value, obj)
156         self.__set(obj, value)
157
158     def __delete__(self, obj, value=None):
159         """Delete is used for element deletion and for removal of elements from
160         a list."""
161         #print '__delete__', self, obj, value
162         if self.upper > 1 and not value:
163             raise Exception, 'Can not delete collections'
164         if self.opposite:
165             getattr(self.type, self.opposite)._del(value or self._get(obj), obj)
166         self._del(obj, value)
167        
168     def _get(self, obj):
169         #print '_get', self, obj
170         # TODO: Handle lower and add items if lower > 0
171         try:
172             return getattr(obj, '_' + self.name)
173         except AttributeError:
174             if self.upper > 1:
175                 l = Collection(self, obj, self.type)
176                 setattr(obj, '_' + self.name, l)
177                 return l
178             else:
179                 return None
180
181     def _set(self, obj, value):
182         #print '_set', self, obj, value
183         if not isinstance(value, self.type):
184             raise AttributeError, 'Value should be of type %s' % self.type.__name__
185         self.__set(obj, value)
186
187     def __set(self, obj, value):
188         """Real setter, avoid doing the assertion check twice."""
189         if self.upper > 1:
190             self._get(obj).items.append(value)
191         else:
192             setattr(obj, '_' + self.name, value)
193         self.notify(obj)
194
195     def _del(self, obj, value=None):
196         #print '_del', self, obj, value
197         if self.upper > 1:
198             self._get(obj).items.remove(value)
199         else:
200             delattr(obj, '_' + self.name)
201         self.notify(obj)
202
203     def unlink(self, obj):
204         lst = getattr(obj, '_' + self.name):
205         while lst:
206             self.__delete__(obj, lst[0])
207
208     def notify(self, obj):
209         """Notify that the property has changed. Also let derived unions
210         notify.
211         TODO: does this also apply to attributes and enumerations?
212         """
213         umlproperty.notify(self, obj)
214
215         # Also notify derived unions that superset this property
216         # This is done by finding derived unions in the class' dict that
217         # contain references to this association.
218         # Optimize for speed:
219         _isinstance = isinstance
220         _derivedunion = derivedunion
221         for u in obj.__class__.__dict__.values():
222             if _isinstance(u, _derivedunion) and self in u:
223                 u.notify(obj)
224
225
226 class derivedunion(umlproperty):
227     """Derived union
228     Element.union = derivedunion('union', subset1, subset2..subsetn)
229     The subsets are the properties that participate in the union (Element.name),
230     """
231
232     def __init__(self, name, *subsets):
233         self.name = name
234         self.subsets = subsets
235         self.single = len(subsets) == 1
236
237     def __contains__(self, obj):
238         """Returns if 'obj' is in the subsets list."""
239         return obj in self.subsets
240
241     def _get(self, obj):
242         if self.single:
243             #return getattr(obj, self.subsets[0])
244             return self.subsets[0].__get__(obj)
245         else:
246             u = list()
247             for s in self.subsets:
248                 #tmp = getattr(obj, s)
249                 tmp = s.__get__(obj)
250                 if tmp:
251                     # append or extend tmp (is it a list or not)
252                     try:
253                         iter(tmp)
254                     except TypeError:
255                         u.append(tmp)
256                     else:
257                         u.extend(tmp)
258             return u
259
260     def _set(self, obj, value):
261         raise AttributeError, 'Can not set values on a union'
262
263     def _del(self, obj, value=None):
264         raise AttributeError, 'Can not delete values on a union'
265
266     def load(self, obj, value):
267         pass
268
269     def save(self, obj, save_func):
270         pass
271
272     def unlink(self, obj):
273         pass
274
Note: See TracBrowser for help on using the browser.