"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/dulcinea/lib/permission.py $
$Id: permission.py 23445 2004-02-06 16:14:55Z dbinger $

"""

from types import NoneType, DictType
from dulcinea.base import PersistentMapping
from dulcinea.persistent_list import PersistentList
from dulcinea.typeutils import typecheck
from dulcinea.spec import mapping, no, instance, sequence

class PermissionManager:

    """
    Class attributes:
      valid_permissions : { permission:string : description:string }
        Subclasses should set this value to list and describe the permissions
        they may grant.

    Instance attributes:
      permissions : PersistentMapping
        This mapping records the circumstances under which specific permissions
        are granted.
        If permissions['happy'].items()
             includes (other_permission_manager, ['rich']),
        it means that this permission manager grants 'happy' to any object
        for which the other_permission_manager grants 'rich'.
        If permissions['happy'].items() includes
          (non_permission_manager, None),
        it means that this permission manager grants 'happy' to
        non_permission_manager, with no other prerequisite.

    """

    permissions_is = mapping({str: mapping({
        instance('PermissionManager'): sequence(str, PersistentList),
        no(instance('PermissionManager')): None }, PersistentMapping)},
                             PersistentMapping)

    valid_permissions = None

    def __init__(self):
        self.clear_permissions()

    def clear_permissions(self):
        self.permissions = PersistentMapping()
        for permission in self.valid_permissions:
            self.permissions[permission] = PersistentMapping()

    def set_permission(self, permission, object, prerequisite=None):
        typecheck(permission, str)
        if prerequisite is None:
            assert not isinstance(object, PermissionManager)
            self.permissions[permission][object] = None
            return

        typecheck(prerequisite, str)
        typecheck(object, PermissionManager)

        if not self.permissions[permission].has_key(object):
            self.permissions[permission][object] = PersistentList()

        prerequisites = self.permissions[permission][object]
        if prerequisite not in prerequisites:
            prerequisites.append(prerequisite)

    def set_permission_items(self, permission, items):
        self.permissions[permission].clear()
        for object, prerequisite in items:
            self.set_permission(permission, object, prerequisite=prerequisite)

    def remove_permission(self, permission, object):
        typecheck(permission, str)
        del self.permissions[permission][object]

    def get_valid_permissions(self):
        return self.valid_permissions

    def get_permission_items(self, permission):
        for object, prerequisites in self.permissions[permission].items():
            if prerequisites is None:
                yield (object, None)
            else:
                for prerequisite in prerequisites:
                    yield (object, prerequisite)

    def get_direct_grantees(self, permission):
        return [ grantee
                 for grantee, prerequisite
                 in self.get_permission_items(permission)
                 if prerequisite is None ]

    def get_permissions(self, object):
        return [ permission
                 for permission in self.permissions.keys()
                 if self.grants(permission, object) ]

    def directly_grants(self, permission, object):
        """(permission:str, object)
            -> boolean

        Test if permission is granted directly to object.
        This is much faster than grants(), and often sufficient.
        """
        return self.permissions.get(permission, {}).get(object, 1) is None

    def grants(self, permission, object, _covered=None):
        """(permission:str, object, _covered:dict=None)
            -> None|[(str,PermissionManager)+]

        Test if permission is granted to object, returning the
        list of (permission, PermissionManager) pairs that explains
        a positive result.  Return None if the permission is
        not granted.
        """
        if permission not in self.valid_permissions:
            return None
        assert type(_covered) in (NoneType, DictType)
        # If (object, None) is an item in self.permissions[permission].
        if self.permissions[permission].get(object, 1) is None:
            # This list explains that permission is granted by self.
            return [(permission, self)]
        else:
            covered = _covered or {}
            if (permission, self) in covered:
                return None
            for permission_manager, prerequisite in self.get_permission_items(
                permission):
                if prerequisite is None:
                    continue
                # Guard against cycles.
                covered[(permission, permission_manager)] = 1
                grants = permission_manager.grants(prerequisite, object,
                                                   _covered=covered)
                if grants:
                    if grants[0] != (prerequisite, permission_manager):
                        # Add to the explanation a clause that says that
                        # prerequisite was granted by permission_manager.
                        grants = [(prerequisite, permission_manager)] + grants
                    return grants
                del covered[(permission, permission_manager)] # Clean up.
        return None



