import sys
import types
from kjbuckets import kjSet
import searchreplace as sr
class ObjectDomain(object):
    """
    A container holding all of the regular runtime objects
    that we can map our MetaObjects onto.
    """
    
    def __init__(self):
        self.modules = kjSet()
    def addModuleToDomain(self, module):
        self.modules.add(module)
    def removeModuleFromDomain(self, module):
        del self.modules[module]
        
class SysModulesObjectDomain(ObjectDomain):
    """
    Container for all of the objects in all the modules
    loaded into the python runtime
    """
    
    def __init__(self):
        ObjectDomain.__init__(self)
        for module in sys.modules.values():
            self.modules.add(module)
class MetaObjectSystem(object):
    """
    Central Object of the Meta Object Programming System. A MetaObjectSystem
    defines a domain of candidate objects as well as a set of MetaObjects
    to map to this domain. It binds and unbinds the MetaObjects to the
    functions and structures within the object domain based on different criteria
    \sa ObjectDomain
    \sa MetaCallableObject
    \sa MetaStructuralObject
    \sa SelectionCriteria
    """
    def __init__(self, metaobjectDomain=None):
        self.mcos = kjSet()
        self.msos = kjSet()
        self.metaobjectDomain = metaobjectDomain
        if self.metaobjectDomain is None:
            self.metaobjectDomain = SysModulesObjectDomain()
        
    def addMetaCallableObject(self, mco):
        self.mcos.add(mco)
        
        the MetaCallableObject, and add the matches to the
        
        for selection in mco.selections:
            criteria = selection.criteria
            for module in self.getModules():
                callsites = self.getCallInterceptorsForModule(criteria, module)
                for cs in callsites:
                    selection.matches.add(cs)
    
    def removeMetaCallableObject(self, mco):
        del self.mcos[mco]
        for selection in mco.selections:
            mco.removeSelection(selection)
            
            
    def addMetaStructuralObject(self, mso):
        """
        add in any mixins (introductions) of the
        mso to qualifying objects
        """
        for selection in mso.selections:
            criteria = selection.criteria
            for module in self.getModules():
                msosites = self.getMixinSitesForModule(criteria, module)
                for mi in msosites:
                    selection.matches.add(mi)
    def removeMetaStructuralObject(self, mso):
        """
        remove any mixins (introductions) from
        the selections in the mso
        """
        for selection in mso.selections:
            mso.removeSelection(selection)
                    
    
    def getCallInterceptorsForModule(self, criteria, module):
        nameFilter = [sr.STD_IGNORE]
        objectFilter = [criteria]
        ctx = sr.SearchContext(nameFilter, objectFilter)
        callsites = []
        matches = sr.recurseSelectMembers(module, ctx)
        for match in matches:
            
            if hasattr(match, '_mop_callsite'):
                callsites.append(match)
            else:
                callsites.append(CallInterceptor(match))
        return callsites
    
    def getMixinSitesForModule(self, criteria, module):
        nameFilter = [sr.STD_IGNORE]
        objectFilter = [criteria]
        ctx = sr.SearchContext(nameFilter, objectFilter)
        mixinSites = []
        matches = sr.recurseSelectMembers(module, ctx)
        for match in matches:
            mixinSites.append(match)
            
        return mixinSites
    def getModules(self):
        return self.metaobjectDomain.modules.items()
    
class Selection(object):
    """
    Selection
    
    This class represents a set of objects taken out of the
    object domain that match a set of criteria. This class
    is analogous to a combination of SELECT statement and the resulting
    ResultSet in RDBMS-land
    """
    def __init__(self, selectionCriteria, matches=None):
        
        if matches is None:
            matches = kjSet()
            
        self.matches = matches
        self.criteria = selectionCriteria
class SelectionCriteria(object):
    """
    SelectionCriteria
    
    Pattern used to map a selection to a set of objects in the object domain.
    Can use attributes such as namespace (i.e. com.blah.mypackage.mymodule.*),
    class name (mypackage.mymodule.MyClass),
    method name (i.e. set*, get*, execute(), blah() ),
    attribute access (__getattr__, someFieldName or __setattr__ someFieldName),
    or using tagged metadata loaded elsewhere (@mutable-method, @security-required)
    """
    def __init__(self):
        self.andClauses = []
        self.orClauses = []
        
    def AND(self, andClause):
        self.andClauses.append(andClause)
    def OR(self, orClause):
        self.orClauses.append(orClause)
    def match(self, ctx):
        """
        If ANY of the orClauses match, return True
        else if ALL of the andClauses match, return True
        else return False
        """
        for oc in self.orClauses:
            if oc.match(ctx):
                return True
        for cc in self.andClauses:
            if not cc.match(ctx):
                return False
        
        return True
    
class CallContext(object):
    """
    CallContext
    Provides a shared context to be used by all the hooks
    attatched to a call-site.
    """
    def __init__(self, callsiteObj):
        self.callsiteObj = callsiteObj
        self.args = []
        self.kwargs = {}
        self.retVal = None
        self.continueCall = True
