"""$URL: svn+ssh://svn/repos/trunk/dulcinea/lib/permission.py $
$Id: permission.py 25764 2004-12-14 19:27:32Z rmasse $

"""

from sets import Set
from durus.persistent_dict import PersistentDict
from dulcinea.spec import mapping
from dulcinea.typeutils import typecheck
from dulcinea.base import DulcineaPersistent

class PermissionSet(DulcineaPersistent):
    """
    Instance attributes:
      direct_grantees : { any }
        This permission is granted to any object in this set.
      indirect_grantees : { (pm:PermissionManager, prerequisite:str) }
        This permission is granted if 'pm' grants the permission
        'prerequisite' to the object.
    """

    def __init__(self):
        self.direct_grantees = Set()
        self.indirect_grantees = Set()

    def get_items(self):
        items = [(obj, None) for obj in self.direct_grantees]
        items += list(self.indirect_grantees)
        return items

    def get_direct_grantees(self):
        return list(self.direct_grantees)

    def directly_grants(self, obj):
        return obj in self.direct_grantees

    def grants(self, obj, _covered=None):
        if obj in self.direct_grantees:
            return True
        for permission_manager, prerequisite in self.indirect_grantees:
            if permission_manager.grants(prerequisite, obj, _covered):
                return True
        return False

    def add(self, obj, prerequisite=None):
        typecheck(prerequisite, str, allow_none=1)
        if prerequisite is not None:
            typecheck(obj, PermissionManager)
            self.indirect_grantees.add((obj, prerequisite))
        else:
            self.direct_grantees.add(obj)
        self._p_changed = 1

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 : PersistentDict {permission:str : PermissionSet}
    """

    permissions_is = mapping({str:PermissionSet}, PersistentDict)

    valid_permissions = None

    def __init__(self):
        self.permissions = PersistentDict()

    def set_permission(self, permission, obj, prerequisite=None):
        typecheck(permission, str)
        permission_set = self.permissions.get(permission)
        if permission_set is None:
            self.permissions[permission] = permission_set = PermissionSet()
        permission_set.add(obj, prerequisite)

    def set_permission_items(self, permission, items):
        try:
            del self.permissions[permission]
        except KeyError:
            pass
        for obj, prerequisite in items:
            self.set_permission(permission, obj, prerequisite=prerequisite)

    def get_valid_permissions(self):
        return self.valid_permissions

    def get_permission_items(self, permission):
        typecheck(permission, str)
        permission_set = self.permissions.get(permission)
        if permission_set is None:
            return []
        else:
            return permission_set.get_items()

    def get_direct_grantees(self, permission):
        """(permission: str) -> [obj]

        Return the list of objects that are directly granted 'permission'.
        """
        permission_set = self.permissions.get(permission)
        if permission_set is None:
            return []
        else:
            return permission_set.get_direct_grantees()

    def get_permissions(self, obj):
        """(obj: any) -> [str]

        Return the list of permissions granted to 'obj'
        """
        return [ permission
                 for permission in self.permissions
                 if self.grants(permission, obj) ]

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

        Test if permission is granted directly to object.
        This is much faster than grants(), and often sufficient.
        """
        if permission not in self.permissions:
            return False
        else:
            return self.permissions[permission].directly_grants(obj)

    def grants(self, permission, obj, _covered=None):
        """(permission:str, obj, _covered:Set=None) -> bool

        Return true if 'permission' is granted (directly or indirectly)
        to 'obj'.
        """
        if permission not in self.permissions:
            return False
        else:
            _covered = _covered or Set()
            if self in _covered:
                return False
            else:
                _covered.add(self)
                return self.permissions[permission].grants(obj, _covered)
