########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Rdf/Drivers/Memory.py,v 1.21 2005/03/04 16:32:49 mbrown Exp $
"""
A non-persistent RDF model driver

Copyright 2005 Fourthought, Inc. (USA).
Detailed license and copyright information: http://4suite.org/COPYRIGHT
Project home, documentation, distributions: http://4suite.org/
"""

import re

from Ft.Rdf import Model, RdfException
from Ft.Rdf import OBJECT_TYPE_RESOURCE, OBJECT_TYPE_UNKNOWN
from Ft.Rdf.Drivers import PROPERTIES
from Ft.Rdf.Statement import Statement


def InitializeModule():
    """
    Post-import hook to initialize module's runtime variables that are not
    required at import time, but will be needed before the module-level
    functions are called.
    """
    pass

def CreateDb(dbName, modelName='default'):
    return DbAdapter(dbName, modelName)

#Identical function for Memory driver
GetDb = CreateDb

def DestroyDb(dbName, modelName='default'):
    """
    Does nothing in this driver (there is no persistent data store)
    """
    pass

def ExistsDb(dbName, modelName='default'):
    """
    Returns False in this driver (there is no persistent data store)
    """
    return False

def ForceUnicode(*args):
    #Especially needed because of what seems to be a weird bug in Python 2.2
    #that can cause failed compares between identical strings & unicode objs
    #in certain obscure cases we run into in this driver (nested functions, etc.)
    return tuple([ a and unicode(a) or a for a in args ])


class DbAdapter:
    def __init__(self, name, modelName='default'):
        self._acl = {}
        self._statements = {modelName: []}
        self._bound = {}
        self._modelName = modelName
        self.props = {PROPERTIES.OBJECT_TYPE_SUPPORTED: 1}
        return

    ### Transactional Interface ###

    def begin(self):
        """
        Does nothing in this driver (transactions are not supported)
        """
        return

    def commit(self):
        """
        Does nothing in this driver (transactions are not supported)
        """
        return

    def rollback(self):
        """
        Does nothing in this driver (transactions are not supported)
        """
        return

    ### Operations ###

    def add(self, statements):
        # stored statements -> statement tuple
        self._statements[self._modelName].extend(statements)
        return

    def remove(self, statements):
        for s in statements:
            self.removePattern(s[0], s[1], s[2], s[3], s[4], {})
        return

    def removePattern(self, subject, predicate, object_, statementUri,
                      scope, flags):
        (subject, predicate, object_, statementUri, scope) = ForceUnicode(
            subject, predicate, object_, statementUri, scope
            )
        # we use not not because '' and None are equivalent to us
        command = g_removes[(not not subject,
                             not not predicate,
                             not not object_,
                             not not statementUri,
                             not not scope,
                             )]
        operators = (g_operators[flags.get('subjectFlags')](subject or ''),
                     g_operators[flags.get('predicateFlags')](predicate or ''),
                     g_operators[flags.get('objectFlags')](object_ or ''),
                     g_operators[flags.get('statementUriFlags')](statementUri or ''),
                     g_operators[flags.get('scopeFlags')](scope or ''),
                     )
        self._statements[self._modelName] = filter(lambda s, f=command, o=operators:
                                             not f(o, s),
                                             self._statements[self._modelName])
        return

    ### Queries

    def properties(self, scope):
        stmts = self.complete(None, None, None, None, scope, {})
        pdict = {}
        for s in stmts: pdict[s[1]] = None
        return pdict.keys()

    def resources(self, scope):
        stmts = self.complete(None, None, None, None, scope, {})
        rdict = {}
        for s in stmts:
            rdict[s[0]] = None
            rdict[s[1]] = None
        return rdict.keys()

    def complete(self, subject, predicate, object_, statementUri, scope,
                 flags):
        (subject, predicate, object_, statementUri, scope) = ForceUnicode(
            subject, predicate, object_, statementUri, scope
            )
        command = g_completes[(not not subject,
                               not not predicate,
                               not not object_,
                               not not statementUri,
                               not not scope,
                               )]
        operators = (g_operators[flags.get('subjectFlags')](subject or ''),
                     g_operators[flags.get('predicateFlags')](predicate or ''),
                     g_operators[flags.get('objectFlags')](object_ or ''),
                     g_operators[flags.get('statementUriFlags')](statementUri or ''),
                     g_operators[flags.get('scopeFlags')](scope or ''),
                     )

        return filter(lambda s, f=command, o=operators:
                      f(o, s),
                      self._statements[self._modelName])

    def size(self, scope):
        if scope:
            return reduce(lambda r, s, u=scope:
                          r + (s[4] == u),
                          self._statements[self._modelName], 0)
        else:
            return len(self._statements[self._modelName])

    def contains(self, subject, predicate, object_, statementUri, scope,
                 flags):
        (subject, predicate, object_, statementUri, scope) = ForceUnicode(
            subject, predicate, object_, statementUri, scope
            )
        command = g_contains[(not not subject,
                              not not predicate,
                              not not object_,
                              not not statementUri,
                              not not scope,
                              )]
        operators = (g_operators[flags.get('subjectFlags')](subject or ''),
                     g_operators[flags.get('predicateFlags')](predicate or ''),
                     g_operators[flags.get('objectFlags')](object_ or ''),
                     g_operators[flags.get('statementUriFlags')](statementUri or ''),
                     g_operators[flags.get('scopeFlags')](scope or ''),
                     )

        size = reduce(lambda r, s, f=command, o=operators:
                      r + (f(o, s) and 1 or 0),
                      self._statements[self._modelName], 0)
        return size > 0

    def bind(self, object_, name, scope):
        if scope not in self._bound:
            self._bound[scope] = {}
        self._bound[scope][name] = object_
        return

    def unbind(self, name, scope):
        if scope not in self._bound:
            return
        info = self._bound[scope].get(name)
        if info:
            del self._bound[scope][name]
        return

    def lookup(self, name, scope):
        if scope not in self._bound:
            return None
        return self._bound[scope].get(name)

    def keys(self, scope):
        if not scope:
            result = []
            for bindings in self._bound.values():
                result.extend(bindings.keys())
            return result
        else:
            if self._bound.has_key(scope):
                result = self._bound[scope].keys()
            else:
                result = []
        return result

    def has_key(self, name, scope):
        if not scope:
            result = reduce(lambda a, b, n=name:
                            a + b.has_key(n),
                            self._bound.values(), False)
        else:
            if self._bound.has_key(scope):
                result = name in self._bound[scope]
            else:
                result = False

        return result


    ## Utilities for performance, primarily in Versa ##
    def subjectsFromPredAndObjs(self, predicate, objects, scope):
        """
        Get a list of subjects with the given predicate and objects
        """
        resDict = {}
        if predicate is not None:
            for object_ in objects:
                for s in self.complete(None, predicate, object_, None,
                                       scope, {}):
                    resDict[s[0]] = True
        else:
            #FIXME: for purposes of Versa, we should not be using null as a wildcard, it seems
            for s in self._statements[self._modelName]:
                resDict[s[0]] = True
        return resDict.keys()

    def subjectsFromPredsAndObj(self, predicates, object_, scope):
        """
        Get a list of subjects with the given predicates and object
        """
        resDict = {}
        if object_ is not None:
            for p in predicates:
                for s in self.complete(None, p, object_, None, scope, {}):
                    resDict[s[0]] = True
        else:
            #FIXME: for purposes of Versa, we should not be using null as a wildcard, it seems
            for s in self._statements[self._modelName]:
                resDict[s[0]] = True
        return resDict.keys()

    def objectsFromSubAndPreds(self, subject, predicates, scope):
        """
        Get a list of objects with the given predicates and subject
        """
        resDict = {}
        if subject is not None:
            for predicate in predicates:
                for s in self.complete(subject, predicate, None, None,
                                       scope, {}):
                    resDict[(s[2], s[5])] = True
        else:
            #FIXME: for purposes of Versa, we should not be using null as a wildcard, it seems
            for s in self._statements[self._modelName]:
                resDict[(s[2], s[5])] = True
        return resDict.keys()

    def isResource(self, res):
        #return [ s for s in self._statements[self._modelName] if res == s[0] or res == s[1] ]
        r = False
        for s in self._statements[self._modelName]:
            if res == s[0]:
                r = True
                break
        return r


