############################################################################
#
# File Name: 		OifAdapter.py
#
# Documentation:	http://docs.ftsuite.com/4ODS/StorageManager/OifAdapter.py/html
#
"""
Manages Oif input and Output
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
"""

import string, re

from Ft.Ods.Parsers.Oif import OifParser
from Ft.Ods.PersistentObject import TupleDefinitions
from Ft.Ods import StorageManager
from Ft.Ods.StorageManager import Adapters
from Ft.Ods.StorageManager.Adapters import Constants, ClassCache

def unescapeQuotes(chars):
    return string.replace(chars, '&#34;', '"')

def escapeQuotes(chars):
    return '"%s"' % string.replace(chars, '"', '&#34;')

class OifAdapter(StorageManager.StorageManager):
    def __init__(self, dbName):
        self._dbName = dbName
        self._adapter = Adapters.GetAdapter()
        self._manager = Adapters.GetManager()

        self._db = None
        self._open = 0
        return

    def begin(self):
        if self._open:
            raise TransactionInProgress()
        self._open = 1
        self._db = self._manager.connect(self._dbName)
        self._adapter.begin(self._db)
        self._newObjects = {}
        self._newObjects[Constants.ObjectTypes.COLLECTION] = []
        self._newObjects[Constants.ObjectTypes.OBJECT] = []
        return
    
    def abort(self):
        if not self._open:
            raise TransactionNotInProgress()
        self._adapter.abort(self._db)
        self._db = None
        self._open = 0
        self._newObjects = {}
        return

    def commit(self):
        if not self._open:
            raise TransactionNotInProgress()

        for (objectType, objects) in self._newObjects.items():
            for object in objects:
                self.newIdMapping[objectType](self, object)
                
        for (objectType, objects) in self._newObjects.items():
            for object in objects:
                self.newWriteMapping[objectType](self, object)
        
        self._adapter.commit(self._db)
        self._db = None
        self._open = 0
        self._newObjects = {}
        return

    def odmsdump(self, stream):
        if not self._open:
            raise TransactionNotInProgress()
        
        # Get all object ids in the database
        oids = self._adapter.getAllObjectIds(self._db)
        for oid in oids:
            data = self._adapter.getObject(self._db, oid)
            if not data:
                continue
            (klass, data) = data
            tagName = self._4ods_dumpTagName(oid)

            #Get the actual class Name
            mods = string.split(klass.__module__,'.')
            if mods[0] == '__main__':
                import sys
                mods[0] = string.join(string.split(sys.argv[0],'.')[:-1],'.')
            pName = string.join(mods,'.') + '.' + klass.__name__
            objectDecl = "%s %s {" % (tagName,pName)
            stream.write(objectDecl)
            padding = ' '*len(objectDecl)
            first = 1
            tupleData = map(None, klass._tupleNames, klass._tupleTypes, data)
            # All objects have the _oid as their first attribute
            for (name, type, value) in tupleData[1:]:
                # Determine the conversion function
                if Constants.g_listTypes[type]:
                    if value == 0:
                        # Uninitialized
                        continue
                    # A collection or relationship (the same thing)
                    func = self._4ods_dumpCollection
                elif type == Constants.Types.OBJECT:
                    if value == 0:
                        # Uninitialized
                        continue
                    func = self._4ods_dumpTagName
                else:
                    try:
                        func = self._toOifPrimitive[type]
                    except:
                        raise RuntimeError('Unsupported attribute type: %d' % type)

                # Values are comma separated
                if not first:
                    stream.write(',\n%s' % padding)
                else:
                    first = 0

                # Convert the attribute to the OIF equivalent
                stream.write('%s %s' % (name, func(value)))
            # End of object definition
            stream.write('}\n')
        return

    # Helper function for OIF dump
    def _4ods_dumpCollection(self, cid):
        (klass, subtype, data) = self._adapter.getCollection(self._db, cid)
        if subtype == Constants.Types.OBJECT:
            func = self._4ods_dumpTagName
        else:
            try:
                func = self._toOifValues[subtype]
            except:
                raise RuntimeError('Unsupported collection subtype: %d' % subtype)
        values = map(lambda item, func=func: func(item), data)
        if values:
            return '{%s}' % string.join(values, ', ')
        return ''

    # Helper function for OIF dump
    def _4ods_dumpTagName(self, oid):
        names = self._adapter.getObjectBindings(self._db, oid)
        if names:
            tagName = string.replace(names[0], '_', '__')
            tagName = string.replace(tagName, ' ', '_')
        else:
            tagName = "OID%d" % oid
        return tagName

    #Code to load in OIF
    def odmsload(self, stream):
        if not self._open:
            raise TransactionNotInProgress()

        parser = OifParser.OifParser()
        objects = parser.parse(stream.read())

        # Prebuild all the objects (just instances)
        ClassCache.ClassCache().purgeAll()
        oif_info = {}
        for object_def in objects:
            (object, initialized) = self._4ods_loadObject(object_def)
            oif_info[object_def.object_tag] = {'def' : object_def,
                                               'obj' : object,
                                               'loaded' : initialized}

        for object_def in objects:
            tag = object_def.object_tag
            if not oif_info[tag]['loaded']:
                self._4ods_loadInitialization(object_def, oif_info)
                oif_info[tag]['loaded'] = 1
        return

    _4ods_escapes = re.compile('_(?!_)|__')
    _4ods_replaces = {'_' : ' ',
                      '__' : '_',
                      }
    def _4ods_loadObject(self, object_def):
        tag = object_def.object_tag
        if object_def.method == OifParser.InitMethod.FORWARD_DECL:
            if tag[:3] != 'OID':
                # A bound name
                name = self._4ods_escapes.sub(lambda m, d=self._4ods_replaces:
                                              d[m.group()],
                                              object_def.object_tag)
                oid = self._adapter.lookup(self._db, name)
                if not oid:
                    raise RuntimeError('Object not found: %s' % tag)
            else:
                oid = int(tag[3:])
            data = self._adapter.getObject(self._db, oid)
            if not data:
                raise RuntimeError('Object not found: %s' % tag)
            return (data[0](data[1]), 1)
        else:
            path = string.split(object_def.classname, '.')
            modulePath = string.join(path[:-1], '.')
            module = __import__(modulePath, {}, {}, path[-1])
            object = getattr(module, path[-1])(self._db,None)
            if object_def.object_tag[:3] != 'OID':
                # A bound name
                name = self._4ods_escapes.sub(lambda m, d=self._4ods_replaces:
                                              d[m.group()],
                                              object_def.object_tag)
                self._adapter.bind(self._db, pstub._4ods_getId(), name)
            return (object, 0)

    def _4ods_loadCopyData(self, object_tag, oif_info):
        object_def = oif_info[object_tag]['def']
        if oif_info[object_tag]['loaded']:
            # Already loaded, use its values
            object = oif_info[tag]['obj']
            data = map(None, object._tupleNames, object._4ods_getFullTuple())
            method = OifParser.InitMethod.ATTRIBUTE_LIST
        elif object_def.method == OifParser.InitMethod.OBJECT_TAG:
            # Copy initialization again, how deep can we go?
            return self._4ods_loadCopyData(object_def.initialization, oif_info)
        else:
            method = object_def.method
            data = object_def.initialization
        return (method, data)

    def _4ods_loadInitialization(self, object_def, oif_info):
        if object_def.method == OifParser.InitMethod.OBJECT_TAG:
            # Copy initialization
            (method, data) = self._loadCopyData(object_def.initialization, oif_info)
        else:
            method = object_def.method
            data = object_def.initialization

        object = oif_info[object_def.object_tag]['obj']
        if method == OifParser.InitMethod.VALUE_LIST:
            data = map(None, object._tupleNames, data)

        for (name, value) in data:
            if value is None:
                # Uninitialized attribute
                continue
            attrDef = object._tupleDefinitions[name]
            if attrDef.get(TupleDefinitions.COLLECTION_CLASS):
                collection = getattr(object, name)
                if collection._subType == Constants.Types.OBJECT:
                    value = map(lambda o, d=oif_info:
                                 d[o]['obj'],
                                 value)
                for element in value:
                    collection.append(element)
                self._newObjects[Constants.ObjectTypes.COLLECTION].append(collection)
            else:
                if attrDef.get(TupleDefinitions.ENUM_CLASS):
                    enum = object.attrDef[TupleDefinitions.ENUM_CLASS]
                    value = enum.lookup(value)
                elif attrDef[TupleDefinitions.TYPE] == Constants.Types.OBJECT:
                    value = oif_info[value]['obj']
                elif value[0] in ['"', "'"] and value[0] == value[-1]:
                    # strings and chars
                    value = unescapeQuotes(value[1:-1])
                object.__dict__[name] = value
        self._newObjects[Constants.ObjectTypes.OBJECT].append(object)
        return

    _toOifPrimitive = {Constants.Types.UNSIGNED_SHORT : str,
                       Constants.Types.SIGNED_SHORT : str,
                       Constants.Types.SIGNED_LONG : str,
                       Constants.Types.UNSIGNED_LONG : lambda ul: str(long(ul))[:-1],
                       Constants.Types.SIGNED_LONG_LONG : lambda ul: str(long(ul))[:-1],
                       Constants.Types.FLOAT : str,
                       Constants.Types.DOUBLE : str,
                       Constants.Types.BOOLEAN : lambda b: b and 'true' or 'false',
                       Constants.Types.STRING : escapeQuotes,
                       Constants.Types.BLOB : escapeQuotes,
                       }
