"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/dulcinea/lib/base.py $
$Id: base.py 23388 2004-02-03 00:14:30Z rmasse $

Provides universal base classes which all classes in the Dulcinea
library should subclass.  Currently there are two such
universal base classes:
    DulcineaPersistent    for classes whose instances should always be
                          first-class, persistent database objects
    DulcineaBase          for classes whose instances may be transient,
                          but can still be persisted if they happen to
                          be attached to another persistent object

"""

import new
from types import ClassType
from ZODB import DB, utils
from ExtensionClass import ExtensionClass
from Persistence import Persistent


# If you're wondering which of DulcineaBase and DulcineaPersistent to use
# for some class, then use DulcineaPersistent: that will make instances of
# that class "first-class" database objects, subject to all of ZODB's
# machinery.  Usually this is a good thing, but keep in mind that deriving
# from DulcineaPersistent means your class is an ExtensionClass, which has
# a number of ramifications:
#   * the '__radd__()' (and, presumably, '__rmul__()', '__rsub__()',
#     '__rdiv__()') protocol doesn't work: this is a bug in
#     ExtensionClass.
#   * the '__cmp__()' protocol may not work as expected (unsure on
#     the details here)
#
# In short, be leery of ExtensionClass (and thus DulcineaPersistent) for
# any class that needs to do operator overloading: this stuff is rather
# flaky for ExtensionClasses.  Conversely, only do operator overloading if
# it's really necessary and a big win, since it means that objects of
# classes with operator overloads won't be able to be first-class database
# objects, at least until the '__radd__()' bug in ExtensionClass is fixed.


class DulcineaBase:
    """The standard base class for classes whose instances do not need
    to be first-class database objects.  Instances of DulcineaBase can still
    be stored in the database if they happen to be attached to
    other persistent objects, but they don't really participate in
    persistence -- they just come along for the ride.

    Instance attributes: none

    """

    # Our standard __repr__(): like Python's, except it also puts the
    # str() of the object in the repr() and uses bare class name
    # (no full module path).
    def __repr__ (self):
        if hasattr(self, '__str__'):
            strvalue = ": " + str(self)
        else:
            strvalue = ""

        return ("<%s at %08x%s>" % (self.__class__.__name__,
                                   id(self), strvalue))

    def copy (self):
        klass = self.__class__
        if type(klass) is ExtensionClass:
            newself = klass.__basicnew__()
        elif type(klass) is ClassType:
            newself = new.instance(self.__class__, {})
        elif isinstance(klass, object):
            newself = klass.__new__(klass)
        else:
            raise RuntimeError, "unknown type of class object: %s" % `klass`

        for attr in self.__dict__.keys():
            if attr[0:3] not in ("_p_", "_v_"):
                val = getattr(self, attr)
                setattr(newself, attr, val)

        return newself

    def __copy__ (self):
        return self.copy()


class DulcineaPersistent (DulcineaBase, Persistent):
    """The standard base class for classes whose instances should be
    first-class database objects that participate fully in the
    persistence mechanism.  Instances of DulcineaPersistent still have to be
    referenced by an existing persistent object, i.e. they must
    ultimately connect back to one of the database root objects.

    Instance attributes: none
    """

    # used below by __repr__ to determine if __str__ has been overridden
    _default__str__ = getattr(Persistent, "__str__", None)

    def __repr__ (self):
        str_method = getattr(self.__class__, '__str__', None)
        if str_method is not self.__class__._default__str__:
            # subclass overrode __str__ method, it's safe to call str()
            strvalue = ": " + str(self)
        else:
            # default __str__ falls back to __repr__ so don't call it
            strvalue = ""
        if self._p_oid:                 # brand-new objects don't have OID yet
            oid = "%016x" % utils.U64(self._p_oid)
        else:
            oid = "%08x" % id(self)
        return "<%s at %s%s>" % (self.__class__.__name__, oid, strvalue)

    def __hash__ (self):
        # 'object' provides a default hash method but ExtensionClass does not
        return id(self)


from Persistence import PersistentMapping
def _persistent_map_copy(self):
    return self.__class__(self.data)
PersistentMapping.__copy__ = _persistent_map_copy
PersistentMapping.copy = _persistent_map_copy

# XXX ZODB4 drops support for old _container attribute.  We should
# re-commit all PersistentMapping objects and remove this code.
def _persistent_map_setstate(self, dict):
    self.__dict__.update(dict)
    if hasattr(self, '_container'):
        self.data = self._container
        del self._container
PersistentMapping.__setstate__ = _persistent_map_setstate