def _regexCompile(cmd):
    try:
        return re.compile(cmd).match
    except re.error, e:
        raise RdfException(RdfException.INVALID_REGEX_STATEMENT, cmd, str(e))

def _regexICCompile(cmd):
    try:
        return re.compile(cmd,re.IGNORECASE).match
    except re.error, e:
        raise RdfException(RdfException.INVALID_REGEX_STATEMENT, cmd, str(e))


g_operators = {
    None : lambda s: lambda a, b=s: a == b,
    Model.NORMAL : lambda s: lambda a, b=s: a == b,
    Model.IGNORE_CASE : lambda s: lambda a, b=s.lower(): a.lower() == b,
    Model.REGEX : _regexCompile,
    Model.IGNORE_CASE + Model.REGEX : _regexICCompile,
    }

g_completes = {}
g_removes = {}
g_contains = {}

for bits in range(32):
    key = (bits & 16 > 0,
           bits & 8 > 0,
           bits & 4 > 0,
           bits & 2 > 0,
           bits & 1)

    # where f = comparison function for each item of a statement tuple
    #       s = the statement tuple

    parts = []
    if bits & 16:
        parts.append('f[0](s[0])')
    if bits & 8:
        parts.append('f[1](s[1])')
    if bits & 4:
        parts.append('f[2](s[2])')
    if bits & 2:
        parts.append('f[3](s[3])')
    if bits & 1:
        parts.append('f[4](s[4])')

    if parts:
        body = ' and '.join(parts)
    else:
        body = '1'

    g_completes[key] = eval('lambda f, s: %s' % body)
    g_removes[key] = eval('lambda f, s: %s' % body)

    if parts:
        body = ' and '.join(parts)
    else:
        body = '1'
    g_contains[key] = eval('lambda f, s: %s' % body)
