########################################################################
#
# File Name: 	        PersistentObject.py
#
# Documentation:	http://docs.ftsuite.com/4ODS/RepositoryObject.py.html
#
"""
Implements the Repository meta-data interface.
WWW: http://4suite.org/4ODS         e-mail: support@4suite.org

Copyright (c) 2000-2001 Fourthought Inc, USA.   All Rights Reserved.
See  http://4suite.org/COPYRIGHT  for license and copyright information
"""

from Ft.Ods import Database
from Ft.Ods.StorageManager.Adapters import Constants
from Ft.Ods.Exception import IntegrityError
from Ft.Ods.Transaction import TransactionNotInProgress
from Ft.Ods import Date
from Ft.Ods import TimeImp
from Ft.Ods import Interval
from Ft.Ods import TimeStamp
from Ft.Ods.Exception import FtodsUnknownError, FtodsUnsupportedError, TransactionNotInProgress

import tempfile,os

class TupleDefinitions:
    TYPE = 1
    READONLY = 2
    LITERAL_REPO_ID = 3
    RELATIONSHIP = 4
    COLLECTION_SUBTYPE = 5
    COLLECTION_KEYTYPE = 6
    COLLECTION_SUBTYPE_REPO_ID = 7 #Used by collections of collections
    COLLECTION_KEYTYPE_REPO_ID = 8 #Used by collections of collections
    OBJECT_ATTRIBUTE_REPO_ID = 9
    COLLECTION_MAXSIZE = 10