class CallInterceptor(object):
    """
    CallInterceptor
    
    A CallInterceptor is a given point in object-oriented code. It can be a method call,
    object initialization, variable retrieval...
    
    It provides the place to attatch hooks to the call site.
    A particular CallInterceptor can belong to many selections
    """
    def __init__(self, callableObj):
        self.callableObj = callableObj
        self.__name__ = callableObj.__name__
        self._mop_callsite = True
        self.beforeHooks = None
        self.afterHooks = None
        self.exceptionHooks= None
        self.bind()
        
    def addHook(self, hook):
        if hook.getBeforeAdvice() is not None:
            
            if self.beforeHooks is None:
                self.beforeHooks = []
            self.beforeHooks.append(hook.getBeforeAdvice())
            
        if hook.getAfterAdvice() is not None:
            if self.afterHooks is None:
                self.afterHooks = []
            self.afterHooks.append(hook.getAfterAdvice())
            
        if hook.getExceptionHandler() is not None:
            if self.exceptionHooks is None:
                self.exceptionHooks = []
            self.exceptionHooks.append(hook.getExceptionHandler())
            
    def removeHook(self, hook):
        if hook.getBeforeAdvice() is not None:
            if self.beforeHooks is not None:
                self.beforeHooks.remove(hook.getBeforeAdvice())
                if 0 == len(self.beforeHooks):
                    self.beforeHooks = None
                
        if hook.getAfterAdvice() is not None:
            if self.afterHooks is not None:
                self.afterHooks.remove(hook.getAfterAdvice())
                if 0 == len(self.afterHooks):
                    self.afterHooks = None
                    
        if hook.getExceptionHandler() is not None:
            if self.exceptionHooks is not None:
                self.exceptionHooks.remove(hook.getExceptionHandler())
                if 0 == len(self.exceptionHooks):
                    self.exceptionHooks = None
                
        if None == self.beforeHooks == self.afterHooks == self.exceptionHooks:
            self.unbind()
            
    def bind(self):
        sr.replaceCallable(self.callableObj, self)
        
    def unbind(self):
        sr.replaceInGlobals(self, self.callableObj)
        sr.replaceCallable(self, self.callableObj, False)
        
    def __call__(self, *args, **kwargs):
        
        ctx = CallContext(self)
                
        
        
        a tuple to a list
        ctx.args = list(args)
        ctx.kwargs = kwargs
        
        
        if self.beforeHooks is not None:
            for advice in self.beforeHooks:
                advice(ctx)
                if not ctx.continueCall:
                    return ctx.retVal
                
        
        try:
            retVal = apply(self.callableObj, ctx.args, ctx.kwargs)
        except Exception, ex:
            
            
            
            
            ctx.exception = ex
            if self.exceptionHooks is not None:
                exChain = self.exceptionHooks
                for exhandler in exChain:
                    exhandler(ctx)
            
            
            raise ex
            
        
        ctx.retVal = retVal
        
        if self.afterHooks is not None:
            for advice in self.afterHooks:
                advice(ctx)
                if not ctx.continueCall:
                    return ctx.retVal
                
        return ctx.retVal
class BaseHook(object):
    """
    BaseHook
    
    a callable that will be called when a callsite is reached. It may be
    triggered before the callsite is called, after, or when an exception
    occurs.
    An hook may also wrap around a callsite, meaning that it will be
    able to do some job before and after the callsite executes.
    """
    def getBeforeAdvice(self):
        return getattr(self, "doBeforeAdvice", None)
    def getAfterAdvice(self):
        return getattr(self, "doAfterAdvice", None)
    def getExceptionHandler(self):
        return getattr(self, "handleException", None)
class MetaCallableObject(object):
    """
    MetaCallableObject A class that wraps and scripts (advises) a callable
    object during its call execution. These advises can be simple operations orthoganal
    to the original callable(tracing, debugging), to fully scripting the call path
    as it progresses through the call execution(throw exceptions, alter return values, alter
    incoming parameters, etc...).
     
    """
    def __init__(self):
        self.hooks = []
        self.selections = []
        
    def addSelection(self, selection):
        self.selections.append(selection)
        for hook in self.hooks:
            self.__addHookToSelection(hook, selection)
        
    def removeSelection(self, selection):
        self.selections.remove(selection)
        for hook in self.hooks:
            self.__removeHookFromSelection(hook, selection)
        
    def addHook(self, hook):
        self.hooks.append(hook)
        for selection in self.selections:
            self.__addHookToSelection(hook, selection)
                
    def removeHook(self, hook):
        self.hooks.remove(hook)
        for selection in self.selections:
            self.__removeHookFromSelection(hook, selection)
        
    def __addHookToSelection(self, hook, selection):
        for ci in selection.matches.items():
            ci.addHook(hook)
            
    def __removeHookFromSelection(self, hook, selection):
        for ci in selection.matches.items():
            ci.removeHook(hook)
