Source

Webware / MiddleKit / Core / Klass.py

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
from ModelObject import ModelObject
from MiscUtils import NoDefault
from MiddleKit.Core.ListAttr import ListAttr
from MiddleKit.Core.ObjRefAttr import ObjRefAttr
from MiddleDict import MiddleDict


class Klass(MiddleDict, ModelObject):
    """A Klass represents a class specification.

    It is consisting primarily of a name and a list of attributes.

    """


    ## Init ##

    def __init__(self, klassContainer, rawDict=None):
        """Initialize a Klass definition with a raw dictionary.

        This is typically read from a file. The 'Class' field contains the name
        and can also contain the name of the superclass (like "Name : SuperName").
        Multiple inheritance is not yet supported.

        """
        MiddleDict.__init__(self, {})
        self._klassContainer = klassContainer
        self._attrsList = []
        self._attrsByName = {}
        self._superklass = None
        self._subklasses = []
        # False means never computed; None would mean computed, but not found:
        self._pyClass = False
        self._backObjRefAttrs = None
        self._allAttrs = None
        if rawDict is not None:
            self.readDict(rawDict)


    ## Reading ##

    def readDict(self, rawDict):
        name = rawDict['Class']
        if '(' in name:
            assert ')' in name, 'Invalid class spec. Missing ).'
            self._name, rest = name.split('(', 1)
            self._supername, rest = rest.split(')', 1)
            assert rest.strip() == ''
            self._name = self._name.strip()
            self._supername = self._supername.strip()
        elif ':' in name:
            # deprecated: we used to use a C++-like syntax involving colons
            # instead of a Python-like syntax with parens
            parts = [part.strip() for part in name.split(':', 2)]
            if len(parts) != 2:
                raise RuntimeError('Invalid class spec: %s' % string)
            self._name, self._supername = parts
        else:
            self._name = name
            self._supername = rawDict.get('Super', 'MiddleObject')
        self._isAbstract = rawDict.get('isAbstract', False)

        # fill in dictionary (self) with the contents of the dict argument
        for key, value in rawDict.items():
            # @@ 2001-02-21 ce: should we always strip string fields? Probably.
            if isinstance(value, basestring) and not value.strip():
                value = None
            self[key] = value

    def awakeFromRead(self, klasses):
        """Perform further initialization.

        Invoked by Klasses after all basic Klass definitions have been read.

        """
        assert self._klasses is klasses

        self._makeAllAttrs()
        # Python classes need to know their MiddleKit classes in
        # order for MiddleKit.Run.MiddleObject methods to work.
        # Invoking pyClass() makes that happen.
        self.pyClass()
        for attr in self.attrs():
            attr.awakeFromRead()

    def _makeAllAttrs(self):
        """Make all attributes.

        Makes list attributes accessible via methods for the following:
            allAttrs - every attr of the klass including inherited and derived attributes
            allDataAttrs - every attr of the klass including inherited, but NOT derived
            allDataRefAttrs - same as allDataAttrs, but only obj refs and lists

        ...and a dictionary attribute used by lookupAttr().

        Does nothing if called extra times.

        """
        if self._allAttrs is not None:
            return

        klass = self
        klasses = []
        while klass:
            klasses.append(klass)
            klass = klass.superklass()
        klasses.reverse()

        allAttrs = []
        allDataAttrs = []
        allDataRefAttrs = []
        for klass in klasses:
            attrs = klass.attrs()
            allAttrs.extend(attrs)
            for attr in attrs:
                if not attr.get('isDerived', False):
                    allDataAttrs.append(attr)
                    if isinstance(attr, (ObjRefAttr, ListAttr)):
                        allDataRefAttrs.append(attr)

        self._allAttrs = allAttrs
        self._allDataAttrs = allDataAttrs
        self._allDataRefAttrs = allDataRefAttrs

        # set up _allAttrsByName which is used by lookupAttr()
        self._allAttrsByName = {}
        for attr in allAttrs:
            self._allAttrsByName[attr.name()] = attr


    ## Names ##

    def name(self):
        return self._name

    def supername(self):
        return self._supername


    ## Id ##

    def id(self):
        """Return the id of the class, which is an integer.

        Ids can be fundamental to storing object references in concrete object stores.
        This method will throw an exception if setId() was not previously invoked.

        """
        return self._id

    def setId(self, id):
        if isinstance(id, set):
            # create an id that is a hash of the klass name
            # see Klasses.assignClassIds()
            allIds = id
            # the limit of 2 billion keeps the id easily in the range
            # of a 32 bit signed int without going negative
            limit = 2000000000
            id = abs(hash(self.name()) % limit)
            assert 0 < id < limit
            while id in allIds:
                # adjust for collision
                id += 1
            assert 0 < id < limit
            self._id = id
        else:
            self._id = id


    ## Superklass ##

    def superklass(self):
        return self._superklass

    def setSuperklass(self, klass):
        assert self._superklass is None, "Can't set superklass twice."
        self._superklass = klass
        klass.addSubklass(self)


    ## Ancestors ##

    def lookupAncestorKlass(self, name, default=NoDefault):
        """Search for and return the ancestor klass with the given name.

        Raises an exception if no such klass exists, unless a default
        is specified (in which case it is returned).

        """
        if self._superklass:
            if self._superklass.name() == name:
                return self._superklass
            else:
                return self._superklass.lookupAncestorKlass(name, default)
        else:
            if default is NoDefault:
                raise KeyError(name)
            else:
                return default

    def isKindOfKlassNamed(self, name):
        """Check whether the klass is from the given kind.

        Returns true if the klass is the same as, or inherits from,
        the klass with the given name.

        """
        if self.name() == name:
            return True
        else:
            return self.lookupAncestorKlass(name, None) is not None


    ## Subklasses ##

    def subklasses(self):
        return self._subklasses

    def addSubklass(self, klass):
        self._subklasses.append(klass)

    def descendants(self, init=1, memo=None):
        """Return all descendant klasses of this klass."""
        if memo is None:
            memo = set()
        if self in memo:
            return
        memo.add(self)
        for k in self.subklasses():
            k.descendants(init=0, memo=memo)
        if init:
            memo.remove(self)
        return memo


    ## Accessing attributes ##

    def addAttr(self, attr):
        self._attrsList.append(attr)
        self._attrsByName[attr.name()] = attr
        attr.setKlass(self)

    def attrs(self):
        """Return a list of all the klass' attributes not including inheritance."""
        return self._attrsList

    def hasAttr(self, name):
        return name in self._attrsByName

    def attr(self, name, default=NoDefault):
        """Return the attribute with the given name.

        If no such attribute exists, an exception is raised unless a default
        was provided (which is then returned).

        """
        if default is NoDefault:
            return self._attrsByName[name]
        else:
            return self._attrsByName.get(name, default)

    def lookupAttr(self, name, default=NoDefault):
        if self._allAttrs is None:
            # happens sometimes during awakeFromRead()
            self._makeAllAttrs()
        if default is NoDefault:
            return self._allAttrsByName[name]
        else:
            return self._allAttrsByName.get(name, default)

    def allAttrs(self):
        """Returns a list of all attributes.

        This includes those attributes that are inherited and derived.
        The order is top down; that is, ancestor attributes come first.

        """
        return self._allAttrs

    def allDataAttrs(self):
        """Return a list of all data attributes.

        This includes those attributes that are inherited.
        The order is top down; that is, ancestor attributes come first.
        Derived attributes are not included in the list.

        """
        return self._allDataAttrs

    def allDataRefAttrs(self):
        """Return a list of all data referencing attributes.

        Returns a list of all data attributes that are obj refs or lists,
        including those that are inherited.

        """
        return self._allDataRefAttrs


    ## Klasses access ##

    def klasses(self):
        return self._klasses

    def setKlasses(self, klasses):
        """Set the klasses object of the klass. This is the klass' owner."""
        self._klasses = klasses

    def model(self):
        return self._klasses.model()


    ## Other access ##

    def isAbstract(self):
        return self._isAbstract

    def pyClass(self):
        """Return the Python class that corresponds to this class.

        This request will even result in the Python class' module being
        imported if necessary. It will also set the Python class
        attribute _mk_klass which is used by MiddleKit.Run.MiddleObject.

        """
        if self._pyClass == False:
            if self._klassContainer._model._havePythonClasses:
                self._pyClass = self._klassContainer._model.pyClassForName(self.name())
                assert self._pyClass.__name__ == self.name(), (
                    'self.name()=%r, self._pyClass=%r' % (self.name(), self._pyClass))
                self._pyClass._mk_klass = self
            else:
                self._pyClass = None
        return self._pyClass

    def backObjRefAttrs(self):
        """Return a list of all potentially referencing attributes.

        Returns a list of all ObjRefAttrs in the given object model that can
        potentially refer to this object. The list does NOT include attributes
        inherited from superclasses.

        """
        if self._backObjRefAttrs is None:
            backObjRefAttrs = []
            # Construct targetKlasses = a list of this object's klass and all superklasses
            targetKlasses = {}
            super = self
            while super:
                targetKlasses[super.name()] = super
                super = super.superklass()
            # Look at all klasses in the model
            for klass in self._klassContainer._model.allKlassesInOrder():
                # find all ObjRefAttrs of klass that refer to one of our targetKlasses
                for attr in klass.attrs():
                    if not attr.get('isDerived', False):
                        if (isinstance(attr, ObjRefAttr)
                                and attr.targetClassName() in targetKlasses):
                            backObjRefAttrs.append(attr)
            self._backObjRefAttrs = backObjRefAttrs
        return self._backObjRefAttrs

    def setting(self, name, default=NoDefault):
        """Return the value of a particular configuration setting taken from the model."""
        return self._klassContainer.setting(name, default)


    ## As string ##

    def asShortString(self):
        return '<Klass, %s, %x, %d attrs>' % (self._name, id(self), len(self._attrsList))

    def __str__(self):
        return self.asShortString()


    ## As a dictionary key (for "set" purposes) ##

    def __hash__(self):
        return hash(self.name()) # | hash(self.model().name())

    def __cmp__(self, other):
        if other is None:
            return 1
        if not isinstance(other, Klass):
            return 1
        if self.model() is not other.model():
            value = cmp(self.model().name(), other.model().name())
            if value == 0:
                value = cmp(self.name(), other.name())
            return value
        return cmp(self.name(), other.name())


    ## Warnings ##

    def printWarnings(self, out):
        for attr in self.attrs():
            attr.printWarnings(out)


    ## Model support ##

    def willBuildDependencies(self):
        """Preps the klass for buildDependencies()."""
        self._dependencies = set() # who self depends on
        self._dependents = set() # who depends on self

    def buildDependencies(self):
        """Build dependencies of the klass.

        A klass' immediate dependencies are its ancestor classes (which may
        have auxilliary tables such as enums), the target klasses of all its
        obj ref attrs and their descendant classes.

        """
        if self._dependents is not None:
            # already done
            pass
        klass = self.superklass()
        while klass is not None:
            self._dependencies.add(klass)
            klass._dependents.add(self)
            klass = klass.superklass()
        from MiddleKit.Core.ObjRefAttr import ObjRefAttr
        for attr in self.allAttrs():
            if isinstance(attr, ObjRefAttr):
                klass = attr.targetKlass()
                if klass is not self and attr.boolForKey('Ref', True):
                    self._dependencies.add(klass)
                    klass._dependents.add(self)
                    for klass in klass.descendants():
                        self._dependencies.add(klass)
                        klass._dependents.add(self)

    def recordDependencyOrder(self, order, visited, indent=0):
        # print '%srecordDependencyOrder() for %s' % (' '*indent*4, self.name())
        if self in visited:
            return
        visited.add(self)
        for klass in self._dependencies:
            klass.recordDependencyOrder(order, visited, indent+1)
        order.append(self)