class PersistentObject:
#    _objectIds = {}
#    _literalIds = {}
#    _collectionIds = {}
#    _fileNames = {}

    def __init__(self, db,data):
        self.__dict__['_objectIds'] = {}
        self.__dict__['_collectionIds'] = {}
        self.__dict__['_dictionaryIds'] = {}
        self.__dict__['_blobIds'] = {}
        self.__dict__['_literals'] = {}
        self.__dict__['_modifiedAttrs'] = {}
        self.__dict__['_initialized'] = 0
        
        #See if we are in a transaction
        self.__dict__['_db'] = db
        if self._db:
            self.__dict__['_tx'] = self._db.current()
        else:
            self.__dict__['_tx'] = None

        # Initialize member storage
        if data is not None:
            self.__dict__['_initialized'] = 1
            map(lambda typ, name, value, self=self,
                funcs=self._initFuncs:
                funcs[typ[0]](self, name[0], value),
                self._tupleTypes,
                self._tupleNames,
                data
                )

            if self._tx:
                self._tx._4ods_registerModifiedPersistentObject(self)
        elif self._tx:
            self._tx._4ods_registerNewPersistentObject(self)

        return

    def _pseudo_del(self):
        # Remove all circular references from relationships
        for name,d in self._tupleDefinitions.items():
            if d.get(TupleDefinitions.RELATIONSHIP,0):
                if self.__dict__.has_key(name):
                    obj = self.__dict__[name]
                    del self.__dict__[name]
                    obj._pseudo_del()


    def _4ods_getId(self):
        raise FtodsUnknownError(msg='Derived class must override')

    def _4ods_isModified(self):
        #See if any of our structures are modified
        for name in self._tupleNames:
            name = name[0]
            data = self._tupleDefinitions[name]
            if data[TupleDefinitions.TYPE] == Constants.Types.STRUCTURE and self.__dict__.has_key(name):
                if self.__dict__[name]._4ods_isModified():
                    self.__dict__['_modifiedAttrs'][name] = 1

        return len(self.__dict__['_modifiedAttrs']) > 0

    def _4ods_setTransaction(self, newTx, unregister=0):
        if self._tx and unregister:
            self._tx._4ods_unregisterPersistentObject(self)
        self.__dict__['_tx'] = newTx
        if newTx:
            self._tx._4ods_registerPersistentObject(self)

    def _4ods_setBlobId(self, name, bid):
        self.__dict__['_blobIds'][name] = bid

    def delete(self, recurse=0):
        if self._tx:
            self._tx._4ods_deletePersistentObject(self)
        Types = Constants.Types
        lists = Constants.g_listTypes
        for name,data in self._tupleDefinitions.items():
            typ = data[TupleDefinitions.TYPE]
            if data.get(TupleDefinitions.RELATIONSHIP):
                obj = None
                if typ in [Types.POBJECT,Types.ROBJECT]:
                    obj = getattr(self,name)
                    if obj:
                        funcName = 'drop_%s' % name
                        func = getattr(self,funcName)
                        func(self.__dict__[name])
                        if recurse:
                            obj.delete(recurse)
                elif lists[typ]:
                    coll = getattr(self,name)
                    while 1:
                        if not len(coll):
                            break
                        element = coll[0]
                        funcName = 'remove_%s' % name
                        func = getattr(self,funcName)
                        func(element)
                        if recurse:
                            element.delete(recurse)
                    coll.delete()
            elif typ in [Types.ROBJECT,Types.POBJECT]:
                obj = getattr(self,name)
                self.__dict__[name] = None
                if self.__dict__['_objectIds'].has_key(name):
                    del self.__dict__['_objectIds'][name]
                if obj:
                    obj.delete(recurse)
            elif typ == Types.BLOB:
                if self.__dict__['_blobIds'].has_key(name):
                    if self.__dict__['_tx']:
                        self.__dict__['_tx']._4ods_deleteBlob(self,name,self.__dict__['_blobIds'][name])
                    del self.__dict__['_blobIds'][name]
            elif lists[typ]:
                if not self.__dict__.has_key(name) and not self.__dict__['_collectionIds'].has_key(name):
                    continue
                obj = getattr(self,name)
                obj.delete(recurse)
            elif typ == Types.DICTIONARY_COLLECTION:
                if not self.__dict__.has_key(name) and not self.__dict__['_dictionaryIds'].has_key(name):
                    continue
                obj = getattr(self,name)
                obj.delete(recurse)


    def __getattr__(self, name):
        data = self.__class__._tupleDefinitions.get(name)
        if data:
            func = self._getFuncs.get(data[TupleDefinitions.TYPE])
            if not func:
                raise AttributeError(name)
            value = func(self,name)
            if value is not None:
                self.__dict__[name] = value
            return value
        raise AttributeError(name)


    def __setattr__(self, name, value):
        data = self._tupleDefinitions.get(name)
        if data:
            #if data.get(TupleDefinitions.READONLY):
            #    raise AttributeError('readonly %s' % name)
            if self._setFuncs.has_key(data[TupleDefinitions.TYPE]):
                self._setFuncs[data[TupleDefinitions.TYPE]](self,name,value)
                self._modifiedAttrs[name] = 1
            else:
                raise AttributeError('readonly %s' % name)
                
            self._modifiedAttrs[name] = 1
            return
        self.__dict__[name] = value

    def __delattr__(self,name):
        data = self._tupleDefinitions.get(name)
        if data:
            if data.get(TupleDefinitions.RELATIONSHIP):
                #This means drop ro remove
                #Make sure it is loaded
                obj = getattr(self,name)
                if Constants.g_listTypes[data[TupleDefinitions.TYPE]]:
                    func = getattr(self,'remove_%s' % name)
                    for o in self.__dict__[name]:
                        func(o)
                else:
                    o = getattr(self,name)
                    if o:
                        func = getattr(self,'drop_%s' % name)
                        func(o)

            else:
                raise FtodsUnsupportedError(feature="Cannot delete attributes from a Persistent Object")
        else:
            del self.__dict__[name]

    def _4ods_getFullTuple(self):
        rt = map(lambda typ, name, self=self, funcs=self._getDataFuncs:
                 funcs[typ[0]](self, name[0]),
                 self._tupleTypes,
                 self._tupleNames
                 )

        return rt

    def _4ods_getModifiedTuple(self):

        if not self.__dict__['_modifiedAttrs'].keys():
            # No modifications, we're done!
            return None

        rt = map(lambda typ, name, self=self, funcs=self._getDataFuncs,
                 modified=self.__dict__['_modifiedAttrs']:
                 modified.get(name[0],(None,)) and funcs[typ[0]](self, name[0]),
                 self._tupleTypes,
                 self._tupleNames
                 )
        return rt
        
    # Helper for initializing object
    def _4ods_initObject(self, name, value):
        """Initializes an object type for a new object"""
        if value and value[0]:
            self.__dict__['_objectIds'][name] = value[0]

    # Helper for initializing object
    def _4ods_initCollection(self, name, value):
        """Initialize a collection type for a new object"""
        if value and value[0]:
            self.__dict__['_collectionIds'][name] = value[0]

    def _4ods_initDictionary(self, name, value):
        """Initialize a collection type for a new object"""
        if value and value[0]:
            self.__dict__['_dictionaryIds'][name] = value[0]


    # Helper for initializing object
    def _4ods_initBlob(self, name, value):
        """Initialize a file type for a new object"""
        if value and value[0]:
            self.__dict__['_blobIds'][name] = value[0]

    # Helper for initializing object
    def _4ods_initPrimitive(self, name, value):
        """Initialize a primitive type for a new object"""
        if value:
            self.__dict__[name] = value[0]

    # Helper for initializing literals
    def _4ods_initLiteral(self, name, value):
        """Initialize a literal type for a new object"""
        if value:
            self.__dict__['_literals'][name] = value




    # Helper for get[Full|Modified]Tuple
    def _4ods_getObjectData(self, name):
        """The OID of the object member or 0 if unaccessed"""
        if self.__dict__.has_key(name) and self.__dict__[name] is not None:
            return (self.__dict__[name]._4ods_getId(),)
        return (self.__dict__['_objectIds'].get(name, 0),)

            
    # Helper for get[Full|Modified]Tuple
    def _4ods_getCollectionData(self, name):
        """The CID of the collection member or None if unaccessed or empty"""
        if self.__dict__.has_key(name) and self.__dict__[name].cardinality() > 0:
            return (self.__dict__[name]._4ods_getId(),)
        return (self.__dict__['_collectionIds'].get(name,0),)

    def _4ods_getDictionaryData(self, name):
        """The DID of the collection member or None if unaccessed or empty"""
        if self.__dict__.has_key(name) and self.__dict__[name].cardinality() > 0:
            return (self.__dict__[name]._4ods_getId(),)
        return (self.__dict__['_dictionaryIds'].get(name,0),)


    # Helper for get[Full|Modified]Tuple
    def _4ods_getBlobData(self, name):
        """The contents of the blob member or 0 if unaccessed"""
        return (self.__dict__['_blobIds'].get(name, 0),)

    # Helper for get[Full|Modified]Tuple
    def _4ods_getPrimitiveData(self, name):
        """The contents of the primitive type member"""
        return (self.__dict__[name],)

    def _4ods_getLiteralData(self, name):
        """The data of the literal or None"""
        if self.__dict__.has_key(name) and self.__dict__[name]._4ods_isModified():
            return self.__dict__[name]._4ods_getData()
        rt = self.__dict__['_literals'].get(name,None)
        if rt is None:
            return None
        return rt



    #Helper Function for getting an object
    def _4ods_getObject(self,name):
        if self.__dict__['_objectIds'].has_key(name):
            if not self._tx:
                raise TransactionNotInProgress()
            if self._4ods_getOdsType() == Constants.Types.POBJECT:
                obj = self._tx._4ods_getObject(self.__dict__['_objectIds'][name])
            else:
                obj = self._tx._4ods_getRepositoryObject(self.__dict__['_objectIds'][name])
            self.__dict__[name] = obj
            return obj
        return None

    def _4ods_getCollection(self,name):
        if self.__dict__['_collectionIds'].has_key(name):
            if not self._tx:
                raise TransactionNotInProgress()
            col = self._tx._4ods_getCollection(self.__dict__['_collectionIds'][name])
            self.__dict__[name] = col
            return col

        #Create a new one
        from Ft.Ods import Collections
        d = self._tupleDefinitions[name]

        isRel = d.has_key(TupleDefinitions.RELATIONSHIP)
        
        rt = Collections.CollectionFromTypes(self._db,
                                             d[TupleDefinitions.TYPE],
                                             d[TupleDefinitions.COLLECTION_SUBTYPE],
                                             isRelationship = isRel,
                                             subTypeRepositoryId=d.get(TupleDefinitions.COLLECTION_SUBTYPE_REPO_ID,-1)
                                             )

        self.__dict__['_modifiedAttrs'][name] = 1
        self.__dict__[name] = rt
        return rt


    def _4ods_getDictionary(self,name):
        if self.__dict__['_dictionaryIds'].has_key(name):
            if not self._tx:
                raise TransactionNotInProgress()
            col = self._tx._4ods_getDictionary(self.__dict__['_dictionaryIds'][name])
            self.__dict__[name] = col
            return col

        #Create a new one
        from Ft.Ods import Collections
        d = self._tupleDefinitions[name]
        rt = Collections.CollectionFromTypes(self._db,
                                             d[TupleDefinitions.TYPE],
                                             d[TupleDefinitions.COLLECTION_SUBTYPE],
                                             subTypeRepositoryId=d.get(TupleDefinitions.COLLECTION_SUBTYPE_REPO_ID,-1),
                                             keyTypeRepositoryId=d.get(TupleDefinitions.COLLECTION_KEYTYPE_REPO_ID,-1),
                                             keyType = d[TupleDefinitions.COLLECTION_KEYTYPE],
                                             )




        self.__dict__['_modifiedAttrs'][name] = 1
        self.__dict__[name] = rt
        return rt

                                                        
    def _4ods_getBlob(self,name):
        if self.__dict__['_blobIds'].has_key(name) and self.__dict__['_blobIds'][name]:
            if not self._tx:
                raise TransactionNotInProgress()
            return self._tx._4ods_readBlob(self.__dict__['_blobIds'][name])
        return ''
            

    def _4ods_getLiteral(self,name):
        if not self._tx:
            raise TransactionNotInProgress()

        if self.__dict__['_literals'].has_key(name) and self.__dict__['_literals'][name]:
            value = self.__dict__['_literals'][name]
        else:
            value = None

        return self._tx._4ods_createLiteralInstance(self._tupleDefinitions[name][TupleDefinitions.TYPE],
                                                    self._tupleDefinitions[name].get(TupleDefinitions.LITERAL_REPO_ID),
                                                    value)
                                                  
        



    def _4ods_setPrimitive(self,name,value):
        Constants.g_typeCheck[self._tupleDefinitions[name][TupleDefinitions.TYPE]](value,None)
        self.__dict__[name] = value

    def _4ods_setLiteral(self,name,value):
        d = self._tupleDefinitions[name]
        #Special case for fixed length strings
        if d[TupleDefinitions.TYPE] == Constants.Types.FIXEDSTRING and type(value) == type(''):
            getattr(self,name).value = value
            self.__dict__['_literals'][name] = value
            return
        rid = self._tupleDefinitions[name].get(TupleDefinitions.LITERAL_REPO_ID,-1)
        Constants.g_typeCheck[self._tupleDefinitions[name][TupleDefinitions.TYPE]](value,rid)
        self.__dict__['_literals'][name] = value._4ods_getData()
        self.__dict__[name] = value



    #Helper Function for setting an object
    def _4ods_setObject(self,name,value):
        #Is it a relationship or an attribute?
        td = self._tupleDefinitions[name]
        if td.get(TupleDefinitions.RELATIONSHIP):
            if self.__dict__.has_key(name) and Constants.g_listTypes[td[TupleDefinitions.TYPE]]:
                raise FtodsUnsupportedError(feature="Cannot use setattr short cut on N-? relationships")
            func = getattr(self,'form_%s' % name)
            func(value)
        else:
            #Set an object attribute
            if value is None:
                self.__dict__[name] = None
                if self.__dict__['_objectIds'].has_key(name):
                    del self.__dict__['_objectIds'][name]
            else:
                Constants.g_typeCheck[td[TupleDefinitions.TYPE]](value,td[TupleDefinitions.OBJECT_ATTRIBUTE_REPO_ID])
                self.__dict__['_objectIds'][name] = value._4ods_getId()
            self.__dict__[name] = value

    def _4ods_setBlob(self,name,value):
        Constants.g_typeCheck[Constants.Types.STRING](value,None)
        if self.__dict__['_tx']:
            if self.__dict__['_blobIds'].has_key(name) and self.__dict__['_blobIds'][name]:
                self.__dict__['_tx']._4ods_registerModifiedBlob(self.__dict__['_blobIds'][name],value)
            else:
                self.__dict__['_tx']._4ods_registerNewBlob(self,name,value)
        self.__dict__[name] = value



    _initFuncs = {Constants.Types.POBJECT : _4ods_initObject,
                  Constants.Types.ROBJECT : _4ods_initObject,
                  Constants.Types.ENUMERATION : _4ods_initLiteral,
                  Constants.Types.STRUCTURE : _4ods_initLiteral,
                  Constants.Types.BLOB : _4ods_initBlob,
                  Constants.Types.DICTIONARY_COLLECTION : _4ods_initDictionary,
                  }

    _getDataFuncs = {Constants.Types.POBJECT : _4ods_getObjectData,
                     Constants.Types.ROBJECT : _4ods_getObjectData,                     
                     Constants.Types.BLOB : _4ods_getBlobData,
                     Constants.Types.DICTIONARY_COLLECTION : _4ods_getDictionaryData,
                     }

    _getFuncs = {Constants.Types.POBJECT : _4ods_getObject,
                 Constants.Types.ROBJECT : _4ods_getObject,
                 Constants.Types.BLOB : _4ods_getBlob,
                 Constants.Types.DICTIONARY_COLLECTION : _4ods_getDictionary,
                 }


    _setFuncs = {Constants.Types.POBJECT : _4ods_setObject,
                 Constants.Types.ROBJECT : _4ods_setObject,
                 Constants.Types.BLOB : _4ods_setBlob,
                 }
    for t,v in Constants.g_listTypes.items():
        if v:
            if t in [Constants.Types.DICTIONARY_COLLECTION]:
                continue
            _initFuncs[t] = _4ods_initCollection
            _getDataFuncs[t] = _4ods_getCollectionData
            _getFuncs[t] = _4ods_getCollection

    for t,v in Constants.g_primitiveTypes.items():
        if v:
            _initFuncs[t] = _4ods_initPrimitive
            _getDataFuncs[t] = _4ods_getPrimitiveData
            _setFuncs[t] = _4ods_setPrimitive

    for t,v in Constants.g_literalTypes.items():
        if v:
            _initFuncs[t] = _4ods_initLiteral
            _getDataFuncs[t] = _4ods_getLiteralData
            _getFuncs[t] = _4ods_getLiteral
            _setFuncs[t] = _4ods_setLiteral

    del t
    del v