class MetaStructuralObject(object):
    """
    MetaStructuralObject A class that injects (introduces) structure to another
    objects, usually without the injected object being aware of the injection. The mechanism
    used to do this is using one or more Mixin classes.
    """
    def __init__(self):
        self.mixins = []
        self.selections = []
        
    def addSelection(self, selection):
        self.selections.append(selection)
        for mixin in self.mixins:
            self.__addMixinToSelection(mixin, selection)
        
    def removeSelection(self, selection):
        self.selections.remove(selection)
        for mixin in self.mixins:
            self.__removeMixinFromSelection(mixin, selection)
        
    def addMixin(self, mixin):
        self.mixins.append(mixin)
        for selection in self.selections:
            self.__addMixinToSelection(mixin, selection)
                
    def removeMixin(self, mixin):
        self.mixins.remove(mixin)
        for selection in self.selections:
            self.__removeMixinFromSelection(mixin, selection)
        
    def __addMixinToSelection(self, mixin, selection):
        for cls in selection.matches.items():
            addMixIn(cls, mixin)
            
    def __removeMixinFromSelection(self, mixin, selection):
        for obj in selection.matches.items():
            removeMixIn(obj, mixin)
                
                
advertising or publicity pertaining to distribution of the software
def addMixIn(pyClass, mixInClass, makeAncestor=0):
    """
    Mixes in the attributes of the mixInClass into the pyClass.
    These attributes are typically methods (but don't have to be).
    Note that private attributes, denoted by a double underscore,
    are not mixed in. Collisions are resolved by the mixInClass'
    attribute overwriting the pyClass'. This gives mix-ins the
    power to override the behavior of the pyClass.
    
    After using MixIn(), instances of the pyClass will respond to
    the messages of the mixInClass.
    
    An assertion fails if you try to mix in a class with itself.
    
    The pyClass will be given a new attribute mixInsForCLASSNAME
    which is a list of all mixInClass' that have ever been installed,
    in the order they were installed. You may find this useful for
    inspection and debugging.
    
    You are advised to install your mix-ins at the start up of
    your program, prior to the creation of any objects. This approach
    will result in less headaches. But like most things in Python, you're
    free to do whatever you're willing to live with.  :-)
    
    There is a bitchin' article in the Linux Journal, April 2001,
    Using Mix-ins with Python by Chuck Esterbrook which gives a
    thorough treatment of this topic.
    
    An example, that resides in Webware, is MiddleKit.Core.ModelUser.py,
    which install mix-ins for SQL adapters. Search for MixIn(.
    
    If makeAncestor is 1, then a different technique is employed: the
    mixInClass is made the first base class of the pyClass. You probably
    don't need to use this and if you do, be aware that your mix-in can no
    longer override attributes/methods in pyClass.
    
    This function only exists if you are using Python 2.0 or later. Python 1.5.2
    has a problem where functions (as in aMethod.im_func) are tied to their class,
    when in fact, they should be totally generic with only the methods being tied to
    their class. Apparently this was fixed in Py 2.0.
    """
    assert mixInClass is not pyClass, 'mixInClass = %r, pyClass = %r' % (mixInClass, pyClass)
    if makeAncestor:
        if mixInClass not in pyClass.__bases__:
            pyClass.__bases__ = (mixInClass,) + pyClass.__bases__
    else:
        
        
        baseClasses = list(mixInClass.__bases__)
        baseClasses.reverse()
        for baseClass in baseClasses:
            addMixIn(pyClass, baseClass)
            
        
        attrName = 'mixInsFor'+pyClass.__name__
        mixIns = getattr(pyClass, attrName, None)
        if mixIns is None:
            mixIns = []
            setattr(pyClass, attrName, mixIns)
                
        
        
        
        
        
        mixIns.append(mixInClass)
        
        
        for name in dir(mixInClass):
            if not name.startswith('__'): 
                member = getattr(mixInClass, name)
                if type(member) is types.MethodType:
                    member = member.im_func
                    setattr(pyClass, name, member)
def removeMixIn(pyClass, mixInClass, removeAncestor=0):
    assert mixInClass is not pyClass, 'mixInClass = %r, pyClass = %r' % (mixInClass, pyClass)
    if removeAncestor:
        if mixInClass in pyClass.__bases__:
            pyClass.__bases__ = pyClass.__bases__ - (mixInClass,)
    else:
        
        
        baseClasses = list(mixInClass.__bases__)
        baseClasses.reverse()
        for baseClass in baseClasses:
            removeMixIn(pyClass, baseClass)
            
        
        attrName = 'mixInsFor'+pyClass.__name__
        mixIns = getattr(pyClass, attrName, None)
        if mixIns is not None:
            mixIns.remove(mixInClass)
        
        for name in dir(mixInClass):
            if not name.startswith('__'): 
                member = getattr(mixInClass, name)
                if type(member) is types.MethodType:
                    delattr(pyClass, name)