Changeset 445 for trunk/gaphor/uml2

Show
Ignore:
Timestamp:
11/02/04 02:52:18 (4 years ago)
Author:
arjanmol
Message:

*** empty log message ***

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/gaphor/uml2/collection.py

    r192 r445  
    1 # vim:sw=4 
     1# vi:sw=4:et 
    22 
    33import inspect 
     
    66    pass 
    77 
    8 class Collection(object): 
     8class collection(object): 
    99 
    1010    def __init__(self, property, object, type): 
    11        self.property = property 
    12        self.object = object 
    13        self.type = type 
    14        self.items = [] 
     11        self.property = property 
     12        self.object = object 
     13        self.type = type 
     14        self.items = [] 
    1515 
    1616    def __len__(self): 
     
    1818 
    1919    def __setitem__(self, key, value): 
    20        raise CollectionError, 'items should not be overwritten.' 
     20        raise CollectionError, 'items should not be overwritten.' 
    2121 
    2222    def __delitem__(self, key): 
     
    3030 
    3131    def __setslice__(self, i, j, s): 
    32        raise CollectionError, 'items should not be overwritten.' 
     32        raise CollectionError, 'items should not be overwritten.' 
    3333 
    3434    def __delslice__(self, i, j): 
    35        raise CollectionError, 'items should not be deleted this way.' 
     35        raise CollectionError, 'items should not be deleted this way.' 
    3636 
    3737    def __contains__(self, obj): 
     
    3939 
    4040    def __iter__(self): 
    41        return iter(self.items) 
     41        return iter(self.items) 
    4242 
    4343    def __str__(self): 
    44         return str(self.items) 
     44        return str(self.items) 
     45 
     46    __repr__ = __str__ 
    4547 
    4648    def __nonzero__(self): 
    47        return self.items!=[] 
     49        return self.items!=[] 
    4850 
    4951    def append(self, value): 
    50        if isinstance(value, self.type): 
    51            self.property._set(self.object, value) 
    52        else: 
    53            raise CollectionError, 'Object is not of type %s' % self.type.__name__ 
     52        if isinstance(value, self.type): 
     53            self.property._set(self.object, value) 
     54        else: 
     55            raise CollectionError, 'Object is not of type %s' % self.type.__name__ 
    5456 
    5557    def remove(self, value): 
    56         if value in self.items: 
    57             self.property.__delete__(self.object, value) 
    58         else: 
    59             raise AttributeError, '%s not in collection' % value 
     58        if value in self.items: 
     59            self.property.__delete__(self.object, value) 
     60        else: 
     61            raise AttributeError, '%s not in collection' % value 
     62 
    6063 
    6164    def index(self, key): 
    62         """Given an object, return the position of that object in the 
    63         collection.""" 
    64         return self.items.index(key) 
    65      
     65        """Given an object, return the position of that object in the 
     66        collection.""" 
     67        return self.items.index(key) 
     68 
     69 
    6670    # OCL members (from SMW by Ivan Porres, http://www.abo.fi/~iporres/smw) 
    6771 
     
    128132    def forAll(self,f): 
    129133        if not self.items or not inspect.getargspec(f)[0]: 
    130             return 1 
     134            return True 
    131135 
    132136        nargs=len(inspect.getargspec(f)[0]) 
     
    143147                args.append(self.items[x]) 
    144148            if not apply(f,args): 
    145                 return 0 
     149                return False 
    146150            c=len(index)-1 
    147151            index[c]=index[c]+1 
     
    150154                c=c-1 
    151155                if c<0: 
    152                     return 1 
     156                    return True 
    153157                else: 
    154158                    index[c]=index[c]+1  
    155159                if index[c]==nitems-1: 
    156160                    c=c-1 
     161        return False 
    157162 
    158163    def exist(self,f): 
    159164        if not self.items or not inspect.getargspec(f)[0]: 
    160             return 0 
     165            return False 
    161166 
    162167        nargs=len(inspect.getargspec(f)[0]) 
     
    172177                args.append(self.items[x]) 
    173178            if apply(f,args): 
    174                 return 1 
     179                return True 
    175180            c=len(index)-1 
    176181            index[c]=index[c]+1 
     
    179184                c=c-1 
    180185                if c<0: 
    181                     return 0 
     186                    return False 
    182187                else: 
    183188                    index[c]=index[c]+1  
    184189                if index[c]==nitems-1: 
    185190                    c=c-1 
     191        return False 
     192 
     193 
     194    def moveUp(self, value): 
     195        """ 
     196        Move element up. Owner is notified about the change. 
     197        """ 
     198        i1 = self.items.index(value) 
     199        i2 = i1 - 1 
     200        if i2 >= 0: 
     201            self.items[i1], self.items[i2] = self.items[i2], self.items[i1] 
     202            self.property.notify(self.object) # send a notification that this list has changed 
     203        else: 
     204            log.warning('Cannot move up first element') 
     205 
     206 
     207    def moveDown(self, value): 
     208        """ 
     209        Move element down. Owner is notified about the change. 
     210        """ 
     211        i1 = self.items.index(value) 
     212        i2 = i1 + 1 
     213        if i2 < len(self.items): 
     214            self.items[i1], self.items[i2] = self.items[i2], self.items[i1] 
     215            self.property.notify(self.object) # send a notification that this list has changed 
     216        else: 
     217            log.warning('Cannot move down last element') 
  • trunk/gaphor/uml2/element.py

    r196 r445  
    66with a completely new data model here. 
    77 
    8 save()/load()/postload() 
     8save(save_func) 
     9load(name, value) 
     10postload() 
    911    Load/save the element. 
    1012 
    1113unlink() 
    12     Remove all references to the element. First of all the signal 
    13     '__unlink__' is send to all attached signals. 
    14  
    15 attach ('name', callback, *data) or 
    16 attach (('name', 'other_property'), callback, *data) 
     14     Remove all references to the element. This is done by emiting the 
     15     '__unlink__' signal to all attached signals. unlink() can not be called 
     16     recursively. 
     17#relink() 
     18#    Inverse operation of unlink(). Used by diagram items during undo operations. 
     19 
     20connect ('name', callback, *data) or 
     21connect (('name', 'other_property'), callback, *data) 
    1722    Connect 'callback' to recieve notifications if one of the properties 
    1823    change (the latter being more memory efficient if you want to listen on 
     
    2025    first argument, followed by *data. 
    2126 
    22 detach (callback, *data) 
     27disconnect (callback, *data) 
    2328    Disconnect callback as listener. 
    2429 
    2530notify (name) 
    2631    Notify all listeners the property 'name' has changed. 
     32    The notifier calls callbacks registered by connect() by sending 
     33    callback(self, pspec, *data) 
     34    where self is the data object (Element) and pspec is the property spec. 
    2735""" 
    2836 
    29 import types 
     37__all__ = [ 'Element' ] 
     38 
     39import types, mutex 
     40import gaphor.misc.uniqueid as uniqueid 
     41from properties import umlproperty, association 
    3042 
    3143class Element(object): 
    3244    """Base class for UML data classes.""" 
    3345 
    34     def __init__(self): 
    35         self._observers = dict() 
     46    def __init__(self, id=None, factory=None): 
     47        self.id = id or uniqueid.generate_id() 
     48        # The factory this element belongs to. 
     49        self._factory = factory 
     50        self._observers = dict() 
     51        self.__in_unlink = mutex.mutex() 
     52 
     53    factory = property(lambda self: self._factory, 
     54                       doc="The factory that created this element") 
    3655 
    3756    def save(self, save_func): 
    38         """Save the state by calling save_func(name, value).""" 
    39         lst = dir(self.__class__) 
    40         for prop in map(isinstance, lst, [umlproperty] * len(lst)): 
     57        """Save the state by calling save_func(name, value).""" 
     58        umlprop = umlproperty 
     59        clazz = type(self) 
     60        for propname in dir(clazz): 
     61            prop = getattr(clazz, propname) 
     62            if isinstance(prop, umlprop): 
    4163                prop.save(self, save_func) 
    4264 
    4365    def load(self, name, value): 
    44         """Loads value in name. Make sure that for every load postload() 
    45         should be called.""" 
    46         try: 
    47             prop = getattr(self.__class__, name) 
     66        """Loads value in name. Make sure that for every load postload() 
     67        should be called. 
     68        """ 
     69        try: 
     70            prop = getattr(type(self), name) 
    4871        except AttributeError, e: 
    4972            raise AttributeError, "'%s' has no property '%s'" % \ 
    50                                         (self.__class__.__name__, name) 
     73                                        (type(self).__name__, name) 
    5174        else: 
    5275            prop.load(self, value) 
    5376 
    5477    def postload(self): 
    55         pass 
     78        """Fix up the odds and ends. 
     79        """ 
     80        for name in dir(type(self)): 
     81            try: 
     82                prop = getattr(type(self), name) 
     83            except AttributeError, e: 
     84                raise AttributeError, "'%s' has no property '%s'" % \ 
     85                                            (type(self).__name__, name) 
     86            else: 
     87                if isinstance(prop, umlproperty): 
     88                    prop.postload(self) 
     89 
     90    def __unlink(self, signal): 
     91        """Unlink the element. For both the __unlink__ and __relink__ signal 
     92        the __unlink__ callback list is used. 
     93        """ 
     94        # Uses a mutex to make sure it is not called recursively 
     95        if self.__in_unlink.testandset(): 
     96            try: 
     97                self.notify(signal, '__unlink__') 
     98            finally: 
     99                self.__in_unlink.unlock() 
    56100 
    57101    def unlink(self): 
    58         self.notify('__unlink__') 
    59             
    60     def attach(self, names, callback, *data): 
    61         """Attach 'callback' to a list of names. Names may also be a string.""" 
     102        """Unlink the element.""" 
     103        #log.debug('Element.unlink(%s)' % self) 
     104        self.__unlink('__unlink__') 
     105 
     106#    def relink(self): 
     107#        """Undo the unlink operation.""" 
     108#        log.debug('Element.relink(%s)' % self) 
     109#        self.__unlink('__relink__') 
     110 
     111    def connect(self, names, callback, *data): 
     112        """Attach 'callback' to a list of names. Names may also be a string. 
     113        A name is the name od a property of the object or '__unlink__'. 
     114        """ 
     115        #log.debug('Element.connect(%s, %s, %s)' % (names, callback, data)) 
    62116        if type(names) is types.StringType: 
    63117            names = (names,) 
     
    72126                self._observers[name] = [cb] 
    73127 
    74     def detach(self, callback, *data): 
    75         """Detach a callback identified by it's data.""" 
    76         #print 'detach', callback, data 
     128    def disconnect(self, callback, *data): 
     129        """Detach a callback identified by it's data.""" 
    77130        cb = (callback,) + data 
    78        for values in self._observers.values(): 
     131        for values in self._observers.values(): 
    79132            # Remove all occurences of 'cb' from values 
    80133            # (if none is found ValueError is raised). 
     
    85138                pass 
    86139 
    87     def notify(self, name): 
     140    def notify(self, name, cb_name=None, pspec=None): 
    88141        """Send notification to attached callbacks that a property 
    89         has changed.""" 
    90         cb_list = self._observers.get(name) or () 
    91         for cb_data in cb_list: 
    92             try: 
    93                 apply(cb_data[0], (name,) + cb_data[1:]) 
    94             except: 
    95                 pass 
     142        has changed. the __relink__ signal uses the callbacks for __unlink__. 
     143        """ 
     144        cb_list = self._observers.get(cb_name or name, ()) 
     145        #log.debug('Element.notify: %s' % cb_list) 
     146        if not pspec: 
     147            try: 
     148                pspec = getattr(type(self), name) 
     149            except AttributeError: 
     150                pspec = name 
     151         
     152        # Use a copy of the list to ensure all items are notified 
     153        for cb_data in list(cb_list): 
     154            try: 
     155                apply(cb_data[0], (self, pspec) + cb_data[1:]) 
     156            except Exception, e: 
     157                log.error('failed notification for %s' % cb_data[0], e) 
    96158 
    97159    # OCL methods: (from SMW by Ivan Porres (http://www.abo.fi/~iporres/smw)) 
    98160 
    99     def isKindOf(self,clazz): 
     161    def isKindOf(self, class_): 
    100162        """Returns true if the object is an instance of clazz.""" 
    101         return isinstance(self, clazz
     163        return isinstance(self, class_
    102164 
    103165    def isTypeOf(self, other): 
     
    105167        return type(self) == type(other) 
    106168 
     169#    def __setattr__(self, key, value): 
     170#        if key.startswith('_') or key == 'id': 
     171#            object.__setattr__(self, key, value) 
     172#        else: 
     173#            raise AttributeError, 'Invalid attribute "%s"' % key 
     174 
     175try: 
     176    import psyco 
     177except ImportError: 
     178    pass 
     179else: 
     180    psyco.bind(Element) 
    107181 
    108182if __name__ == '__main__': 
     
    112186        print '  cb_func:', name, args 
    113187 
    114     a.attach('ev1', cb_func, a) 
    115     a.attach('ev1', cb_func, a) 
    116     a.attach('ev2', cb_func, 'ev2', a) 
     188    a.connect('ev1', cb_func, a) 
     189    a.connect('ev1', cb_func, a) 
     190    a.connect('ev2', cb_func, 'ev2', a) 
    117191 
    118192    print 'notify: ev1' 
     
    121195    a.notify('ev2') 
    122196  
    123     a.detach(cb_func, a) 
     197    a.disconnect(cb_func, a) 
    124198 
    125199    print 'notify: ev1' 
  • trunk/gaphor/uml2/properties.py

    r197 r445  
    2424    load(value):      load 'value' as the current value for this property 
    2525    save(save_func):  send the value of the property to save_func(name, value) 
    26     unlink():         remove references to other elements. 
     26    #unlink():         remove references to other elements. 
    2727""" 
    2828 
    29 from collection import Collection 
     29__all__ = [ 'attribute', 'enumeration', 'association', 'derivedunion', 'redefine' ] 
     30 
     31from collection import collection 
    3032import operator 
     33from gaphor.undomanager import get_undo_manager 
    3134 
    3235#infinite = 100000 
     36 
     37class undoaction(object): 
     38    """This undo action contains one undo action for an attribute 
     39    property. 
     40    """ 
     41 
     42    def __init__(self, prop, obj, value): 
     43        self.prop = prop 
     44        self.obj = obj 
     45        self.value = value 
     46        #print 'adding new property', prop.name, obj, value 
     47        get_undo_manager().add_undo_action(self) 
     48 
    3349 
    3450class umlproperty(object): 
     
    4561            self.deriviates = [ deriviate ] 
    4662 
    47     def __get__(self, obj, clazz=None): 
     63    def __get__(self, obj, class_=None): 
    4864        if not obj: 
    4965            return self 
     
    5672        self._del(obj, value) 
    5773 
     74    def save(self, obj, save_func): 
     75        if hasattr(obj, self._name): 
     76            save_func(self.name, self._get(obj)) 
     77 
    5878    def load(self, obj, value): 
    5979        self._set(obj, value) 
    6080 
    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) 
     81    def postload(self, obj): 
     82        pass 
     83 
     84#    def unlink(self, obj): 
     85#        if hasattr(obj, self._name): 
     86#            self.__delete__(obj) 
    6887 
    6988    def notify(self, obj): 
    7089        """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 
     90        Deriviates are also triggered to send a notify signal. 
     91        """ 
     92        try: 
     93            obj.notify(self.name, pspec=self) 
     94        except Exception, e: 
     95            log.error(str(e), e) 
    7696 
    7797        # we need to check if a deriviate is part of the current object: 
    7898        # 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(): 
     99        try: 
     100            deriviates = self.deriviates 
     101        except AttributeError: 
     102            pass # no derivedunion or redefine attribute 
     103        else: 
     104            values = [] 
     105            for c in type(obj).__mro__: 
     106                values.extend(c.__dict__.values()) 
     107 
     108            for d in deriviates: 
     109                if d in values: 
     110                    try: 
    84111                        d.notify(obj) 
    85         except AttributeError: 
    86             pass # no derivedunion or redefine attribute 
     112                    except Exception, e: 
     113                        log.error(e, e) 
     114 
     115 
     116class undoattributeaction(undoaction): 
     117    """This undo action contains one undo action for an attribute 
     118    property. 
     119    """ 
     120 
     121    def undo(self): 
     122        self.redo_value = self.prop._get(self.obj) 
     123        setattr(self.obj, self.prop._name, self.value) 
     124        self.prop.notify(self.obj) 
     125 
     126    def redo(self): 
     127        setattr(self.obj, self.prop._name, self.redo_value) 
     128        self.prop.notify(self.obj) 
    87129 
    88130 
     
    92134 
    93135    # 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 
     136    def __init__(self, name, type, default=None, lower=0, upper=1): 
     137        self.name = intern(name) 
     138        self._name = intern('_' + name) 
    96139        self.type = type 
    97140        self.default = default 
     
    100143         
    101144    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) 
     145        # FixMe: value might be a string while some other type is required: 
     146        #print 'attribute.load:', self.name, self.type, value, 
     147        if self.type is not object: 
     148            value = self.type(value) 
     149        setattr(obj, self._name, value) 
    105150 
    106151    def __str__(self): 
     
    112157    def _get(self, obj): 
    113158        try: 
    114             return getattr(obj, '_' + self.name) 
     159            return getattr(obj, self._name) 
    115160        except AttributeError: 
    116161            return self.default 
    117162 
    118163    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) 
     164        if value is not None and not isinstance(value, self.type): 
     165            raise AttributeError, 'Value should be of type %s' % hasattr(self.type, '__name__') and self.type.__name__ or self.type 
     166 
     167        if value == self._get(obj): 
     168            return 
     169 
     170        undoattributeaction(self, obj, self._get(obj)) 
     171 
     172        if value == self.default and hasattr(obj, self._name): 
     173            delattr(obj, self._name) 
     174        else: 
     175            setattr(obj, self._name, value) 
    125176        self.notify(obj) 
    126177 
    127178    def _del(self, obj, value=None): 
    128         delattr(obj, '_' + self.name) 
    129         self.notify(obj) 
     179        #self.old = self._get(obj) 
     180        try: 
     181            undoattributeaction(self, obj, self._get(obj)) 
     182            delattr(obj, self._name) 
     183        except AttributeError: 
     184            pass 
     185        else: 
     186            self.notify(obj) 
    130187 
    131188 
     
    135192 
    136193    def __init__(self, name, values, default): 
    137         self.name = name 
     194        self.name = intern(name) 
     195        self._name = intern('_' + name) 
    138196        self.values = values 
    139197        self.default = default 
     
    144202    def _get(self, obj): 
    145203        try: 
    146             return getattr(obj, '_' + self.name) 
     204            return getattr(obj, self._name) 
    147205        except AttributeError: 
    148206            return self.default 
     
    150208    def load(self, obj, value): 
    151209        if not value in self.values: 
    152             raise AttributeError, 'Value should be in %s' % str(self.values) 
    153         setattr(obj, '_' + self.name, value) 
     210            raise AttributeError, 'Value should be one of %s' % str(self.values) 
     211        setattr(obj, self._name, value) 
    154212 
    155213    def _set(self, obj, value): 
    156214        if not value in self.values: 
    157             raise AttributeError, 'Value should be in %s' % str(self.values) 
     215            raise AttributeError, 'Value should be one of %s' % str(self.values) 
    158216        if value != self._get(obj): 
     217            undoattributeaction(self, obj, self._get(obj)) 
    159218            if value == self.default: 
    160                 delattr(obj, '_' + self.name) 
     219                delattr(obj, self._name) 
    161220            else: 
    162                 setattr(obj, '_' + self.name, value) 
     221                setattr(obj, self._name, value) 
    163222            self.notify(obj) 
    164223 
    165224    def _del(self, obj, value=None): 
    166         delattr(obj, '_' + self.name) 
    167         self.notify(obj) 
     225        try: 
     226            undoattributeaction(self, obj, self._get(obj)) 
     227            delattr(obj, self._name) 
     228        except AttributeError: 
     229            pass 
     230        else: 
     231            self.notify(obj) 
     232 
     233 
     234class undosetassociationaction(undoaction): 
     235    """Undo a 'set' action in an association. 
     236    """ 
     237 
     238    def undo(self): 
     239        #log.debug('undosetassociationaction del: %s %s %s' % (self.obj, self.prop.name, self.value)) 
     240        self.prop._del(self.obj, self.value) 
     241 
     242    def redo(self): 
     243        #log.debug('undosetassociationaction set: %s %s %s' % (self.obj, self.prop.name, self.value)) 
     244        self.prop._set(self.obj, self.value) 
     245 
     246 
     247class undodelassociationaction(undoaction): 
     248    """Undo a 'del' action in an association. 
     249    """ 
     250 
     251    def undo(self): 
     252        #log.debug('undodelassociationaction set: %s %s %s' % (self.obj, self.prop.name, self.value)) 
     253        self.prop._set(self.obj, self.value) 
     254 
     255    def redo(self): 
     256        #log.debug('undodelassociationaction del: %s %s %s' % (self.obj, self.prop.name, self.value)) 
     257        self.prop._del(self.obj, self.value) 
    168258 
    169259 
    170260class association(umlproperty): 
    171261    """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 
     262    Element.assoc = association('assoc', Element, opposite='other') 
     263     
     264    A listerer is connected to the value added to the association. This 
     265    will cause the association to be ended if the element on the other end 
     266    of the association is unlinked. 
     267    If the association is a composite relationship, the value is connected to 
     268    the elements __unlink__ signal too. This will cause the value to be 
     269    unlinked as soon as the element is unlinked. 
     270    """ 
     271  
     272    def __init__(self, name, type, lower=0, upper='*', composite=False, opposite=None): 
     273        self.name = intern(name) 
     274        self._name = intern('_' + name) 
    176275        self.type = type 
    177276        self.lower = lower 
    178277        self.upper = upper 
    179         self.opposite = opposite 
     278        self.composite = composite 
     279        self.opposite = opposite and intern(opposite) 
    180280 
    181281    def load(self, obj, value): 
    182282        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) 
     283            raise AttributeError, 'Value for %s should be of type %s (%s)' % (self.name, self.type.__name__, type(value).__name__) 
     284        # TODO: avoid sending notifications: 
     285        self._set(obj, value, do_notify=False) 
     286 
     287    def postload(self, obj): 
     288        """In the postload step, ensure that bi-directional associations 
     289        are bi-directional. 
     290        """ 
     291        values = self._get(obj) 
     292        if not values: 
     293            return 
     294        if self.upper == 1: 
     295            values = [ values ] 
     296        for value in values: 
     297            if not isinstance(value, self.type): 
     298                raise AttributeError, 'Error in postload validation for %s: Value %s should be of type %s' % (self.name, value, self.type.__name__) 
    188299 
    189300    def __str__(self): 
     
    193304            s = '<association %s: %s[%s..%s]' % (self.name, self.type.__name__, self.lower, self.upper) 
    194305        if self.opposite: 
    195             s += ' -> %s' % self.opposite 
     306            s += ' %s-> %s' % (self.composite and '<>' or '', self.opposite) 
    196307        return s + '>' 
    197308 
    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): 
     309    def _get(self, obj): 
     310        #print '_get', self, obj 
     311        # TODO: Handle lower and add items if lower > 0 
     312        try: 
     313            return getattr(obj, self._name) 
     314        except AttributeError: 
     315            if self.upper > 1: 
     316                # Create the empty collection here since it might be used to 
     317                # add  
     318                #c = collection(self, obj, self.type) 
     319                #setattr(obj, self._name, c) 
     320                c = collection(self, obj, self.type) 
     321                setattr(obj, self._name, c) 
     322                return c 
     323            else: 
     324                return None 
     325 
     326    def _set(self, obj, value, from_opposite=False, do_notify=True): 
    207327        """Set a new value for our attribute. If this is a collection, append 
    208         to the existing collection.""" 
     328        to the existing collection. 
     329 
     330        This method is called from the opposite association property. 
     331        """ 
    209332        #print '__set__', self, obj, value 
    210         if not isinstance(value, self.type): 
     333        if not (isinstance(value, self.type) or \ 
     334                (value is None and self.upper == 1)): 
    211335            raise AttributeError, 'Value should be of type %s' % self.type.__name__ 
    212336        # Remove old value only for uni-directional associations 
    213337        if self.upper == 1: 
    214338            old = self._get(obj) 
     339            # do nothing if we are assigned our current value: 
     340            if value is old: 
     341                return 
     342 
     343            # is done is _del(): undoassociationaction(self, obj, old) 
    215344            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.""" 
     345                self._del(obj, old) 
     346            if value is None: 
     347                return 
     348            if value is self._get(obj): 
     349                #log.debug('association: value already in obj: %s' % value) 
     350                return 
     351 
     352            if not from_opposite: 
     353                undosetassociationaction(self, obj, value) 
     354            setattr(obj, self._name, value) 
     355        else: 
     356            # Set the actual value 
     357            c = self._get(obj) 
     358            if not c: 
     359                c = collection(self, obj, self.type) 
     360                setattr(obj, self._name, c) 
     361            elif value in c: 
     362                #log.debug('association: value already in obj: %s' % value) 
     363                return 
     364 
     365            if not from_opposite: 
     366                undosetassociationaction(self, obj, value) 
     367            c.items.append(value) 
     368 
     369        # Callbacks are only connected if a new relationship has 
     370        # been established. 
     371        value.connect('__unlink__', self.__on_unlink, obj) 
     372        if self.composite: 
     373            obj.connect('__unlink__', self.__on_composite_unlink, value) 
     374         
     375        if not from_opposite and self.opposite: 
     376            getattr(type(value), self.opposite)._set(value, obj, from_opposite=True, do_notify=do_notify) 
     377 
     378        if do_notify: 
     379            self.notify(obj) 
     380 
     381    def _del(self, obj, value, from_opposite=False): 
     382        # TODO: move code to _del 
     383        """Delete is used for element deletion and for removal of 
     384        elements from a list. 
     385        """ 
    225386        #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: 
     387 
     388        if not value: 
    238389            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.""" 
     390                raise Exception, 'Can not delete collections' 
     391            value = self._get(obj) 
     392            if value is None: 
     393                return 
     394 
     395        if not from_opposite: 
     396            undodelassociationaction(self, obj, value) 
     397 
     398        if not from_opposite and self.opposite: 
     399            getattr(type(value), self.opposite)._del(value, obj, from_opposite=True) 
     400 
    253401        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) 
     402            c = self._get(obj) 
     403            if c: 
     404                items = c.items 
     405                try: 
     406                    items.remove(value) 
     407                except: 
     408                    pass 
     409                if not items: 
     410                    delattr(obj, self._name) 
     411        else: 
     412            try: 
     413                delattr(obj, self._name) 
     414            except: 
     415                pass 
     416                #print 'association._del: delattr failed for %s' % self.name 
     417 
     418        value.disconnect(self.__on_unlink, obj) 
     419        if self.composite: 
     420            obj.disconnect(self.__on_composite_unlink, value) 
    260421        self.notify(obj) 
    261422 
    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): 
     423#    def unlink(self, obj): 
     424#        #print 'unlink', self, obj 
     425#        lst = getattr(obj, self._name) 
     426#        while lst: 
     427#            self.__delete__(obj, lst[0]) 
     428#            # re-establish unlink handler: 
     429#            value.connect('__unlink__', self.__on_unlink, obj) 
     430 
     431    def __on_unlink(self, value, pspec, obj): 
    280432        """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. 
     433        (value) sends the '__unlink__' signal. This is especially important 
     434        for uni-directional associations. 
    283435        """ 
    284         #print '__on_unlink:', self, name, obj, value 
    285         self.__delete__(obj, value) 
     436        #print '__on_unlink', name, obj, value 
     437        if pspec == '__unlink__': 
     438            self.__delete__(obj, value) 
     439            # re-establish unlink handler: 
     440            value.connect('__unlink__', self.__on_unlink, obj) 
     441 
     442    def __on_composite_unlink(self, obj, pspec, value): 
     443        """Unlink value if we have a part-whole (composite) relationship 
     444        (value is a composite of obj). 
     445        The implementation of value.unlink() should ensure that no deadlocks 
     446        occur. 
     447        """ 
     448        #print '__on_composite_unlink:', self, name, value 
     449        if pspec == '__unlink__': 
     450            value.unlink() 
     451            obj.connect('__unlink__', self.__on_composite_unlink, value) 
     452        #else: 
     453            #print 'RELINK' 
     454            #value.relink() 
    286455 
    287456 
     
    295464 
    296465    def __init__(self, name, lower, upper, *subsets): 
    297         self.name = name 
     466        self.name = intern(name) 
     467        self._name = intern('_' + name) 
    298468        self.lower = lower 
    299469        self.upper = upper 
     
    304474 
    305475    def load(self, obj, value): 
    306         pass 
     476        raise ValueError, 'Derivedunion: Properties should not be loaded in a derived union %s: %s' % (self.name, value) 
    307477 
    308478    def save(self, obj, save_func): 
    309479        pass 
    310480 
    311     def unlink(self, obj): 
    312         pass 
     481#    def unlink(self, obj): 
     482#        pass 
    313483 
    314484    def __str__(self): 
     
    322492            u = list() 
    323493            for s in self.subsets: 
    324                 #tmp = getattr(obj, s) 
    325494                tmp = s.__get__(obj) 
    326495                if tmp: 
     
    337506                return u 
    338507            else: 
    339                 assert len(u) <= 1, 'Derived union %s should have length 1 %s' % (self.name, tuple(u)) 
     508                assert len(u) <= 1, 'Derived union %s of item %s should have length 1 %s' % (self.name, obj.id, tuple(u)) 
    340509                return u and u[0] or None 
    341510 
     
    345514    def _del(self, obj, value=None): 
    346515        raise AttributeError, 'Can not delete values on a union' 
     516 
    347517 
    348518class redefine(umlproperty): 
     
    356526 
    357527    def __init__(self, name, type, original): 
    358         self.name = name 
     528        self.name = intern(name) 
     529        self._name = intern('_' + name) 
    359530        self.type = type 
    360531        self.original = original 
    361532        original.add_deriviate(self) 
    362533 
     534    upper = property(lambda s: s.original.upper) 
     535    lower = property(lambda s: s.original.lower) 
     536 
    363537    def load(self, obj, value): 
    364538        if self.original.name == self.name: 
     
    369543            self.original.save(obj, save_func) 
    370544 
    371     def unlink(self, obj): 
    372         self.original.unlink(obj) 
     545#    def unlink(self, obj): 
     546#        self.original.unlink(obj) 
    373547 
    374548    def __str__(self): 
    375549        return '<redefine %s: %s = %s>' % (self.name, self.type.__name__, str(self.original)) 
    376550 
    377     def __get__(self, obj, clazz=None): 
     551    def __get__(self, obj, class_=None): 
     552        # No longer needed 
    378553        if not obj: 
    379554            return self 
    380         return self.original.__get__(obj, clazz
     555        return self.original.__get__(obj, class_
    381556 
    382557    def __set__(self, obj, value): 
     558        # No longer needed 
    383559        if not isinstance(value, self.type): 
    384560&n