from __future__ import absolute_import
import sys

from builtins import range
from .Mixins import _ConfigureComponent, PrintOptions
from .Mixins import _Labelable, _Unlabelable
from .Mixins import _ValidatingParameterListBase
from .ExceptionHandling import *
from .OrderedSet import OrderedSet

class _HardDependency(object):
    """Information relevant for when a hard dependency,
       which uses the * operator, is found"""
    def __init__(self, sequenceName, depSet):
        self.sequenceName = sequenceName
        self.depSet = depSet

class _Sequenceable(object):
    """Denotes an object which can be placed in a sequence"""
    def __init__(self):
        pass
    def __mul__(self,rhs):
        return _SequenceCollection(self,rhs)
    def __add__(self,rhs):
        return _SequenceCollection(self,rhs)
    def __invert__(self):
        return _SequenceNegation(self)
    def _clonesequence(self, lookuptable):
        try:
            return lookuptable[id(self)]
        except:
            raise KeyError("no "+str(type(self))+" with id "+str(id(self))+" found")
    def resolve(self, processDict,keepIfCannotResolve=False):
        return self
    def isOperation(self):
        """Returns True if the object is an operator (e.g. *,+ or !) type"""
        return False
    def isLeaf(self):
        return False
    def _visitSubNodes(self,visitor):
        pass
    def visitNode(self,visitor):
        visitor.enter(self)
        self._visitSubNodes(visitor)
        visitor.leave(self)
    def _appendToCollection(self,collection):
        collection.append(self)
    def _errorstr(self):
        return "A Sequenceable type"

def _checkIfSequenceable(caller, v):
    if not isinstance(v,_Sequenceable):
        typename = format_typename(caller)
        msg = format_outerframe(2)
        msg += "%s only takes arguments of types which are allowed in a sequence, but was given:\n" %typename
        msg +=format_typename(v)
        msg +="\nPlease remove the problematic object from the argument list"
        raise TypeError(msg)

def _checkIfBooleanLogicSequenceable(caller, v):
    if not isinstance(v,_BooleanLogicSequenceable):
        typename = format_typename(caller)
        msg = format_outerframe(2)
        msg += "%s only takes arguments of types which are allowed in a boolean logic sequence, but was given:\n" %typename
        msg +=format_typename(v)
        msg +="\nPlease remove the problematic object from the argument list"
        raise TypeError(msg)

class _BooleanLogicSequenceable(_Sequenceable):
    """Denotes an object which can be used in a boolean logic sequence"""
    def __init__(self):
        super(_BooleanLogicSequenceable,self).__init__()
    def __or__(self,other):
        return _BooleanLogicExpression(_BooleanLogicExpression.OR,self,other)
    def __and__(self,other):
        return _BooleanLogicExpression(_BooleanLogicExpression.AND,self,other)


class _BooleanLogicExpression(_BooleanLogicSequenceable):
    """Contains the operation of a boolean logic expression"""
    OR = 0
    AND = 1
    def __init__(self,op,left,right):
        _checkIfBooleanLogicSequenceable(self,left)
        _checkIfBooleanLogicSequenceable(self,right)
        self._op = op
        self._items = list()
        #if either the left or right side are the same kind of boolean expression
        # then we can just add their items to our own. This keeps the expression
        # tree more compact
        if isinstance(left,_BooleanLogicExpression) and left._op == self._op:
            self._items.extend(left._items)
        else:
            self._items.append(left)
        if isinstance(right,_BooleanLogicExpression) and right._op == self._op:
            self._items.extend(right._items)
        else:
            self._items.append(right)
    def isOperation(self):
        return True
    def _visitSubNodes(self,visitor):
        for i in self._items:
            i.visitNode(visitor)
    def dumpSequencePython(self, options=PrintOptions()):
        returnValue = ''
        join = ''
        operatorJoin =self.operatorString()
        for m in self._items:
            returnValue +=join
            join = operatorJoin
            if not isinstance(m,_BooleanLogicSequenceLeaf):
                returnValue += '('+m.dumpSequencePython(options)+')'
            else:
                returnValue += m.dumpSequencePython(options)
        return returnValue
    def operatorString(self):
        returnValue ='|'
        if self._op == self.AND:
            returnValue = '&'
        return returnValue


class _SequenceLeaf(_Sequenceable):
    def __init__(self):
        pass
    def isLeaf(self):
        return True


class _BooleanLogicSequenceLeaf(_BooleanLogicSequenceable):
    def __init__(self):
        pass
    def isLeaf(self):
        return True

class _SequenceCollection(_Sequenceable):
    """Holds representation of the operations without having to use recursion.
    Operations are added to the beginning of the list and their operands are
    added to the end of the list, with the left added before the right
    """
    def __init__(self,*seqList):
        self._collection = list()
        for s in seqList:
            _checkIfSequenceable(self,s)
            s._appendToCollection(self._collection)
    def __mul__(self,rhs):
        _checkIfSequenceable(self,rhs)
        rhs._appendToCollection(self._collection)
        return self
    def __add__(self,rhs):
        _checkIfSequenceable(self,rhs)
        rhs._appendToCollection(self._collection)
        return self
    def __str__(self):
        sep = ''
        returnValue = ''
        for m in self._collection:
            if m is not None:
                returnValue += sep+str(m)
                sep = '+'
        return returnValue
    def _appendToCollection(self,collection):
        collection.extend(self._collection)

    def dumpSequencePython(self, options=PrintOptions()):
        returnValue = ''
        separator = ''
        for item in self._collection:
            itemDump = item.dumpSequencePython(options)
            if itemDump:
                returnValue += (separator + itemDump)
                separator = '+'
        return returnValue

    def dumpSequenceConfig(self):
        returnValue = self._collection[0].dumpSequenceConfig()
        for m in self._collection[1:]:
            returnValue += '&'+m.dumpSequenceConfig()
        return returnValue

    def directDependencies(self,sortByType=True):
        return findDirectDependencies(self, self._collection,sortByType=sortByType)

    def visitNode(self,visitor):
        for m in self._collection:
            m.visitNode(visitor)

    def resolve(self, processDict,keepIfCannotResolve=False):
        self._collection = [x.resolve(processDict,keepIfCannotResolve) for x in self._collection]
        return self

    def index(self,item):
        return self._collection.index(item)

    def insert(self,index,item):
        self._collection.insert(index,item)
    def _replaceIfHeldDirectly(self,original,replacement):
        didReplace = False
        for i in self._collection:
            if original == i:
                self._collection[self._collection.index(original)] = replacement
                didReplace = True
            elif isinstance(i,_UnarySequenceOperator) and i._has(original):
                didReplace = True
                if replacement is None:
                    self._collection[self._collection.index(i)] = None
                else:
                    self._collection[self._collection.index(i)] = type(i)(replacement)
        if replacement is None:
            self._collection = [ i for i in self._collection if i is not None]
        return didReplace


def findDirectDependencies(element, collection,sortByType=True):
    dependencies = []
    for item in collection:
        # skip null items
        if item is None:
            continue
        # EDFilter, EDProducer, EDAnalyzer, OutputModule
        # should check for Modules._Module, but that doesn't seem to work
        elif isinstance(item, _SequenceLeaf):
            t = 'modules'
        # cms.ignore(module), ~(module)
        elif isinstance(item, (_SequenceIgnore, _SequenceNegation)):
            if isinstance(item._operand, _SequenceCollection):
                dependencies += item.directDependencies(sortByType)
                continue
            t = 'modules'
        # _SequenceCollection
        elif isinstance(item, _SequenceCollection):
            dependencies += item.directDependencies(sortByType)
            continue
        # cms.Sequence
        elif isinstance(item, Sequence):
            if not item.hasLabel_():
                dependencies += item.directDependencies(sortByType)
                continue
            t = 'sequences'
        # cms.Task
        elif isinstance(item, Task):
            if not item.hasLabel_():
                dependencies += item.directDependencies(sortByType)
                continue
            t = 'tasks'
        # cms.ConditionalTask
        elif isinstance(item, ConditionalTask):
            if not item.hasLabel_():
                dependencies += item.directDependencies(sortByType)
                continue
            t = 'conditionaltasks'
        # SequencePlaceholder and TaskPlaceholder do not add an explicit dependency
        elif isinstance(item, (SequencePlaceholder, TaskPlaceholder, ConditionalTaskPlaceholder)):
            continue
        # unsupported elements
        else:
            sys.stderr.write("Warning: unsupported element '%s' in %s '%s'\n" % (str(item), type(element).__name__, element.label_()))
            continue
        dependencies.append((t, item.label_()))
    if sortByType:
        return sorted(set(dependencies), key = lambda t_item: (t_item[0].lower(), t_item[1].lower().replace('_cfi', '')))
    else:
        return dependencies


class _ModuleSequenceType(_ConfigureComponent, _Labelable):
    """Base class for classes which define a sequence of modules"""
    def __init__(self,*arg, **argv):
        self.__dict__["_isFrozen"] = False
        self._seq = None
        if (len(arg) > 1 and not isinstance(arg[1], _TaskBase)) or (len(arg) > 0 and not isinstance(arg[0],_Sequenceable) and not isinstance(arg[0],_TaskBase)):
            typename = format_typename(self)
            msg = format_outerframe(2)
            msg += "The %s constructor takes zero or one sequenceable argument followed by zero or more arguments of type Task. But the following types are given:\n" %typename
            for item,i in zip(arg, range(1,20)):
                try:
                    msg += "    %i) %s \n"  %(i, item._errorstr())
                except:
                    msg += "    %i) Not sequenceable and not a Task\n" %(i)
            if len(arg) > 1 and isinstance(arg[0],_Sequenceable) and isinstance(arg[1], _Sequenceable):
                msg += "Maybe you forgot to combine the sequenceable arguments via '*' or '+'."
            raise TypeError(msg)
        tasks = arg
        if len(arg) > 0 and isinstance(arg[0], _Sequenceable):
            self._seq = _SequenceCollection()
            arg[0]._appendToCollection(self._seq._collection)
            tasks = arg[1:]
        self._isModified = False

        self._tasks = OrderedSet()

        if len(tasks) > 0:
            self.associate(*tasks)
    def associate(self,*tasks):
        for task in tasks:
            if not isinstance(task, _TaskBase):
                raise TypeError("associate only works with objects of type Task")
            self._tasks.add(task)
    def isFrozen(self):
        return self._isFrozen
    def setIsFrozen(self):
        self._isFrozen = True
    def _place(self,name,proc):
        self._placeImpl(name,proc)
    def __imul__(self,rhs):
        _checkIfSequenceable(self, rhs)
        if self._seq is None:
            self.__dict__["_seq"] = _SequenceCollection()
        self._seq+=rhs
        return self
    def __iadd__(self,rhs):
        _checkIfSequenceable(self, rhs)
        if self._seq is None:
            self.__dict__["_seq"] = _SequenceCollection()
        self._seq += rhs
        return self
    def __str__(self):
        v = ExpandVisitor(type(self))
        self.visit(v)
        return v.resultString()

    def dumpConfig(self, options):
        s = ''
        if self._seq is not None:
            s = self._seq.dumpSequenceConfig()
        return '{'+s+'}\n'

    def dumpPython(self, options=PrintOptions()):
        """Returns a string which is the python representation of the object"""
        s = self.dumpPythonNoNewline(options)
        return s + "\n"

    def dumpPythonNoNewline(self, options=PrintOptions()):
        s=''
        if self._seq is not None:
            s =self._seq.dumpSequencePython(options)
        associationContents = set()
        for task in self._tasks:
            if task.hasLabel_():
                associationContents.add(_Labelable.dumpSequencePython(task, options))
            else:
                associationContents.add(task.dumpPythonNoNewline(options))
        for iString in sorted(associationContents):
            if s:
                s += ", "
            s += iString
        if len(associationContents) > 254:
            return 'cms.'+type(self).__name__+'(*['+s+'])'
        return 'cms.'+type(self).__name__+'('+s+')'

    def dumpSequencePython(self, options=PrintOptions()):
        """Returns a string which contains the python representation of just the internal sequence"""
        # only dump the label, if possible
        if self.hasLabel_():
            return _Labelable.dumpSequencePython(self, options)
        elif len(self._tasks) == 0:
            if self._seq is None:
                return ''
            s = self._seq.dumpSequencePython(options)
            if s:
              return '('+s+')'
            return ''
        return self.dumpPythonNoNewline(options)

    def dumpSequenceConfig(self):
        """Returns a string which contains the old config language representation of just the internal sequence"""
        # only dump the label, if possible
        if self.hasLabel_():
            return _Labelable.dumpSequenceConfig(self)
        else:
            # dump it verbose
            if self._seq is None:
                return ''
            return '('+self._seq.dumpSequenceConfig()+')'

    def __repr__(self):
        s = ''
        if self._seq is not None:
           s = str(self._seq)
        return "cms."+type(self).__name__+'('+s+')\n'

    def directDependencies(self,sortByType=True):
        """Returns the list of modules and other entities that are directly used"""
        result = []
        if self._seq:
          result += self._seq.directDependencies(sortByType=sortByType)
        if self._tasks:
          result += findDirectDependencies(self, self._tasks,sortByType=sortByType)
        return result

    def moduleNames(self):
        """Returns a set containing the names of all modules being used"""
        result = set()
        visitor = NodeNameVisitor(result)
        self.visit(visitor)
        return result

    def contains(self, mod):
        visitor = ContainsModuleVisitor(mod)
        self.visit(visitor)
        return visitor.result()

    def copy(self):
        returnValue =_ModuleSequenceType.__new__(type(self))
        if self._seq is not None:
            returnValue.__init__(self._seq)
        else:
            returnValue.__init__()
        returnValue._tasks = OrderedSet(self._tasks)
        return returnValue

    def copyAndExclude(self,listOfModulesToExclude):
        """Returns a copy of the sequence which excludes those module in 'listOfModulesToExclude'"""
        # You can exclude instances of these types EDProducer, EDFilter, OutputModule,
        # EDAnalyzer, ESSource, ESProducer, Service, Sequence, SequencePlaceholder, Task,
        # _SequenceNegation, and _SequenceIgnore.
        # Mostly this is very intuitive, but there are some complications in cases
        # where objects that contain other objects are involved. See the comments
        # for the _MutatingSequenceVisitor.
        v = _CopyAndExcludeSequenceVisitor(listOfModulesToExclude)
        self.visit(v)
        result = self.__new__(type(self))
        result.__init__(v.result(self)[0], *v.result(self)[1])
        return result
    def expandAndClone(self):
        # Name of this function is not very good. It makes a shallow copy with all
        # the subTasks and subSequences flattened out (removed), but keeping all the
        # modules that were in those subSequences and subTasks as well as the top level
        # ones. Note this will also remove placeholders so one should probably
        # call resolve before using this if the sequence contains any placeholders.
        visitor = ExpandVisitor(type(self))
        self.visit(visitor)
        return visitor.result()
    def _postProcessFixup(self,lookuptable):
        self._seq = self._seq._clonesequence(lookuptable)
        return self
    def replace(self, original, replacement):
        """Finds all instances of 'original' and substitutes 'replacement' for them.
           Returns 'True' if a replacement occurs."""
        # This works for either argument being of type EDProducer, EDFilter, OutputModule,
        # EDAnalyzer, ESProducer, ESSource, Service, Sequence, SequencePlaceHolder,
        # Task, _SequenceNegation, _SequenceIgnore. Although it will fail with a
        # raised exception if the replacement actually hits a case where a
        # non-Sequenceable object is placed in the sequenced part of a Sequence
        # or a type not allowed on a Task is put on a Task.
        # There is one special case where we need an explicit check to prevent
        # the algorithm from getting confused, either both or neither can be Tasks
        #
        # Mostly this is very intuitive, but there are some complications in cases
        # where objects that contain other objects are involved. See the comments
        # for the _MutatingSequenceVisitor.

        if (isinstance(original,Task) != isinstance(replacement,Task)):
               raise TypeError("replace only works if both arguments are Tasks or neither")
        if (isinstance(original,ConditionalTask) != isinstance(replacement,ConditionalTask)):
               raise TypeError("replace only works if both arguments are ConditionalTasks or neither")
        v = _CopyAndReplaceSequenceVisitor(original,replacement)
        self.visit(v)
        if v.didReplace():
            self._seq = v.result(self)[0]
            if v.result(self)[1]:
              self._tasks.clear()
              self.associate(*v.result(self)[1])
        return v.didReplace()
    def _replaceIfHeldDirectly(self,original,replacement):
        """Only replaces an 'original' with 'replacement' if 'original' is directly held.
            If another Sequence or Task holds 'original' it will not be replaced."""
        didReplace = False
        if original in self._tasks:
            self._tasks.remove(original)
            if replacement is not None:
                self._tasks.add(replacement)
            didReplace = True
        if self._seq is not None:
            didReplace |= self._seq._replaceIfHeldDirectly(original,replacement)
        return didReplace


    def index(self,item):
        """Returns the index at which the item is found or raises an exception"""
        if self._seq is not None:
            return self._seq.index(item)
        raise ValueError(str(item)+" is not in the sequence")
    def insert(self,index,item):
        """Inserts the item at the index specified"""
        _checkIfSequenceable(self, item)
        if self._seq is None:
            self.__dict__["_seq"] = _SequenceCollection()
        self._seq.insert(index,item)
    def remove(self, something):
        """Remove the first occurrence of 'something' (a sequence or a module)
           Returns 'True' if the module has been removed, False if it was not found"""
        # You can remove instances of these types EDProducer, EDFilter, OutputModule,
        # EDAnalyzer, ESSource, ESProducer, Service, Sequence, SequencePlaceholder, Task,
        # _SequenceNegation, and _SequenceIgnore.
        # Mostly this is very intuitive, but there are some complications in cases
        # where objects that contain other objects are involved. See the comments
        # for the _MutatingSequenceVisitor.
        #
        # Works very similar to copyAndExclude, there are 2 differences. This changes
        # the object itself instead of making a copy and second it only removes
        # the first instance of the argument instead of all of them.
        v = _CopyAndRemoveFirstSequenceVisitor(something)
        self.visit(v)
        if v.didRemove():
            self._seq = v.result(self)[0]
            if v.result(self)[1]:
              self._tasks.clear()
              self.associate(*v.result(self)[1])
        return v.didRemove()
    def resolve(self, processDict,keepIfCannotResolve=False):
        if self._seq is not None:
            self._seq = self._seq.resolve(processDict,keepIfCannotResolve)
        for task in self._tasks:
            task.resolve(processDict,keepIfCannotResolve)
        return self
    def __setattr__(self,name,value):
        if not name.startswith("_"):
            raise AttributeError("You cannot set parameters for sequence like objects.")
        else:
            self.__dict__[name] = value
    #def replace(self,old,new):
    #"""Find all instances of old and replace with new"""
    #def insertAfter(self,which,new):
    #"""new will depend on which but nothing after which will depend on new"""
    #((a*b)*c)  >> insertAfter(b,N) >> ((a*b)*(N+c))
    #def insertBefore(self,which,new):
    #"""new will be independent of which"""
    #((a*b)*c) >> insertBefore(b,N) >> ((a*(N+b))*c)
    #def __contains__(self,item):
    #"""returns whether or not 'item' is in the sequence"""
    #def modules_(self):
    def nameInProcessDesc_(self, myname):
        return myname
    def insertInto(self, parameterSet, myname, decoratedList):
        parameterSet.addVString(True, myname, decoratedList)
    def visit(self,visitor):
        """Passes to visitor's 'enter' and 'leave' method each item describing the module sequence.
        If the item contains 'sub' items then visitor will see those 'sub' items between the
        item's 'enter' and 'leave' calls.
        """
        if self._seq is not None:
            self._seq.visitNode(visitor)
        for item in self._tasks:
            visitor.enter(item)
            item.visit(visitor)
            visitor.leave(item)

class _UnarySequenceOperator(_BooleanLogicSequenceable):
    """For ~ and - operators"""
    def __init__(self, operand):
       self._operand = operand
       if isinstance(operand, _ModuleSequenceType):
           raise RuntimeError("This operator cannot accept a sequence")
       if not isinstance(operand, _Sequenceable):
           raise RuntimeError("This operator cannot accept a non sequenceable type")
    def __eq__(self, other):
        # allows replace(~a, b)
        return type(self) is type(other) and self._operand==other._operand
    def __ne__(self, other):
        return not self.__eq__(other)
    def __hash__(self):
        # this definition implies that self._operand MUST NOT be changed after the construction
        return hash((type(self), self._operand))
    def _findDependencies(self,knownDeps, presentDeps):
        self._operand._findDependencies(knownDeps, presentDeps)
    def _clonesequence(self, lookuptable):
        return type(self)(self._operand._clonesequence(lookuptable))
    def _has(self, op):
        return self._operand == op
    def resolve(self, processDict,keepIfCannotResolve=False):
        return type(self)(self._operand.resolve(processDict,keepIfCannotResolve))
    def isOperation(self):
        return True
    def _visitSubNodes(self,visitor):
        self._operand.visitNode(visitor)
    def decoration(self):
        self._operand.decoration()
    def directDependencies(self,sortByType=True):
        return self._operand.directDependencies(sortByType=sortByType)
    def label_(self):
        return self._operand.label_()

class _SequenceNegation(_UnarySequenceOperator):
    """Used in the expression tree for a sequence as a stand in for the '!' operator"""
    def __init__(self, operand):
        super(_SequenceNegation,self).__init__(operand)
    def __str__(self):
        return '~%s' %self._operand
    def dumpSequenceConfig(self):
        return '!%s' %self._operand.dumpSequenceConfig()
    def dumpSequencePython(self, options=PrintOptions()):
        if self._operand.isOperation():
            return '~(%s)' %self._operand.dumpSequencePython(options)
        return '~%s' %self._operand.dumpSequencePython(options)
    def decoration(self):
        return '!'

class _SequenceIgnore(_UnarySequenceOperator):
    """Used in the expression tree for a sequence as a stand in for the '-' operator"""
    def __init__(self, operand):
        super(_SequenceIgnore,self).__init__(operand)
    def __str__(self):
        return 'ignore(%s)' %self._operand
    def dumpSequenceConfig(self):
        return '-%s' %self._operand.dumpSequenceConfig()
    def dumpSequencePython(self, options=PrintOptions()):
        return 'cms.ignore(%s)' %self._operand.dumpSequencePython(options)
    def decoration(self):
        return '-'

class _SequenceWait(_UnarySequenceOperator):
    """Used in the expression tree for a sequence as a stand in for the '|' operator"""
    def __init__(self, operand):
        super(_SequenceWait,self).__init__(operand)
    def __str__(self):
        return 'wait(%s)' %self._operand
    def dumpSequenceConfig(self):
        return '|%s' %self._operand.dumpSequenceConfig()
    def dumpSequencePython(self, options=PrintOptions()):
        return 'cms.wait(%s)' %self._operand.dumpSequencePython(options)
    def decoration(self):
        return '|'

class _SequenceWaitAndIgnore(_UnarySequenceOperator):
    """Used in the expression tree for a sequence as a stand in for the '+' operator"""
    def __init__(self, operand):
        super(_SequenceWaitAndIgnore,self).__init__(operand)
    def __str__(self):
        return 'wait(ignore(%s))' %self._operand
    def dumpSequenceConfig(self):
        return '+%s' %self._operand.dumpSequenceConfig()
    def dumpSequencePython(self, options=PrintOptions()):
        return 'cms.wait(cms.ignore(%s))' %self._operand.dumpSequencePython(options)
    def decoration(self):
        return '+'

def ignore(seq):
    """The EDFilter passed as an argument will be run but its filter value will be ignored
    """
    if isinstance(seq,_SequenceWait):
        return _SequenceWaitAndIgnore(seq._operand)
    return _SequenceIgnore(seq)

def wait(seq):
    """All modules after this module in the sequence will wait for this module to finish before being scheduled to run.
    """
    if isinstance(seq,_SequenceIgnore):
        return _SequenceWaitAndIgnore(seq._operand)
    return _SequenceWait(seq)

class Path(_ModuleSequenceType):
    def __init__(self,*arg,**argv):
        super(Path,self).__init__(*arg,**argv)
    def _placeImpl(self,name,proc):
        proc._placePath(name,self)

class EndPath(_ModuleSequenceType):
    def __init__(self,*arg,**argv):
        super(EndPath,self).__init__(*arg,**argv)
    def _placeImpl(self,name,proc):
        proc._placeEndPath(name,self)

class FinalPath(_ModuleSequenceType):
    def __init__(self,*arg,**argv):
        super(FinalPath,self).__init__(*arg,**argv)
    def _placeImpl(self,name,proc):
        proc._placeFinalPath(name,self)
    def associate(self,task):
      raise TypeError("FinalPath does not allow associations with Tasks")

class Sequence(_ModuleSequenceType,_Sequenceable):
    def __init__(self,*arg,**argv):
        super(Sequence,self).__init__(*arg,**argv)
    def _placeImpl(self,name,proc):
        proc._placeSequence(name,self)
    def _clonesequence(self, lookuptable):
        if id(self) not in lookuptable:
            #for sequences held by sequences we need to clone
            # on the first reference
            if self._seq is not None:
                clone = type(self)(self._seq._clonesequence(lookuptable))
            else:
                clone = type(self)()
            lookuptable[id(self)]=clone
            lookuptable[id(clone)]=clone
        return lookuptable[id(self)]
    def _visitSubNodes(self,visitor):
        self.visit(visitor)
class SequencePlaceholder(_Sequenceable):
    def __init__(self, name):
        self._name = name
    def _placeImpl(self,name,proc):
        pass
    def __str__(self):
        return self._name
    def insertInto(self, parameterSet, myname):
        raise RuntimeError("The SequencePlaceholder "+self._name
                           +" was never overridden")
    def resolve(self, processDict,keepIfCannotResolve=False):
        if not self._name in processDict:
            #print str(processDict.keys())
            if keepIfCannotResolve:
                return self
            raise RuntimeError("The SequencePlaceholder "+self._name+ " cannot be resolved.\n Known keys are:"+str(processDict.keys()))
        o = processDict[self._name]
        if not isinstance(o,_Sequenceable):
            raise RuntimeError("The SequencePlaceholder "+self._name+ " refers to an object type which is not allowed to be on a sequence: "+str(type(o)))
        return o.resolve(processDict)

    def _clonesequence(self, lookuptable):
        if id(self) not in lookuptable:
            #for sequences held by sequences we need to clone
            # on the first reference
            clone = type(self)(self._name)
            lookuptable[id(self)]=clone
            lookuptable[id(clone)]=clone
        return lookuptable[id(self)]
    def copy(self):
        returnValue =SequencePlaceholder.__new__(type(self))
        returnValue.__init__(self._name)
        return returnValue
    def dumpSequenceConfig(self):
        return 'cms.SequencePlaceholder("%s")' %self._name
    def dumpSequencePython(self, options=PrintOptions()):
        return 'cms.SequencePlaceholder("%s")'%self._name
    def dumpPython(self, options=PrintOptions()):
        result = 'cms.SequencePlaceholder(\"'
        if options.isCfg:
           result += 'process.'
        result += self._name+'\")\n'
        return result


class Schedule(_ValidatingParameterListBase,_ConfigureComponent,_Unlabelable):

    def __init__(self,*arg,**argv):
        super(Schedule,self).__init__(*arg)
        self._tasks = OrderedSet()
        theKeys = list(argv.keys())
        if theKeys:
            if len(theKeys) > 1 or theKeys[0] != "tasks":
                raise RuntimeError("The Schedule constructor can only have one keyword argument after its Path and\nEndPath arguments and it must use the keyword 'tasks'")
            taskList = argv["tasks"]
            # Normally we want a list of tasks, but we let it also work if the value is one Task
            if isinstance(taskList,Task):
                self.associate(taskList)
            else:
                try:
                    # Call this just to check that taskList is a list or other iterable object
                    self.__dummy(*taskList)
                except:
                    raise RuntimeError("The Schedule constructor argument with keyword 'tasks' must have a\nlist (or other iterable object) as its value")
                if taskList:
                    self.associate(*taskList)

    def __dummy(self, *args):
        pass

    def associate(self,*tasks):
        for task in tasks:
            if not isinstance(task, Task):
                raise TypeError("The associate function in the class Schedule only works with arguments of type Task")
            self._tasks.add(task)
    @staticmethod
    def _itemIsValid(item):
        return isinstance(item,Path) or isinstance(item,EndPath) or isinstance(item,FinalPath)
    def copy(self):
        import copy
        aCopy = copy.copy(self)
        aCopy._tasks = OrderedSet(self._tasks)
        return aCopy
    def _place(self,label,process):
        process.setPartialSchedule_(self,label)
    def _replaceIfHeldDirectly(self,original,replacement):
        """Only replaces an 'original' with 'replacement' if 'original' is directly held.
        If a contained Path or Task holds 'original' it will not be replaced."""
        didReplace = False
        if original in self._tasks:
            self._tasks.remove(original)
            if replacement is not None:
                self._tasks.add(replacement)
            didReplace = True
        indices = []
        for i, e in enumerate(self):
            if original == e:
                indices.append(i)
        for i in reversed(indices):
            self.pop(i)
            if replacement is not None:
                self.insert(i, replacement)
            didReplace = True
        return didReplace

    def moduleNames(self):
        result = set()
        visitor = NodeNameVisitor(result)
        for seq in self:
            seq.visit(visitor)
        for t in self._tasks:
            t.visit(visitor)
        return result
    def contains(self, mod):
        visitor = ContainsModuleVisitor(mod)
        for seq in self:
            seq.visit(visitor)
            if visitor.result():
                return True
        for t in self._tasks:
            t.visit(visitor)
            if visitor.result():
                return True
        return visitor.result()
    def tasks(self):
        """Returns the list of Tasks (that may contain other Tasks) that are associated directly to the Schedule."""
        return self._tasks
    def dumpPython(self, options=PrintOptions()):
        pathNames = ['process.'+p.label_() for p in self]
        if pathNames:
            s=', '.join(pathNames)
        else:
            s = ''
        associationContents = set()
        for task in self._tasks:
            if task.hasLabel_():
                associationContents.add(_Labelable.dumpSequencePython(task, options))
            else:
                associationContents.add(task.dumpPythonNoNewline(options))
        taskStrings = list()
        for iString in sorted(associationContents):
            taskStrings.append(iString)
        if taskStrings and s:
            return 'cms.Schedule(*[ ' + s + ' ], tasks=[' + ', '.join(taskStrings) + '])\n'
        elif s:
            return 'cms.Schedule(*[ ' + s + ' ])\n'
        elif taskStrings:
            return 'cms.Schedule(tasks=[' + ', '.join(taskStrings) + '])\n'
        else:
            return 'cms.Schedule()\n'

    def __str__(self):
        return self.dumpPython()

# Fills a list of all Sequences visited
# Can visit a Sequence, Path, or EndPath
class SequenceVisitor(object):
    def __init__(self,d):
        self.deps = d
    def enter(self,visitee):
        if isinstance(visitee,Sequence):
            self.deps.append(visitee)
        pass
    def leave(self,visitee):
        pass

# Fills a list of all Tasks visited
# Can visit a Task, Sequence, Path, or EndPath
class TaskVisitor(object):
    def __init__(self,d):
        self.deps = d
    def enter(self,visitee):
        if isinstance(visitee,Task):
            self.deps.append(visitee)
        pass
    def leave(self,visitee):
        pass

# Fills a list of all ConditionalTasks visited
# Can visit a ConditionalTask, Sequence, Path, or EndPath
class ConditionalTaskVisitor(object):
    def __init__(self,d):
        self.deps = d
    def enter(self,visitee):
        if isinstance(visitee,ConditionalTask):
            self.deps.append(visitee)
        pass
    def leave(self,visitee):
        pass

# Fills a list of all modules visited.
# Can visit a Sequence, Path, EndPath, or Task
# For purposes of this visitor, a module is considered
# to be an object that is one of these types: EDProducer,
# EDFilter, EDAnalyzer, OutputModule, ESProducer, ESSource,
# Service. The last three of these can only appear on a
# Task, they are not sequenceable. An object of one
# of these types is also called a leaf.
class ModuleNodeVisitor(object):
    def __init__(self,l):
        self.l = l
    def enter(self,visitee):
        if visitee.isLeaf():
            self.l.append(visitee)
        pass
    def leave(self,visitee):
        pass

# Should not be used on Tasks.
# Similar to ModuleNodeVisitor with the following
# differences. It only lists the modules that were
# contained inside a Task.  It should only be used
# on Sequences, Paths, and EndPaths.
class ModuleNodeOnTaskVisitor(object):
    def __init__(self,l):
        self.l = l
        self._levelInTasks = 0
    def enter(self,visitee):
        if isinstance(visitee, Task):
            self._levelInTasks += 1
        if self._levelInTasks == 0:
            return
        if visitee.isLeaf():
            self.l.append(visitee)
        pass
    def leave(self,visitee):
        if self._levelInTasks > 0:
            if isinstance(visitee, Task):
                self._levelInTasks -= 1

class ModuleNodeOnConditionalTaskVisitor(object):
    def __init__(self,l):
        self.l = l
        self._levelInTasks = 0
    def enter(self,visitee):
        if isinstance(visitee, ConditionalTask):
            self._levelInTasks += 1
        # This block gets the modules contained by SwitchProducer. It
        # needs to be before the "levelInTasks == 0" check because the
        # contained modules need to be treated like in ConditionalTask
        # also when the SwitchProducer itself is in the Path.
        if hasattr(visitee, "modulesForConditionalTask_"):
            self.l.extend(visitee.modulesForConditionalTask_())
        if self._levelInTasks == 0:
            return
        if visitee.isLeaf():
            self.l.append(visitee)
        pass
    def leave(self,visitee):
        if self._levelInTasks > 0:
            if isinstance(visitee, ConditionalTask):
                self._levelInTasks -= 1

# Should not be used on Tasks.
# Similar to ModuleNodeVisitor with the following
# differences. It only lists the modules that were
# outside a Task, in the sequenced part of the sequence.
# It should only be used on Sequences, Paths, and
# EndPaths.
class ModuleNodeNotOnTaskVisitor(object):
    def __init__(self,l):
        self.l = l
        self._levelInTasks = 0
    def enter(self,visitee):
        if isinstance(visitee, Task):
            self._levelInTasks += 1
        if self._levelInTasks > 0:
            return
        if visitee.isLeaf():
            self.l.append(visitee)
        pass
    def leave(self,visitee):
        if self._levelInTasks > 0:
            if isinstance(visitee, Task):
                self._levelInTasks -= 1

# Can visit Tasks, Sequences, Paths, and EndPaths
# result will be set to True if and only if
# the module is in the object directly or
# indirectly through contained Sequences or
# associated Tasks.
class ContainsModuleVisitor(object):
    def __init__(self,mod):
        self._mod = mod
        self._result = False

    def result(self):
        return self._result

    def enter(self,visitee):
        if self._mod is visitee:
            self._result = True

    def leave(self,visitee):
        pass

# Can visit Tasks, Sequences, Paths, and EndPaths
# Fills a set of the names of the visited leaves.
# For the labelable ones the name is the label.
# For a Service the name is the type.
# It raises an exception if a labelable object
# does not have a label at all. It will return
# 'None' if the label attribute exists but was set
# to None. If a Service is not attached to the process
# it will also raise an exception.
class NodeNameVisitor(object):
    """ takes a set as input"""
    def __init__(self,l):
        self.l = l
    def enter(self,visitee):
        if visitee.isLeaf():
            if isinstance(visitee, _Labelable):
                self.l.add(visitee.label_())
            else:
                if visitee._inProcess:
                    self.l.add(visitee.type_())
                else:
                    raise RuntimeError("Service not attached to process: {}".format(visitee.dumpPython()))
    def leave(self,visitee):
        pass

# This visitor works only with Sequences, Paths and EndPaths
# It will not work on Tasks
class ExpandVisitor(object):
    """ Expands the sequence into leafs and UnaryOperators """
    def __init__(self, type):
        self._type = type
        self.l = []
        self.taskLeaves = []
        self.taskLeavesInConditionalTasks = []
        self.presentTaskLeaves = self.taskLeaves
        self._levelInTasks = 0
        self.conditionaltaskLeaves = []
        self._levelInConditionalTasks = 0

    def enter(self,visitee):
        if isinstance(visitee, Task):
            self._levelInTasks += 1
            return
        if isinstance(visitee, ConditionalTask):
            self.presentTaskLeaves = self.taskLeavesInConditionalTasks
            self._levelInConditionalTasks += 1
            return
        if visitee.isLeaf():
            if self._levelInTasks > 0:
                self.presentTaskLeaves.append(visitee)
            elif self._levelInConditionalTasks > 0:
                self.conditionaltaskLeaves.append(visitee)
            else:
                self.l.append(visitee)
    def leave(self, visitee):
        if self._levelInTasks > 0:
            if isinstance(visitee, Task):
                self._levelInTasks -= 1
            return
        if self._levelInConditionalTasks > 0:
            if isinstance(visitee, ConditionalTask):
                self._levelInConditionalTasks -= 1
                if 0 == self._levelInConditionalTasks:
                  self.presentTaskLeaves = self.taskLeaves
            return
        if isinstance(visitee,_UnarySequenceOperator):
            self.l[-1] = visitee
    def result(self):
        tsks = []
        if self.taskLeaves:
          tsks.append(Task(*self.taskLeaves))
        if self.conditionaltaskLeaves:
          ct = ConditionalTask(*self.conditionaltaskLeaves)
          if self.taskLeavesInConditionalTasks:
            ct.append(*self.taskLeavesInConditionalTasks)
          tsks.append(ct)
        if len(self.l) > 0:
            # why doesn't (sum(self.l) work?
            seq = self.l[0]
            for el in self.l[1:]:
                seq += el
            return self._type(seq, *tsks)
        else:
            return self._type(*tsks)
    def resultString(self):
        sep = ''
        returnValue = ''
        for m in self.l:
            if m is not None:
                returnValue += sep+str(m)
                sep = '+'
        if returnValue:
            sep = ','
        for n in self.taskLeaves:
            if n is not None:
                returnValue += sep+str(n)
            sep = ','
        return returnValue


# This visitor is only meant to run on Sequences, Paths, and EndPaths
# It intentionally ignores nodes on Tasks when it does this.
class DecoratedNodeNameVisitor(object):
    """ Adds any '!' or '-' needed.  Takes a list """
    def __init__(self,l):
        self.l = l
        self._decoration =''
        self._levelInTasks = 0

    def initialize(self):
        self.l[:] = []
        self._decoration =''
        self._levelInTasks = 0

    def enter(self,visitee):
        if isinstance(visitee, _TaskBase):
            self._levelInTasks += 1
        if self._levelInTasks > 0:
            return
        if visitee.isLeaf():
            if hasattr(visitee, "_Labelable__label"):
                self.l.append(self._decoration+visitee.label_())
            else:
                error = "An object in a sequence was not found in the process\n"
                if hasattr(visitee, "_filename"):
                    error += "From file " + visitee._filename
                else:
                    error += "Dump follows\n" + repr(visitee)
                raise RuntimeError(error)
        if isinstance(visitee,_BooleanLogicExpression):
            self.l.append(self._decoration+visitee.operatorString())
        if isinstance(visitee,_UnarySequenceOperator):
            self._decoration=visitee.decoration()
        else:
            self._decoration=''

    def leave(self,visitee):
        # Ignore if this visitee is inside a Task
        if self._levelInTasks > 0:
            if isinstance(visitee, _TaskBase):
                self._levelInTasks -= 1
            return
        if isinstance(visitee,_BooleanLogicExpression):
            #need to add the 'go back' command to keep track of where we are in the tree
            self.l.append('@')

# This visitor is only meant to run on Sequences, Paths, and EndPaths
# Similar to DecoratedNodeNameVistor. The only difference
# is it also builds a separate list of leaves on Tasks.
class DecoratedNodeNamePlusVisitor(object):
    """ Adds any '!' or '-' needed.  Takes a list """
    def __init__(self,l):
        self.l = l
        self._decoration =''
        self._levelInTasks = 0
        self._leavesOnTasks = []

    def initialize(self):
        self.l[:] = []
        self._decoration =''
        self._levelInTasks = 0
        self._leavesOnTasks[:] = []

    def enter(self,visitee):
        if isinstance(visitee, Task):
            self._levelInTasks += 1
        if self._levelInTasks > 0:
            if visitee.isLeaf():
                self._leavesOnTasks.append(visitee)
            return
        if visitee.isLeaf():
            if hasattr(visitee, "_Labelable__label"):
                self.l.append(self._decoration+visitee.label_())
            else:
                error = "An object in a sequence was not found in the process\n"
                if hasattr(visitee, "_filename"):
                    error += "From file " + visitee._filename
                else:
                    error += "Dump follows\n" + repr(visitee)
                raise RuntimeError(error)
        if isinstance(visitee,_BooleanLogicExpression):
            self.l.append(self._decoration+visitee.operatorString())
        if isinstance(visitee,_UnarySequenceOperator):
            self._decoration=visitee.decoration()
        else:
            self._decoration=''

    def leave(self,visitee):
        # Ignore if this visitee is inside a Task
        if self._levelInTasks > 0:
            if isinstance(visitee, Task):
                self._levelInTasks -= 1
            return
        if isinstance(visitee,_BooleanLogicExpression):
            #need to add the 'go back' command to keep track of where we are in the tree
            self.l.append('@')

    def leavesOnTasks(self):
        return self._leavesOnTasks

class _CopyAndExcludeSequenceVisitorOld(object):
   """Traverses a Sequence and constructs a new sequence which does not contain modules from the specified list"""
   def __init__(self,modulesToRemove):
       self.__modulesToIgnore = modulesToRemove
       self.__stack = list()
       self.__stack.append(list())
       self.__result = None
       self.__didExclude = False
   def enter(self,visitee):
       if len(self.__stack) > 0:
           #add visitee to its parent's stack entry
           self.__stack[-1].append([visitee,False])
       if visitee.isLeaf():
           if visitee in self.__modulesToIgnore:
               self.__didExclude = True
               self.__stack[-1][-1]=[None,True]
       elif isinstance(visitee, Sequence):
           if visitee in self.__modulesToIgnore:
               self.__didExclude = True
               self.__stack[-1][-1]=[None,True]
           self.__stack.append(list())
       else:
           #need to add a stack entry to keep track of children
           self.__stack.append(list())
   def leave(self,visitee):
       node = visitee
       if not visitee.isLeaf():
           #were any children changed?
           l = self.__stack[-1]
           changed = False
           countNulls = 0
           nonNulls = list()
           for c in l:
               if c[1] == True:
                   changed = True
               if c[0] is None:
                   countNulls +=1
               else:
                   nonNulls.append(c[0])
           if changed:
               self.__didExclude = True
               if countNulls != 0:
                   #this node must go away
                   if len(nonNulls) == 0:
                       #all subnodes went away
                       node = None
                   else:
                       node = nonNulls[0]
                       for n in nonNulls[1:]:
                           node = node+n
               else:
                   #some child was changed so we need to clone
                   # this node and replace it with one that holds
                   # the new child(ren)
                   children = [x[0] for x in l ]
                   if not isinstance(visitee,Sequence):
                       node = visitee.__new__(type(visitee))
                       node.__init__(*children)
                   else:
                       node = nonNulls[0]
       if node != visitee:
           #we had to replace this node so now we need to
           # change parent's stack entry as well
           if len(self.__stack) > 1:
               p = self.__stack[-2]
               #find visitee and replace
               for i,c in enumerate(p):
                   if c[0]==visitee:
                       c[0]=node
                       c[1]=True
                       break
       if not visitee.isLeaf():
           self.__stack = self.__stack[:-1]
   def result(self):
       result = None
       for n in (x[0] for x in self.__stack[0]):
           if n is None:
               continue
           if result is None:
               result = n
           else:
               result = result+n
       return result
   def didExclude(self):
       return self.__didExclude


# This visitor can also be used on Tasks.
class _MutatingSequenceVisitor(object):
    """Traverses a Sequence and constructs a new sequence by applying the operator to each element of the sequence"""

    # In many cases this operates in an intuitive manner that needs
    # no explanation, but there are some complex cases and I will try to
    # explain these in the following comments.
    #
    # First of all the top level Sequence or Task being visited may contain
    # many objects of different types. These contained objects are never
    # modified. If they are not left the same, they are instead replaced
    # by other instances, replaced by new instances or removed.
    # Contained objects are only replaced or removed when they were directly
    # modified or if they contain something that was modified.
    # If all the contents of a Sequence, Task, _SequenceNegation or _SequenceIgnore
    # object that is not at the top level are removed, then the containing
    # object is also removed.
    # If the contents of a Sequence other than the top level sequence are
    # modified, then the sequence elements and Task objects it contains get
    # passed up to be included in the top level sequence. If the contents of
    # a Task are modified, a new Task object is created and passed up to be
    # included in the top level Sequence or Task. If it is a _SequenceNegation
    # or _SequenceIgnore instance it will simply be removed completely if its
    # operand is removed. If the operand is replaced then a new object of the
    # same type will be constructed replacing the old.
    #
    # Note that if a Sequence contains a SequencePlaceholder, the future contents
    # of that placeholder are not affected by the changes. If that is an issue,
    # then you probably want to resolve the placeholders before using this
    # class.
    #
    # If this is used multiple times on the same sequence or task, the consequences
    # might interfere with one another in unusual cases.
    #
    # One example, the matching to find objects to modify is based on instances
    # (the python id) being the same. So if you modify the contents of a Task or
    # Sequence and then subsequently try to modify that Sequence or Task, then
    # it will either no longer exist or be a different instance and so nothing
    # would get modified.  Note that the one exception to this matching by instance
    # is _SequenceIgnore and _SequenceNegation. In that case, two objects are
    # recognized as matching if the contained module is the same instance instead
    # of requiring the _SequenceNegation or _SequenceIgnore object to be the same
    # instance.
    #
    # Another example. There is an input operator that removes the first instance
    # of an object. Applying this visitor with that operation might give unexpected
    # results if another operation previously changed the number of times the
    # that instance appears or the order it appears in the visitation. This
    # should only be an issue if the item is on a Task and even then only in
    # unusual circumstances.

    def __init__(self,operator):
        self.__operator = operator
        # You add a list to the __stack when entering any non-Leaf object
        # and pop the last element when leaving any non-Leaf object
        self.__stack = list()
        self.__stack.append(list())
        self.__didApply = False
        self.__levelInModifiedNonLeaf = 0
    def enter(self,visitee):
        # Ignore the content of replaced or removed Sequences,
        # Tasks, and operators.
        if self.__levelInModifiedNonLeaf > 0:
            if not visitee.isLeaf():
                self.__levelInModifiedNonLeaf += 1
            return

        # Just a sanity check
        if not len(self.__stack) > 0:
            raise RuntimeError("LogicError Empty stack in MutatingSequenceVisitor.\n"
                               "This should never happen. Contact a Framework developer.")

        # The most important part.
        # Apply the operator that might change things, The rest
        # of the class is just dealing with side effects of these changes.
        v = self.__operator(visitee)

        if v is visitee:
            # the operator did not change the visitee
            # The 3 element list being appended has the following contents
            # element 0 - either the unmodified object, the modified object, or
            #   a sequence collection when it is a Sequence whose contents have
            #   been modified.
            # element 1 - Indicates whether the object was modified.
            # element 2 - None or a list of tasks for a Sequence
            #   whose contents have been modified.
            self.__stack[-1].append([visitee, False, None])
            if not visitee.isLeaf():
                # need to add a list to keep track of the contents
                # of the Sequence, Task, or operator we just entered.
                self.__stack.append(list())
        else:
            # the operator changed the visitee
            self.__didApply = True
            self.__stack[-1].append([v, True, None])
            if not visitee.isLeaf():
                # Set flag to indicate modified Sequence, Task, or operator
                self.__levelInModifiedNonLeaf = 1
    def leave(self,visitee):

        # nothing to do for leaf types because they do not have contents
        if visitee.isLeaf():
            return

        # Ignore if this visitee is inside something that was already removed
        # or replaced.
        if self.__levelInModifiedNonLeaf > 0:
            self.__levelInModifiedNonLeaf -= 1
            return

        # Deal with visitees which have contents (Sequence, Task, _SequenceIgnore,
        # or _SequenceNegation) and although we know the visitee itself did not get
        # changed by the operator, the contents of the visitee might have been changed.

        # did any object inside the visitee change?
        contents = self.__stack[-1]
        changed = False
        allNull = True
        for c in contents:
            if c[1] == True:
                changed = True
            if c[0] is not None:
                allNull = False
        if changed:
            if allNull:
                self.__stack[-2][-1] = [None, True, None]

            elif isinstance(visitee, _UnarySequenceOperator):
                node = visitee.__new__(type(visitee))
                node.__init__(contents[0][0])
                self.__stack[-2][-1] = [node, True, None]

            elif isinstance(visitee, _TaskBase):
                nonNull = []
                for c in contents:
                    if c[0] is not None:
                        nonNull.append(c[0])
                self.__stack[-2][-1] = [visitee._makeInstance(*nonNull), True, None]
            elif isinstance(visitee, Sequence):
                seq = _SequenceCollection()
                tasks = list()
                for c in contents:
                    if c[0] is None:
                        continue
                    if isinstance(c[0], _TaskBase):
                        tasks.append(c[0])
                    else:
                        seq = seq + c[0]
                        if c[2] is not None:
                            tasks.extend(c[2])

                self.__stack[-2][-1] = [seq, True, tasks]

        # When you exit the Sequence, Task, or operator,
        # drop the list which holds information about
        # its contents.
        if not visitee.isLeaf():
            self.__stack = self.__stack[:-1]

    def result(self, visitedContainer):

        if isinstance(visitedContainer, _TaskBase):
            result = list()
            for n in (x[0] for x in self.__stack[0]):
                if n is not None:
                    result.append(n)
            return result

        seq = _SequenceCollection()
        tasks = list()
        for c in self.__stack[0]:
            if c[0] is None:
                continue
            if isinstance(c[0], _TaskBase):
                tasks.append(c[0])
            else:
                seq = seq + c[0]
                if c[2] is not None:
                    tasks.extend(c[2])
        return [seq, tasks]

    def _didApply(self):
        return self.__didApply

# This visitor can also be used on Tasks.
class _CopyAndRemoveFirstSequenceVisitor(_MutatingSequenceVisitor):
    """Traverses a Sequence and constructs a new sequence which does not contain modules from the specified list"""
    def __init__(self,moduleToRemove):
        class _RemoveFirstOperator(object):
            def __init__(self,moduleToRemove):
                self.__moduleToRemove = moduleToRemove
                self.__found = False
            def __call__(self,test):
                if not self.__found and test is self.__moduleToRemove:
                    self.__found = True
                    return None
                return test
        super(type(self),self).__init__(_RemoveFirstOperator(moduleToRemove))
    def didRemove(self):
        return self._didApply()

# This visitor can also be used on Tasks.
class _CopyAndExcludeSequenceVisitor(_MutatingSequenceVisitor):
    """Traverses a Sequence and constructs a new sequence which does not contain the module specified"""
    def __init__(self,modulesToRemove):
        class _ExcludeOperator(object):
            def __init__(self,modulesToRemove):
                self.__modulesToIgnore = modulesToRemove
            def __call__(self,test):
                if test in modulesToRemove:
                    return None
                return test
        super(type(self),self).__init__(_ExcludeOperator(modulesToRemove))
    def didExclude(self):
        return self._didApply()

# This visitor can also be used on Tasks.
class _CopyAndReplaceSequenceVisitor(_MutatingSequenceVisitor):
    """Traverses a Sequence and constructs a new sequence which  replaces a specified module with a different module"""
    def __init__(self,target,replace):
        class _ReplaceOperator(object):
            def __init__(self,target,replace):
                self.__target = target
                self.__replace = replace
            def __call__(self,test):
                if test == self.__target:
                    return self.__replace
                return test
        super(type(self),self).__init__(_ReplaceOperator(target,replace))
    def didReplace(self):
        return self._didApply()

class _TaskBase(_ConfigureComponent, _Labelable) :
    def __init__(self, *items):
        self._collection = OrderedSet()
        self.add(*items)

    def __setattr__(self,name,value):
        if not name.startswith("_"):
            raise AttributeError("You cannot set parameters for {} objects.".format(self._taskType()))
        else:
            self.__dict__[name] = value

    def add(self, *items):
        for item in items:
            if not self._allowedInTask(item):
                raise RuntimeError("Adding an entry of type '{0}' to a {1}.\n"
                                   "It is illegal to add this type to a {1}.".format(type(item).__name__, self._taskType()))
            self._collection.add(item)

    def fillContents(self, taskContents, options=PrintOptions()):
        # only dump the label, if possible
        if self.hasLabel_():
            taskContents.add(_Labelable.dumpSequencePython(self, options))
        else:
            for i in self._collection:
                if isinstance(i, _TaskBase):
                    i.fillContents(taskContents, options)
                else:
                    taskContents.add(i.dumpSequencePython(options))

    def dumpPython(self, options=PrintOptions()):
        s = self.dumpPythonNoNewline(options)
        return s + "\n"

    def dumpPythonNoNewline(self, options=PrintOptions()):
        """Returns a string which is the python representation of the object"""
        taskContents = set()
        for i in self._collection:
            if isinstance(i, _TaskBase):
                i.fillContents(taskContents, options)
            else:
                taskContents.add(i.dumpSequencePython(options))
        s=''
        iFirst = True
        for item in sorted(taskContents):
            if not iFirst:
                s += ", "
            iFirst = False
            s += item
        if len(taskContents) > 255:
            s = "*[" + s + "]"
        return "cms.{}({})".format(self._taskType(),s)

    def directDependencies(self,sortByType=True):
        return findDirectDependencies(self, self._collection,sortByType=sortByType)

    def _isTaskComponent(self):
        return False

    def isLeaf(self):
        return False

    def visit(self,visitor):
        for i in self._collection:
            visitor.enter(i)
            if not i.isLeaf():
                i.visit(visitor)
            visitor.leave(i)

    def _errorstr(self):
        return "{}(...)".format(self.taskType_())

    def __iter__(self):
        for key in self._collection:
            yield key

    def __str__(self):
        l = []
        v = ModuleNodeVisitor(l)
        self.visit(v)
        s = ''
        for i in l:
            if s:
                s += ', '
            s += str (i)
        return s

    def __repr__(self):
        s = str(self)
        return "cms."+type(self).__name__+'('+s+')\n'

    def moduleNames(self):
        """Returns a set containing the names of all modules being used"""
        result = set()
        visitor = NodeNameVisitor(result)
        self.visit(visitor)
        return result
    def contains(self, mod):
        visitor = ContainsModuleVisitor(mod)
        self.visit(visitor)
        return visitor.result()
    def copy(self):
        return self._makeInstance(*self._collection)
    def copyAndExclude(self,listOfModulesToExclude):
        """Returns a copy of the sequence which excludes those module in 'listOfModulesToExclude'"""
        # You can exclude instances of these types EDProducer, EDFilter, ESSource, ESProducer,
        # Service, or Task.
        # Mostly this is very intuitive, but there are some complications in cases
        # where objects that contain other objects are involved. See the comments
        # for the _MutatingSequenceVisitor.
        for i in listOfModulesToExclude:
            if not i._isTaskComponent():
                raise TypeError("copyAndExclude can only exclude objects that can be placed on a Task")
        v = _CopyAndExcludeSequenceVisitor(listOfModulesToExclude)
        self.visit(v)
        return self._makeInstance(*v.result(self))
    def copyAndAdd(self, *modulesToAdd):
        """Returns a copy of the Task adding modules/tasks"""
        t = self.copy()
        t.add(*modulesToAdd)
        return t
    def expandAndClone(self):
        # Name of this function is not very good. It makes a shallow copy with all
        # the subTasks flattened out (removed), but keeping all the
        # modules that were in those subTasks as well as the top level
        # ones.
        l = []
        v = ModuleNodeVisitor(l)
        self.visit(v)
        return self._makeInstance(*l)
    def replace(self, original, replacement):
        """Finds all instances of 'original' and substitutes 'replacement' for them.
           Returns 'True' if a replacement occurs."""
        # This works for either argument being of type EDProducer, EDFilter, ESProducer,
        # ESSource, Service, or Task.
        #
        # Mostly this is very intuitive, but there are some complications in cases
        # where objects that contain other objects are involved. See the comments
        # for the _MutatingSequenceVisitor.

        if not self._allowedInTask(original) or (not replacement is None and not self._allowedInTask(replacement)):
           raise TypeError("The {0} replace function only works with objects that can be placed on a {0}\n".format(self._taskType()) + \
                           "           replace was called with original type = {}\n".format(str(type(original))) + \
                           "           and replacement type = {}\n".format(str(type(replacement))))
        else:
            v = _CopyAndReplaceSequenceVisitor(original,replacement)
            self.visit(v)
            if v.didReplace():
                self._collection.clear()
                self.add(*v.result(self))
            return v.didReplace()

    def remove(self, something):
        """Remove the first occurrence of a module
           Returns 'True' if the module has been removed, False if it was not found"""
        # You can remove instances of these types EDProducer, EDFilter, ESSource,
        # ESProducer, Service, or Task,
        #
        # Mostly this is very intuitive, but there are some complications in cases
        # where objects that contain other objects are involved. See the comments
        # for the _MutatingSequenceVisitor.
        #
        # Works very similar to copyAndExclude, there are 2 differences. This changes
        # the object itself instead of making a copy and second it only removes
        # the first instance of the argument instead of all of them.
        if not self._allowedInTask(something):
           raise TypeError("remove only works with objects that can be placed on a Task")
        v = _CopyAndRemoveFirstSequenceVisitor(something)
        self.visit(v)
        if v.didRemove():
            self._collection.clear()
            self.add(*v.result(self))
        return v.didRemove()

    def resolve(self, processDict,keepIfCannotResolve=False):
        temp = OrderedSet()
        for i in self._collection:
            if self._mustResolve(i):
                temp.add(i.resolve(processDict,keepIfCannotResolve))
            else:
                temp.add(i)
        self._collection = temp
        return self
        
class _TaskBasePlaceholder(object):
    def __init__(self, name):
        self._name = name
    def _isTaskComponent(self):
        return False
    def isLeaf(self):
        return False
    def visit(self,visitor):
        pass
    def __str__(self):
        return self._name
    def insertInto(self, parameterSet, myname):
        raise RuntimeError("The {} {} was never overridden".format(self._typeName(), self._name))
    def resolve(self, processDict,keepIfCannotResolve=False):
        if not self._name in processDict:
            if keepIfCannotResolve:
                return self
            raise RuntimeError("The {} {} cannot be resolved.\n Known keys are: {}".format(self._typeName(), self._name,str(processDict.keys())))
        o = processDict[self._name]
        if not self._allowedInTask(o):
            raise RuntimeError("The {} {} refers to an object type which is not allowed to be on a task: {}".format(self._typeName(), self._name, str(type(o))))
        if isinstance(o, self._taskClass()):
            return o.resolve(processDict)
        return o
    def copy(self):
        return self._makeInstance(self._name)
    def dumpSequencePython(self, options=PrintOptions()):
        return 'cms.{}("{}")'.format(self._typeName(), self._name)
    def dumpPython(self, options=PrintOptions()):
        result = 'cms.{}(\"'.format(self._typeName())
        if options.isCfg:
           result += 'process.'
        result += self._name+'\")\n'
        return result

class Task(_TaskBase) :
    """Holds EDProducers, EDFilters, ESProducers, ESSources, Services, and Tasks.
    A Task can be associated with Sequences, Paths, EndPaths, ConditionalTasks and the Schedule.
    An EDProducer or EDFilter will be enabled to run unscheduled if it is on
    a task associated with the Schedule or any scheduled Path or EndPath (directly
    or indirectly through Sequences) and not be on any scheduled Path or EndPath.
    ESSources, ESProducers, and Services will be enabled to run if they are on
    a Task associated with the Schedule or a scheduled Path or EndPath.  In other
    cases, they will be enabled to run if and only if they are not on a Task attached
    to the process.
    """
    @staticmethod
    def _taskType():
      return "Task"
    def _place(self, name, proc):
        proc._placeTask(name,self)
    def _isTaskComponent(self):
        return True
    @staticmethod
    def _makeInstance(*items):
      return Task(*items)
    @staticmethod
    def _allowedInTask(item ):
      return (isinstance(item, _ConfigureComponent) and item._isTaskComponent()) or isinstance(item, TaskPlaceholder)
    @staticmethod
    def _mustResolve(item):
        return isinstance(item, Task) or isinstance(item, TaskPlaceholder)
      
class TaskPlaceholder(_TaskBasePlaceholder):
    def _isTaskComponent(self):
        return True
    @staticmethod
    def _typeName():
      return "TaskPlaceholder"
    @staticmethod
    def _makeInstance(name):
      return TaskPlaceholder(name)
    @staticmethod
    def _allowedInTask(obj):
      return Task._allowedInTask(obj)
    @staticmethod
    def _taskClass():
      return Task


class ConditionalTask(_TaskBase) :
    """Holds EDProducers, EDFilters, ESProducers, ESSources, Services, Tasks and ConditionalTasks.
    A ConditionalTask can be associated with Sequences, Paths, and EndPaths.
    An EDProducer or EDFilter will be added to a Path or EndPath based on which other
    modules on the Path consumes its data products. If that ConditionalTask assigned module
    is placed after an EDFilter, the module will only run if the EDFilter passes. If no module
    on the Path needs the module's data products, the module will be treated as if it were on a Task.
    """
    @staticmethod
    def _taskType():
      return "ConditionalTask"
    def _place(self, name, proc):
        proc._placeConditionalTask(name,self)
    def _isTaskComponent(self):
        return False
    @staticmethod
    def _makeInstance(*items):
      return ConditionalTask(*items)
    @staticmethod
    def _allowedInTask(item):
      return isinstance(item, ConditionalTask) or isinstance(item, ConditionalTaskPlaceholder) or Task._allowedInTask(item)
    @staticmethod
    def _mustResolve(item):
        return Task._mustResolve(item) or isinstance(item, ConditionalTask) or isinstance(item, ConditionalTaskPlaceholder)


class ConditionalTaskPlaceholder(_TaskBasePlaceholder):
    def _isTaskComponent(self):
        return False
    @staticmethod
    def _typeName():
      return "ConditionalTaskPlaceholder"
    @staticmethod
    def _makeInstance(name):
      return ConditionalTaskPlaceholder(name)
    @staticmethod
    def _allowedInTask(obj):
      return Task._allowedInTask(obj) or ConditionalTask._allowedInTask(obj)
    @staticmethod
    def _taskClass():
      return ConditionalTask


if __name__=="__main__":
    import unittest
    class DummyModule(_Labelable, _SequenceLeaf, _ConfigureComponent):
        def __init__(self,name):
            self.setLabel(name)
        def _isTaskComponent(self):
            return True
        def __repr__(self):
            return self.label_()
    class DummyBooleanModule(_Labelable, _BooleanLogicSequenceLeaf):
        def __init__(self,name):
            self.setLabel(name)
    class TestModuleCommand(unittest.TestCase):
        def setUp(self):
            """Nothing to do """
            pass
        def testBoolean(self):
            a = DummyBooleanModule("a")
            b = DummyBooleanModule("b")
            p = Path( a & b)
            self.assertEqual(p.dumpPython(),"cms.Path(process.a&process.b)\n")
            l = list()
            namesVisitor = DecoratedNodeNameVisitor(l)
            p.visit(namesVisitor)
            self.assertEqual(l,['&','a','b','@'])
            p2 = Path( a | b)
            self.assertEqual(p2.dumpPython(),"cms.Path(process.a|process.b)\n")
            l[:]=[]
            p2.visit(namesVisitor)
            self.assertEqual(l,['|','a','b','@'])
            c = DummyBooleanModule("c")
            d = DummyBooleanModule("d")
            p3 = Path(a & b & c & d)
            self.assertEqual(p3.dumpPython(),"cms.Path(process.a&process.b&process.c&process.d)\n")
            l[:]=[]
            p3.visit(namesVisitor)
            self.assertEqual(l,['&','a','b','c','d','@'])
            p3 = Path(((a & b) & c) & d)
            self.assertEqual(p3.dumpPython(),"cms.Path(process.a&process.b&process.c&process.d)\n")
            p3 = Path(a & (b & (c & d)))
            self.assertEqual(p3.dumpPython(),"cms.Path(process.a&process.b&process.c&process.d)\n")
            p3 = Path((a & b) & (c & d))
            self.assertEqual(p3.dumpPython(),"cms.Path(process.a&process.b&process.c&process.d)\n")
            p3 = Path(a & (b & c) & d)
            self.assertEqual(p3.dumpPython(),"cms.Path(process.a&process.b&process.c&process.d)\n")
            p4 = Path(a | b | c | d)
            self.assertEqual(p4.dumpPython(),"cms.Path(process.a|process.b|process.c|process.d)\n")
            p5 = Path(a | b & c & d )
            self.assertEqual(p5.dumpPython(),"cms.Path(process.a|(process.b&process.c&process.d))\n")
            l[:]=[]
            p5.visit(namesVisitor)
            self.assertEqual(l,['|','a','&','b','c','d','@','@'])
            p5 = Path(a & b | c & d )
            self.assertEqual(p5.dumpPython(),"cms.Path((process.a&process.b)|(process.c&process.d))\n")
            l[:]=[]
            p5.visit(namesVisitor)
            self.assertEqual(l,['|','&','a','b','@','&','c','d','@','@'])
            p5 = Path(a & (b | c) & d )
            self.assertEqual(p5.dumpPython(),"cms.Path(process.a&(process.b|process.c)&process.d)\n")
            l[:]=[]
            p5.visit(namesVisitor)
            self.assertEqual(l,['&','a','|','b','c','@','d','@'])
            p5 = Path(a & b & c | d )
            self.assertEqual(p5.dumpPython(),"cms.Path((process.a&process.b&process.c)|process.d)\n")
            l[:]=[]
            p5.visit(namesVisitor)
            self.assertEqual(l,['|','&','a','b','c','@','d','@'])
            p6 = Path( a & ~b)
            self.assertEqual(p6.dumpPython(),"cms.Path(process.a&(~process.b))\n")
            l[:]=[]
            p6.visit(namesVisitor)
            self.assertEqual(l,['&','a','!b','@'])
            p6 = Path( a & ignore(b))
            self.assertEqual(p6.dumpPython(),"cms.Path(process.a&(cms.ignore(process.b)))\n")
            l[:]=[]
            p6.visit(namesVisitor)
            self.assertEqual(l,['&','a','-b','@'])
            p6 = Path( a & wait(b))
            self.assertEqual(p6.dumpPython(),"cms.Path(process.a&(cms.wait(process.b)))\n")
            l[:]=[]
            p6.visit(namesVisitor)
            self.assertEqual(l,['&','a','|b','@'])
            p6 = Path( a & wait(ignore(b)))
            self.assertEqual(p6.dumpPython(),"cms.Path(process.a&(cms.wait(cms.ignore(process.b))))\n")
            l[:]=[]
            p6.visit(namesVisitor)
            self.assertEqual(l,['&','a','+b','@'])
            p6 = Path( a & ignore(wait(b)))
            self.assertEqual(p6.dumpPython(),"cms.Path(process.a&(cms.wait(cms.ignore(process.b))))\n")
            l[:]=[]
            p6.visit(namesVisitor)
            self.assertEqual(l,['&','a','+b','@'])
            p6 = Path(~(a&b))
            self.assertEqual(p6.dumpPython(),"cms.Path(~(process.a&process.b))\n")
            l[:]=[]
            p6.visit(namesVisitor)
            self.assertEqual(l,['!&','a','b','@'])

        def testTaskConstructor(self):
            a = DummyModule("a")
            self.assertRaises(RuntimeError, lambda : Task(ConditionalTask(a)) )

        def testDumpPython(self):
            a = DummyModule("a")
            b = DummyModule('b')
            p = Path((a*b))
            #print p.dumpConfig('')
            self.assertEqual(p.dumpPython(),"cms.Path(process.a+process.b)\n")
            p2 = Path((b+a))
            #print p2.dumpConfig('')
            self.assertEqual(p2.dumpPython(),"cms.Path(process.b+process.a)\n")
            c = DummyModule('c')
            p3 = Path(c*(a+b))
            #print p3.dumpConfig('')
            self.assertEqual(p3.dumpPython(),"cms.Path(process.c+process.a+process.b)\n")
            p4 = Path(c*a+b)
            #print p4.dumpConfig('')
            self.assertEqual(p4.dumpPython(),"cms.Path(process.c+process.a+process.b)\n")
            p5 = Path(a+ignore(b))
            #print p5.dumpConfig('')
            self.assertEqual(p5.dumpPython(),"cms.Path(process.a+cms.ignore(process.b))\n")
            p5a = Path(a+wait(b))
            self.assertEqual(p5a.dumpPython(),"cms.Path(process.a+cms.wait(process.b))\n")
            p5b = Path(a+ignore(wait(b)))
            self.assertEqual(p5b.dumpPython(),"cms.Path(process.a+cms.wait(cms.ignore(process.b)))\n")
            p5c = Path(a+wait(ignore(b)))
            self.assertEqual(p5c.dumpPython(),"cms.Path(process.a+cms.wait(cms.ignore(process.b)))\n")
            p6 = Path(c+a*b)
            #print p6.dumpConfig('')
            self.assertEqual(p6.dumpPython(),"cms.Path(process.c+process.a+process.b)\n")
            p7 = Path(a+~b)
            self.assertEqual(p7.dumpPython(),"cms.Path(process.a+~process.b)\n")
            p8 = Path((a+b)*c)
            self.assertEqual(p8.dumpPython(),"cms.Path(process.a+process.b+process.c)\n")
            t1 = Task(a)
            t2 = Task(c, b)
            t3 = Task()
            p9 = Path((a+b)*c, t1)
            self.assertEqual(p9.dumpPython(),"cms.Path(process.a+process.b+process.c, cms.Task(process.a))\n")
            p10 = Path((a+b)*c, t2, t1)
            self.assertEqual(p10.dumpPython(),"cms.Path(process.a+process.b+process.c, cms.Task(process.a), cms.Task(process.b, process.c))\n")
            p11 = Path(t1, t2, t3)
            self.assertEqual(p11.dumpPython(),"cms.Path(cms.Task(), cms.Task(process.a), cms.Task(process.b, process.c))\n")
            d = DummyModule("d")
            e = DummyModule('e')
            f = DummyModule('f')
            t4 = Task(d, Task(f))
            s = Sequence(e, t4)
            p12 = Path(a+b+s+c,t1)
            self.assertEqual(p12.dumpPython(),"cms.Path(process.a+process.b+cms.Sequence(process.e, cms.Task(process.d, process.f))+process.c, cms.Task(process.a))\n")
            ct1 = ConditionalTask(a)
            ct2 = ConditionalTask(c, b)
            ct3 = ConditionalTask()
            p13 = Path((a+b)*c, ct1)
            self.assertEqual(p13.dumpPython(),"cms.Path(process.a+process.b+process.c, cms.ConditionalTask(process.a))\n")
            p14 = Path((a+b)*c, ct2, ct1)
            self.assertEqual(p14.dumpPython(),"cms.Path(process.a+process.b+process.c, cms.ConditionalTask(process.a), cms.ConditionalTask(process.b, process.c))\n")
            p15 = Path(ct1, ct2, ct3)
            self.assertEqual(p15.dumpPython(),"cms.Path(cms.ConditionalTask(), cms.ConditionalTask(process.a), cms.ConditionalTask(process.b, process.c))\n")
            ct4 = ConditionalTask(d, Task(f))
            s = Sequence(e, ct4)
            p16 = Path(a+b+s+c,ct1)
            self.assertEqual(p16.dumpPython(),"cms.Path(process.a+process.b+cms.Sequence(process.e, cms.ConditionalTask(process.d, process.f))+process.c, cms.ConditionalTask(process.a))\n")

            n = 260
            mods = []
            labels = []
            for i in range(0, n):
                l = "a{}".format(i)
                labels.append("process."+l)
                mods.append(DummyModule(l))
            labels.sort()
            task = Task(*mods)
            self.assertEqual(task.dumpPython(), "cms.Task(*[" + ", ".join(labels) + "])\n")
            conditionalTask = ConditionalTask(*mods)
            self.assertEqual(conditionalTask.dumpPython(), "cms.ConditionalTask(*[" + ", ".join(labels) + "])\n")

            l = list()
            namesVisitor = DecoratedNodeNameVisitor(l)
            p.visit(namesVisitor)
            self.assertEqual(l, ['a', 'b'])
            l[:] = []
            p5.visit(namesVisitor)
            self.assertEqual(l, ['a', '-b'])
            l[:] = []
            p5a.visit(namesVisitor)
            self.assertEqual(l, ['a', '|b'])
            l[:] = []
            p5b.visit(namesVisitor)
            self.assertEqual(l, ['a', '+b'])
            l[:] = []
            p5c.visit(namesVisitor)
            self.assertEqual(l, ['a', '+b'])
            l[:] = []
            p7.visit(namesVisitor)
            self.assertEqual(l, ['a', '!b'])
            l[:] = []
            p10.visit(namesVisitor)
            self.assertEqual(l, ['a', 'b', 'c'])
            l[:] = []
            p12.visit(namesVisitor)
            self.assertEqual(l, ['a', 'b', 'e', 'c'])
            l[:] = []
            p16.visit(namesVisitor)
            self.assertEqual(l, ['a', 'b', 'e', 'c'])
            l[:] = []
            moduleVisitor = ModuleNodeVisitor(l)
            p8.visit(moduleVisitor)
            names = [m.label_() for m in l]
            self.assertEqual(names, ['a', 'b', 'c'])
            tph = TaskPlaceholder('a')
            self.assertEqual(tph.dumpPython(), 'cms.TaskPlaceholder("process.a")\n')
            sph = SequencePlaceholder('a')
            self.assertEqual(sph.dumpPython(), 'cms.SequencePlaceholder("process.a")\n')
            ctph = ConditionalTaskPlaceholder('a')
            self.assertEqual(ctph.dumpPython(), 'cms.ConditionalTaskPlaceholder("process.a")\n')

        def testDumpConfig(self):
            a = DummyModule("a")
            b = DummyModule('b')
            p = Path((a*b))
            #print p.dumpConfig('')
            self.assertEqual(p.dumpConfig(None),"{a&b}\n")
            p2 = Path((b+a))
            #print p2.dumpConfig('')
            self.assertEqual(p2.dumpConfig(None),"{b&a}\n")
            c = DummyModule('c')
            p3 = Path(c*(a+b))
            #print p3.dumpConfig('')
            self.assertEqual(p3.dumpConfig(None),"{c&a&b}\n")
            p4 = Path(c*a+b)
            #print p4.dumpConfig('')
            self.assertEqual(p4.dumpConfig(None),"{c&a&b}\n")
            p5 = Path(a+ignore(b))
            #print p5.dumpConfig('')
            self.assertEqual(p5.dumpConfig(None),"{a&-b}\n")
            p6 = Path(c+a*b)
            #print p6.dumpConfig('')
            self.assertEqual(p6.dumpConfig(None),"{c&a&b}\n")
            p7 = Path(a+~b)
            self.assertEqual(p7.dumpConfig(None),"{a&!b}\n")
            p8 = Path((a+b)*c)
            self.assertEqual(p8.dumpConfig(None),"{a&b&c}\n")
        def testVisitor(self):
            class TestVisitor(object):
                def __init__(self, enters, leaves):
                    self._enters = enters
                    self._leaves = leaves
                def enter(self,visitee):
                    #print visitee.dumpSequencePython()
                    if self._enters[0] != visitee:
                        raise RuntimeError("wrong node ("+str(visitee)+") on 'enter'")
                    else:
                        self._enters = self._enters[1:]
                def leave(self,visitee):
                    if self._leaves[0] != visitee:
                        raise RuntimeError("wrong node ("+str(visitee)+") on 'leave'\n expected ("+str(self._leaves[0])+")")
                    else:
                        self._leaves = self._leaves[1:]
            a = DummyModule("a")
            b = DummyModule('b')
            multAB = a*b
            p = Path(multAB)
            t = TestVisitor(enters=[a,b],
                            leaves=[a,b])
            p.visit(t)

            plusAB = a+b
            p = Path(plusAB)
            t = TestVisitor(enters=[a,b],
                            leaves=[a,b])
            p.visit(t)

            c=DummyModule("c")
            d=DummyModule("d")
            e=DummyModule("e")
            f=DummyModule("f")
            g=DummyModule("g")
            ct1 = ConditionalTask(d)
            ct2 = ConditionalTask(e, ct1)
            ct3 = ConditionalTask(f, g, ct2)
            s=Sequence(plusAB, ct3, ct2)
            multSC = s*c
            p=Path(multSC, ct1, ct2)
            l = []
            v = ModuleNodeVisitor(l)
            p.visit(v)
            expected = [a,b,f,g,e,d,e,d,c,d,e,d]
            self.assertEqual(expected,l)


            t1 = Task(d)
            t2 = Task(e, t1)
            t3 = Task(f, g, t2)
            s=Sequence(plusAB, t3, t2)
            multSC = s*c
            p=Path(multSC, t1, t2)

            l = []
            v = ModuleNodeVisitor(l)
            p.visit(v)
            expected = [a,b,f,g,e,d,e,d,c,d,e,d]
            self.assertEqual(expected,l)

            l[:] = []
            v = ModuleNodeOnTaskVisitor(l)
            p.visit(v)
            expected = [f,g,e,d,e,d,d,e,d]
            self.assertEqual(expected,l)

            l[:] = []
            v = ModuleNodeNotOnTaskVisitor(l)
            p.visit(v)
            expected = [a,b,c]
            self.assertEqual(expected,l)


            t=TestVisitor(enters=[s,a,b,t3,f,g,t2,e,t1,d,t2,e,t1,d,c,t1,d,t2,e,t1,d],
                          leaves=[a,b,f,g,e,d,t1,t2,t3,e,d,t1,t2,s,c,d,t1,e,d,t1,t2])
            p.visit(t)

            notA= ~a
            p=Path(notA)
            t=TestVisitor(enters=[notA,a],leaves=[a,notA])
            p.visit(t)
        def testResolve(self):
            m1 = DummyModule("m1")
            m2 = DummyModule("m2")
            s1 = Sequence(m1)
            s2 = SequencePlaceholder("s3")
            s3 = Sequence(m2)
            p = Path(s1*s2)
            l = list()
            #resolver = ResolveVisitor(d)
            #p.visit(resolver)
            namesVisitor = DecoratedNodeNameVisitor(l)
            p.visit(namesVisitor)
            self.assertEqual(l, ['m1'])
            p.resolve(dict(s1=s1, s2=s2, s3=s3))
            l[:] = []
            p.visit(namesVisitor)
            self.assertEqual(l, ['m1', 'm2'])
            l[:]=[]
            s1 = Sequence(m1)
            s2 = SequencePlaceholder("s3")
            s3 = Sequence(m2)
            s4 = SequencePlaceholder("s2")
            p=Path(s1+s4)
            p.resolve(dict(s1=s1, s2=s2, s3=s3, s4=s4))
            p.visit(namesVisitor)
            self.assertEqual(l, ['m1', 'm2'])
            l[:]=[]
            m3 = DummyModule("m3")
            m4 = DummyModule("m4")
            s1 = Sequence(~m1)
            s2 = SequencePlaceholder("s3")
            s3 = Sequence(ignore(m2))
            s4 = Sequence(wait(m3) + ignore(wait(m4)))
            d = dict(s1=s1, s2=s2, s3=s3, s4=s4)
            p = Path(s1*s2*s4)
            p.resolve(dict(s1=s1, s2=s2, s3=s3, s4=s4))
            p.visit(namesVisitor)
            self.assertEqual(l, ['!m1', '-m2', '|m3', '+m4'])
        def testReplace(self):
            m1 = DummyModule("m1")
            m2 = DummyModule("m2")
            m3 = DummyModule("m3")
            m4 = DummyModule("m4")
            m5 = DummyModule("m5")

            s1 = Sequence(m1*~m2*m1*m2*ignore(m2))
            s2 = Sequence(m1*m2)
            l = []
            namesVisitor = DecoratedNodeNameVisitor(l)
            s1.visit(namesVisitor)
            self.assertEqual(l,['m1', '!m2', 'm1', 'm2', '-m2'])

            s3 = Sequence(~m1*s2)
            s3.replace(~m1, m2)
            l[:] = []
            s3.visit(namesVisitor)
            self.assertEqual(l, ['m2', 'm1', 'm2'])
            s3.replace(m2, ~m1)
            l[:] = []
            s3.visit(namesVisitor)
            self.assertEqual(l, ['!m1', 'm1', '!m1'])

            s3 = Sequence(ignore(m1)*s2)
            s3.replace(ignore(m1), m2)
            l[:] = []
            s3.visit(namesVisitor)
            self.assertEqual(l, ['m2', 'm1', 'm2'])
            s3.replace(m2, ignore(m1))
            l[:] = []
            s3.visit(namesVisitor)
            self.assertEqual(l, ['-m1', 'm1', '-m1'])

            ph = SequencePlaceholder('x')
            s4 = Sequence(Sequence(ph))
            s4.replace(ph,m2)
            self.assertEqual(s4.dumpPython(), "cms.Sequence(process.m2)\n")

            s1.replace(m2,m3)
            l[:] = []
            s1.visit(namesVisitor)
            self.assertEqual(l,['m1', '!m3', 'm1', 'm3', '-m3'])
            s2 = Sequence(m1*m2)
            s3 = Sequence(~m1*s2)
            l[:] = []
            s3.visit(namesVisitor)
            self.assertEqual(l,['!m1', 'm1', 'm2'])
            l[:] = []
            s3.replace(s2,m1)
            s3.visit(namesVisitor)
            self.assertEqual(l,['!m1', 'm1'])

            s1 = Sequence(m1+m2)
            s2 = Sequence(m3+m4)
            s3 = Sequence(s1+s2)
            s3.replace(m3,m5)
            l[:] = []
            s3.visit(namesVisitor)
            self.assertEqual(l,['m1','m2','m5','m4'])

            m6 = DummyModule("m6")
            m7 = DummyModule("m7")
            m8 = DummyModule("m8")
            m9 = DummyModule("m9")

            #Task
            t6 = Task(m6)
            t7 = Task(m7)
            t89 = Task(m8, m9)

            s1 = Sequence(m1+m2, t6)
            s2 = Sequence(m3+m4, t7)
            s3 = Sequence(s1+s2, t89)
            s3.replace(m3,m5)
            l[:] = []
            s3.visit(namesVisitor)
            self.assertEqual(l,['m1','m2','m5','m4'])

            s3.replace(m8,m1)
            self.assertTrue(s3.dumpPython() == "cms.Sequence(cms.Sequence(process.m1+process.m2, cms.Task(process.m6))+process.m5+process.m4, cms.Task(process.m1, process.m9), cms.Task(process.m7))\n")

            s3.replace(m1,m7)
            self.assertTrue(s3.dumpPython() == "cms.Sequence(process.m7+process.m2+process.m5+process.m4, cms.Task(process.m6), cms.Task(process.m7), cms.Task(process.m7, process.m9))\n")
            result = s3.replace(t7, t89)
            self.assertTrue(s3.dumpPython() == "cms.Sequence(process.m7+process.m2+process.m5+process.m4, cms.Task(process.m6), cms.Task(process.m7, process.m9), cms.Task(process.m8, process.m9))\n")
            self.assertTrue(result)
            result = s3.replace(t7, t89)
            self.assertFalse(result)

            t1 = Task()
            t1.replace(m1,m2)
            self.assertTrue(t1.dumpPython() == "cms.Task()\n")

            t1 = Task(m1)
            t1.replace(m1,m2)
            self.assertTrue(t1.dumpPython() == "cms.Task(process.m2)\n")

            t1 = Task(m1,m2, m2)
            t1.replace(m2,m3)
            self.assertTrue(t1.dumpPython() == "cms.Task(process.m1, process.m3)\n")

            t1 = Task(m1,m2)
            t2 = Task(m1,m3,t1)
            t2.replace(m1,m4)
            self.assertTrue(t2.dumpPython() == "cms.Task(process.m2, process.m3, process.m4)\n")

            t1 = Task(m2)
            t2 = Task(m1,m3,t1)
            t2.replace(m1,m4)
            self.assertTrue(t2.dumpPython() == "cms.Task(process.m2, process.m3, process.m4)\n")

            t1 = Task(m2)
            t2 = Task(m1,m3,t1)
            t2.replace(t1,m4)
            self.assertTrue(t2.dumpPython() == "cms.Task(process.m1, process.m3, process.m4)\n")

            t1 = Task(m2)
            t2 = Task(m1,m3,t1)
            t3 = Task(m5)
            t2.replace(m2,t3)
            self.assertTrue(t2.dumpPython() == "cms.Task(process.m1, process.m3, process.m5)\n")

            #ConditionalTask
            ct6 = ConditionalTask(m6)
            ct7 = ConditionalTask(m7)
            ct89 = ConditionalTask(m8, m9)

            cs1 = Sequence(m1+m2, ct6)
            cs2 = Sequence(m3+m4, ct7)
            cs3 = Sequence(cs1+cs2, ct89)
            cs3.replace(m3,m5)
            l[:] = []
            cs3.visit(namesVisitor)
            self.assertEqual(l,['m1','m2','m5','m4'])

            cs3.replace(m8,m1)
            self.assertEqual(cs3.dumpPython(), "cms.Sequence(cms.Sequence(process.m1+process.m2, cms.ConditionalTask(process.m6))+process.m5+process.m4, cms.ConditionalTask(process.m1, process.m9), cms.ConditionalTask(process.m7))\n")

            cs3.replace(m1,m7)
            self.assertEqual(cs3.dumpPython(), "cms.Sequence(process.m7+process.m2+process.m5+process.m4, cms.ConditionalTask(process.m6), cms.ConditionalTask(process.m7), cms.ConditionalTask(process.m7, process.m9))\n")
            result = cs3.replace(ct7, ct89)
            self.assertEqual(cs3.dumpPython(), "cms.Sequence(process.m7+process.m2+process.m5+process.m4, cms.ConditionalTask(process.m6), cms.ConditionalTask(process.m7, process.m9), cms.ConditionalTask(process.m8, process.m9))\n")
            self.assertTrue(result)
            result = cs3.replace(ct7, ct89)
            self.assertFalse(result)

            ct1 = ConditionalTask()
            ct1.replace(m1,m2)
            self.assertEqual(ct1.dumpPython(), "cms.ConditionalTask()\n")

            ct1 = ConditionalTask(m1)
            ct1.replace(m1,m2)
            self.assertEqual(ct1.dumpPython(), "cms.ConditionalTask(process.m2)\n")

            ct1 = ConditionalTask(m1,m2, m2)
            ct1.replace(m2,m3)
            self.assertEqual(ct1.dumpPython(), "cms.ConditionalTask(process.m1, process.m3)\n")

            ct1 = ConditionalTask(m1,m2)
            ct2 = ConditionalTask(m1,m3,ct1)
            ct2.replace(m1,m4)
            self.assertEqual(ct2.dumpPython(), "cms.ConditionalTask(process.m2, process.m3, process.m4)\n")

            ct1 = ConditionalTask(m2)
            ct2 = ConditionalTask(m1,m3,ct1)
            ct2.replace(m1,m4)
            self.assertEqual(ct2.dumpPython(), "cms.ConditionalTask(process.m2, process.m3, process.m4)\n")

            ct1 = ConditionalTask(m2)
            ct2 = ConditionalTask(m1,m3,ct1)
            ct2.replace(ct1,m4)
            self.assertEqual(ct2.dumpPython(), "cms.ConditionalTask(process.m1, process.m3, process.m4)\n")

            ct1 = ConditionalTask(m2)
            ct2 = ConditionalTask(m1,m3,ct1)
            ct3 = ConditionalTask(m5)
            ct2.replace(m2,ct3)
            self.assertEqual(ct2.dumpPython(), "cms.ConditionalTask(process.m1, process.m3, process.m5)\n")

            #FinalPath
            fp = FinalPath()
            fp.replace(m1,m2)
            self.assertEqual(fp.dumpPython(), "cms.FinalPath()\n")
            fp = FinalPath(m1)
            fp.replace(m1,m2)
            self.assertEqual(fp.dumpPython(), "cms.FinalPath(process.m2)\n")

        def testReplaceIfHeldDirectly(self):
            m1 = DummyModule("m1")
            m2 = DummyModule("m2")
            m3 = DummyModule("m3")
            m4 = DummyModule("m4")
            m5 = DummyModule("m5")

            s1 = Sequence(m1*~m2*m1*m2*ignore(m2))
            s1._replaceIfHeldDirectly(m2,m3)
            self.assertEqual(s1.dumpPython()[:-1],
                             "cms.Sequence(process.m1+~process.m3+process.m1+process.m3+cms.ignore(process.m3))")

            s2 = Sequence(m1*m2)
            l = []
            s3 = Sequence(~m1*s2)
            s3._replaceIfHeldDirectly(~m1, m2)
            self.assertEqual(s3.dumpPython()[:-1],
                             "cms.Sequence(process.m2+(process.m1+process.m2))")
            #Task
            m6 = DummyModule("m6")
            m7 = DummyModule("m7")
            m8 = DummyModule("m8")
            m9 = DummyModule("m9")
            t6 = Task(m6)
            t7 = Task(m7)
            t89 = Task(m8, m9)

            s1 = Sequence(m1+m2, t6)
            s2 = Sequence(m3+m4, t7)
            s3 = Sequence(s1+s2, t89)
            s3._replaceIfHeldDirectly(m3,m5)
            self.assertEqual(s3.dumpPython()[:-1], "cms.Sequence(cms.Sequence(process.m1+process.m2, cms.Task(process.m6))+cms.Sequence(process.m3+process.m4, cms.Task(process.m7)), cms.Task(process.m8, process.m9))")
            s2._replaceIfHeldDirectly(m3,m5)
            self.assertEqual(s2.dumpPython()[:-1],"cms.Sequence(process.m5+process.m4, cms.Task(process.m7))")
            self.assertEqual(s3.dumpPython()[:-1], "cms.Sequence(cms.Sequence(process.m1+process.m2, cms.Task(process.m6))+cms.Sequence(process.m5+process.m4, cms.Task(process.m7)), cms.Task(process.m8, process.m9))")

            s1 = Sequence(t6)
            s1._replaceIfHeldDirectly(t6,t7)
            self.assertEqual(s1.dumpPython()[:-1],"cms.Sequence(cms.Task(process.m7))")

            #ConditionalTask
            ct6 = ConditionalTask(m6)
            ct7 = ConditionalTask(m7)
            ct89 = ConditionalTask(m8, m9)

            s1 = Sequence(m1+m2, ct6)
            s2 = Sequence(m3+m4, ct7)
            s3 = Sequence(s1+s2, ct89)
            s3._replaceIfHeldDirectly(m3,m5)
            self.assertEqual(s3.dumpPython()[:-1], "cms.Sequence(cms.Sequence(process.m1+process.m2, cms.ConditionalTask(process.m6))+cms.Sequence(process.m3+process.m4, cms.ConditionalTask(process.m7)), cms.ConditionalTask(process.m8, process.m9))")
            s2._replaceIfHeldDirectly(m3,m5)
            self.assertEqual(s2.dumpPython()[:-1],"cms.Sequence(process.m5+process.m4, cms.ConditionalTask(process.m7))")
            self.assertEqual(s3.dumpPython()[:-1], "cms.Sequence(cms.Sequence(process.m1+process.m2, cms.ConditionalTask(process.m6))+cms.Sequence(process.m5+process.m4, cms.ConditionalTask(process.m7)), cms.ConditionalTask(process.m8, process.m9))")

            s1 = Sequence(ct6)
            s1._replaceIfHeldDirectly(ct6,ct7)
            self.assertEqual(s1.dumpPython()[:-1],"cms.Sequence(cms.ConditionalTask(process.m7))")
        def testIndex(self):
            m1 = DummyModule("a")
            m2 = DummyModule("b")
            m3 = DummyModule("c")

            s = Sequence(m1+m2+m3)
            self.assertEqual(s.index(m1),0)
            self.assertEqual(s.index(m2),1)
            self.assertEqual(s.index(m3),2)

        def testInsert(self):
            m1 = DummyModule("a")
            m2 = DummyModule("b")
            m3 = DummyModule("c")
            s = Sequence(m1+m3)
            s.insert(1,m2)
            self.assertEqual(s.index(m1),0)
            self.assertEqual(s.index(m2),1)
            self.assertEqual(s.index(m3),2)

            s = Sequence()
            s.insert(0, m1)
            self.assertEqual(s.index(m1),0)

            p = Path()
            p.insert(0, m1)
            self.assertEqual(s.index(m1),0)

        def testExpandAndClone(self):
            m1 = DummyModule("m1")
            m2 = DummyModule("m2")
            m3 = DummyModule("m3")
            m4 = DummyModule("m4")
            m5 = DummyModule("m5")

            s1 = Sequence(m1*~m2*m1*m2*ignore(m2))
            s2 = Sequence(m1*m2)
            s3 = Sequence(~m1*s2)

            p = Path(s1+s3)
            p2 = p.expandAndClone()
            l = []
            namesVisitor = DecoratedNodeNameVisitor(l)
            p2.visit(namesVisitor)
            self.assertEqual(l, ['m1', '!m2', 'm1', 'm2', '-m2', '!m1', 'm1', 'm2'])

            #Task
            m6 = DummyModule("m6")
            m7 = DummyModule("m7")
            m8 = DummyModule("m8")
            m9 = DummyModule("m9")
            p = Path(s1+s3, Task(m6))
            p2 = p.expandAndClone()
            l[:] = []
            p2.visit(namesVisitor)
            self.assertEqual(l, ['m1', '!m2', 'm1', 'm2', '-m2', '!m1', 'm1', 'm2'])
            self.assertEqual(p2.dumpPython(), "cms.Path(process.m1+~process.m2+process.m1+process.m2+cms.ignore(process.m2)+~process.m1+process.m1+process.m2, cms.Task(process.m6))\n")

            s2 = Sequence(m1*m2, Task(m9))
            s3 = Sequence(~m1*s2)
            t8 = Task(m8)
            t8.setLabel("t8")
            p = Path(s1+s3, Task(m6, Task(m7, t8)))
            p2 = p.expandAndClone()
            l[:] = []
            p2.visit(namesVisitor)
            self.assertEqual(l, ['m1', '!m2', 'm1', 'm2', '-m2', '!m1', 'm1', 'm2'])
            self.assertTrue(p2.dumpPython() == "cms.Path(process.m1+~process.m2+process.m1+process.m2+cms.ignore(process.m2)+~process.m1+process.m1+process.m2, cms.Task(process.m6, process.m7, process.m8, process.m9))\n")

            t1 = Task(m1,m2,m3)
            s1 = Sequence(t1)
            s2 = s1.expandAndClone()
            l[:] = []
            s2.visit(namesVisitor)
            self.assertEqual(l, [])
            self.assertTrue(s2.dumpPython() == "cms.Sequence(cms.Task(process.m1, process.m2, process.m3))\n")

            t1 = Task(m1,m2)
            t2 = Task(m1,m3,t1)
            t3 = t2.expandAndClone()
            self.assertTrue(t3.dumpPython() == "cms.Task(process.m1, process.m2, process.m3)\n")
            t4 = Task()
            t5 = t4.expandAndClone()
            self.assertTrue(t5.dumpPython() == "cms.Task()\n")
            #ConditionalTask
            s1 = Sequence(m1*~m2*m1*m2*ignore(m2))
            s2 = Sequence(m1*m2)
            s3 = Sequence(~m1*s2)
            p = Path(s1+s3, ConditionalTask(m6))
            p2 = p.expandAndClone()
            l[:] = []
            p2.visit(namesVisitor)
            self.assertEqual(l, ['m1', '!m2', 'm1', 'm2', '-m2', '!m1', 'm1', 'm2'])
            self.assertEqual(p2.dumpPython(), "cms.Path(process.m1+~process.m2+process.m1+process.m2+cms.ignore(process.m2)+~process.m1+process.m1+process.m2, cms.ConditionalTask(process.m6))\n")

            s2 = Sequence(m1*m2, ConditionalTask(m9))
            s3 = Sequence(~m1*s2)
            ct8 = ConditionalTask(m8)
            ct8.setLabel("ct8")
            p = Path(s1+s3, ConditionalTask(m6, ConditionalTask(m7, ct8)))
            p2 = p.expandAndClone()
            l[:] = []
            p2.visit(namesVisitor)
            self.assertEqual(l, ['m1', '!m2', 'm1', 'm2', '-m2', '!m1', 'm1', 'm2'])
            self.assertEqual(p2.dumpPython(), "cms.Path(process.m1+~process.m2+process.m1+process.m2+cms.ignore(process.m2)+~process.m1+process.m1+process.m2, cms.ConditionalTask(process.m6, process.m7, process.m8, process.m9))\n")

            t1 = ConditionalTask(m1,m2,m3)
            s1 = Sequence(t1)
            s2 = s1.expandAndClone()
            l[:] = []
            s2.visit(namesVisitor)
            self.assertEqual(l, [])
            self.assertEqual(s2.dumpPython(), "cms.Sequence(cms.ConditionalTask(process.m1, process.m2, process.m3))\n")

            t1 = ConditionalTask(m1,m2)
            t2 = ConditionalTask(m1,m3,t1)
            t3 = t2.expandAndClone()
            self.assertEqual(t3.dumpPython(), "cms.ConditionalTask(process.m1, process.m2, process.m3)\n")
            t4 = ConditionalTask()
            t5 = t4.expandAndClone()
            self.assertTrue(t5.dumpPython() == "cms.ConditionalTask()\n")

        def testAdd(self):
            m1 = DummyModule("m1")
            m2 = DummyModule("m2")
            m3 = DummyModule("m3")
            m4 = DummyModule("m4")
            s1 = Sequence(m1)
            s3 = Sequence(m3+ignore(m4))
            p = Path(s1)
            p += ~m2
            p *= s3

            l = []
            namesVisitor = DecoratedNodeNameVisitor(l)
            p.visit(namesVisitor)
            self.assertEqual(l, ['m1', '!m2', 'm3', '-m4'])

            s4 = Sequence()
            s4 +=m1
            l[:]=[]; s1.visit(namesVisitor); self.assertEqual(l,['m1'])
            self.assertEqual(s4.dumpPython(),"cms.Sequence(process.m1)\n")
            s4 = Sequence()
            s4 *=m1
            l[:]=[]; s1.visit(namesVisitor); self.assertEqual(l,['m1'])
            self.assertEqual(s4.dumpPython(),"cms.Sequence(process.m1)\n")


        def testRemove(self):
            m1 = DummyModule("m1")
            m2 = DummyModule("m2")
            m3 = DummyModule("m3")
            m4 = DummyModule("m4")
            s1 = Sequence(m1*m2+~m3)
            s2 = Sequence(m1*s1)
            l = []
            namesVisitor = DecoratedNodeNameVisitor(l)
            d = {'m1':m1 ,'m2':m2, 'm3':m3,'s1':s1, 's2':s2}
            l[:] = []; s1.visit(namesVisitor); self.assertEqual(l,['m1', 'm2', '!m3'])
            l[:] = []; s2.visit(namesVisitor); self.assertEqual(l,['m1', 'm1', 'm2', '!m3'])
            s1.remove(m2)
            l[:] = []; s1.visit(namesVisitor); self.assertEqual(l,['m1', '!m3'])
            l[:] = []; s2.visit(namesVisitor); self.assertEqual(l,['m1', 'm1', '!m3'])
            s2.remove(m3)
            l[:] = []; s1.visit(namesVisitor); self.assertEqual(l,['m1', '!m3'])
            l[:] = []; s2.visit(namesVisitor); self.assertEqual(l,['m1', 'm1'])
            s1 = Sequence( m1 + m2 + m1 + m2 )
            l[:] = []; s1.visit(namesVisitor); self.assertEqual(l,['m1', 'm2', 'm1', 'm2'])
            s1.remove(m2)
            l[:] = []; s1.visit(namesVisitor); self.assertEqual(l,['m1', 'm1', 'm2'])
            s1 = Sequence( m1 + m3 )
            s2 = Sequence( m2 + ignore(m3) + s1 + m3 )
            l[:] = []; s2.visit(namesVisitor); self.assertEqual(l,['m2', '-m3', 'm1', 'm3', 'm3'])
            s2.remove(s1)
            l[:] = []; s2.visit(namesVisitor); self.assertEqual(l,['m2', '-m3', 'm3'])
            s2.remove(m3)
            l[:] = []; s2.visit(namesVisitor); self.assertEqual(l,['m2','m3'])
            s1 = Sequence(m1*m2*m3)
            self.assertEqual(s1.dumpPython(), "cms.Sequence(process.m1+process.m2+process.m3)\n")
            s1.remove(m2)
            self.assertEqual(s1.dumpPython(), "cms.Sequence(process.m1+process.m3)\n")
            s1 = Sequence(m1+m2+m3)
            self.assertEqual(s1.dumpPython(), "cms.Sequence(process.m1+process.m2+process.m3)\n")
            s1.remove(m2)
            self.assertEqual(s1.dumpPython(), "cms.Sequence(process.m1+process.m3)\n")
            s1 = Sequence(m1*m2+m3)
            self.assertEqual(s1.dumpPython(), "cms.Sequence(process.m1+process.m2+process.m3)\n")
            s1.remove(m2)
            self.assertEqual(s1.dumpPython(), "cms.Sequence(process.m1+process.m3)\n")
            s1 = Sequence(m1+m2*m3)
            self.assertEqual(s1.dumpPython(), "cms.Sequence(process.m1+process.m2+process.m3)\n")
            s1.remove(m2)
            self.assertEqual(s1.dumpPython(), "cms.Sequence(process.m1+process.m3)\n")
            s1.remove(m1)
            s1.remove(m3)
            l[:]=[]; s1.visit(namesVisitor); self.assertEqual(l,[])
            self.assertEqual(s1.dumpPython(), "cms.Sequence()\n")
            s3 = Sequence(m1)
            s3.remove(m1)
            l[:]=[]; s3.visit(namesVisitor); self.assertEqual(l,[])
            self.assertEqual(s3.dumpPython(), "cms.Sequence()\n")
            s3 = Sequence(m1)
            s4 = Sequence(s3)
            s4.remove(m1)
            l[:]=[]; s4.visit(namesVisitor); self.assertEqual(l,[])
            self.assertEqual(s4.dumpPython(), "cms.Sequence()\n")
            #Task
            s1 = Sequence(m1+m2, Task(m3), Task(m4))
            s1.remove(m4)
            self.assertEqual(s1.dumpPython(), "cms.Sequence(process.m1+process.m2, cms.Task(process.m3))\n")
            s1 = Sequence(m1+m2+Sequence(Task(m3,m4), Task(m3), Task(m4)))
            s1.remove(m4)
            self.assertEqual(s1.dumpPython(), "cms.Sequence(process.m1+process.m2, cms.Task(process.m3), cms.Task(process.m4))\n")
            t1 = Task(m1)
            t1.setLabel("t1")
            t2 = Task(m2,t1)
            t2.setLabel("t2")
            t3 = Task(t1,t2,m1)
            t3.remove(m1)
            self.assertTrue(t3.dumpPython() == "cms.Task(process.m1, process.t2)\n")
            t3.remove(m1)
            self.assertTrue(t3.dumpPython() == "cms.Task(process.m1, process.m2)\n")
            t3.remove(m1)
            self.assertTrue(t3.dumpPython() == "cms.Task(process.m2)\n")
            t3.remove(m2)
            self.assertTrue(t3.dumpPython() == "cms.Task()\n")
            #ConditionalTask
            s1 = Sequence(m1+m2, ConditionalTask(m3), ConditionalTask(m4))
            s1.remove(m4)
            self.assertEqual(s1.dumpPython(), "cms.Sequence(process.m1+process.m2, cms.ConditionalTask(process.m3))\n")
            s1 = Sequence(m1+m2+Sequence(ConditionalTask(m3,m4), ConditionalTask(m3), ConditionalTask(m4)))
            s1.remove(m4)
            self.assertEqual(s1.dumpPython(), "cms.Sequence(process.m1+process.m2, cms.ConditionalTask(process.m3), cms.ConditionalTask(process.m4))\n")
            t1 = ConditionalTask(m1)
            t1.setLabel("t1")
            t2 = ConditionalTask(m2,t1)
            t2.setLabel("t2")
            t3 = ConditionalTask(t1,t2,m1)
            t3.remove(m1)
            self.assertEqual(t3.dumpPython(), "cms.ConditionalTask(process.m1, process.t2)\n")
            t3.remove(m1)
            self.assertEqual(t3.dumpPython(), "cms.ConditionalTask(process.m1, process.m2)\n")
            t3.remove(m1)
            self.assertEqual(t3.dumpPython(), "cms.ConditionalTask(process.m2)\n")
            t3.remove(m2)
            self.assertEqual(t3.dumpPython(), "cms.ConditionalTask()\n")
            #FinalPath
            fp = FinalPath(m1+m2)
            fp.remove(m1)
            self.assertEqual(fp.dumpPython(), "cms.FinalPath(process.m2)\n")
            fp = FinalPath(m1)
            fp.remove(m1)
            self.assertEqual(fp.dumpPython(), "cms.FinalPath()\n")

        def testCopyAndExclude(self):
            a = DummyModule("a")
            b = DummyModule("b")
            c = DummyModule("c")
            d = DummyModule("d")
            s = Sequence(a+b+c)
            self.assertEqual(s.copyAndExclude([d]).dumpPython(),"cms.Sequence(process.a+process.b+process.c)\n")
            s = Sequence(a+b+c+d)
            self.assertEqual(s.copyAndExclude([a]).dumpPython(),"cms.Sequence(process.b+process.c+process.d)\n")
            self.assertEqual(s.copyAndExclude([b]).dumpPython(),"cms.Sequence(process.a+process.c+process.d)\n")
            self.assertEqual(s.copyAndExclude([c]).dumpPython(),"cms.Sequence(process.a+process.b+process.d)\n")
            self.assertEqual(s.copyAndExclude([d]).dumpPython(),"cms.Sequence(process.a+process.b+process.c)\n")
            s=Sequence(a*b+c+d)
            self.assertEqual(s.copyAndExclude([a]).dumpPython(),"cms.Sequence(process.b+process.c+process.d)\n")
            self.assertEqual(s.copyAndExclude([b]).dumpPython(),"cms.Sequence(process.a+process.c+process.d)\n")
            self.assertEqual(s.copyAndExclude([c]).dumpPython(),"cms.Sequence(process.a+process.b+process.d)\n")
            self.assertEqual(s.copyAndExclude([d]).dumpPython(),"cms.Sequence(process.a+process.b+process.c)\n")
            s = Sequence(a+b*c+d)
            self.assertEqual(s.copyAndExclude([a]).dumpPython(),"cms.Sequence(process.b+process.c+process.d)\n")
            self.assertEqual(s.copyAndExclude([b]).dumpPython(),"cms.Sequence(process.a+process.c+process.d)\n")
            self.assertEqual(s.copyAndExclude([c]).dumpPython(),"cms.Sequence(process.a+process.b+process.d)\n")
            self.assertEqual(s.copyAndExclude([d]).dumpPython(),"cms.Sequence(process.a+process.b+process.c)\n")
            s2 = Sequence(a+b)
            s = Sequence(c+s2+d)
            self.assertEqual(s.copyAndExclude([a]).dumpPython(),"cms.Sequence(process.c+process.b+process.d)\n")
            self.assertEqual(s.copyAndExclude([b]).dumpPython(),"cms.Sequence(process.c+process.a+process.d)\n")
            self.assertEqual(s.copyAndExclude([c]).dumpPython(),"cms.Sequence((process.a+process.b)+process.d)\n")
            self.assertEqual(s.copyAndExclude([d]).dumpPython(),"cms.Sequence(process.c+(process.a+process.b))\n")
            self.assertEqual(s.copyAndExclude([a,b]).dumpPython(),"cms.Sequence(process.c+process.d)\n")
            s3 = s.copyAndExclude([c])
            s2.remove(a)
            self.assertEqual(s3.dumpPython(),"cms.Sequence((process.b)+process.d)\n")
            s4 = s.copyAndExclude([a,b])
            seqs = []
            sequenceVisitor = SequenceVisitor(seqs)
            s.visit(sequenceVisitor)
            self.assertEqual(len(seqs),1)
            seqs[:] = []
            s4.visit(sequenceVisitor)
            self.assertEqual(len(seqs),0)
            self.assertEqual(s4.dumpPython(),"cms.Sequence(process.c+process.d)\n")
            holder = SequencePlaceholder("x")
            s3 = Sequence(b+d,Task(a))
            s2 = Sequence(a+b+holder+s3)
            s = Sequence(c+s2+d)
            seqs[:] = []
            s.visit(sequenceVisitor)
            self.assertTrue(seqs == [s2,s3])
            s2 = Sequence(a+b+holder)
            s = Sequence(c+s2+d)
            self.assertEqual(s.copyAndExclude([holder]).dumpPython(),"cms.Sequence(process.c+process.a+process.b+process.d)\n")
            s2 = Sequence(a+b+c)
            s = Sequence(s2+d)
            self.assertEqual(s.copyAndExclude([a]).dumpPython(),"cms.Sequence(process.b+process.c+process.d)\n")
            self.assertEqual(s.copyAndExclude([b]).dumpPython(),"cms.Sequence(process.a+process.c+process.d)\n")
            self.assertEqual(s.copyAndExclude([c]).dumpPython(),"cms.Sequence(process.a+process.b+process.d)\n")
            self.assertEqual(s.copyAndExclude([d]).dumpPython(),"cms.Sequence((process.a+process.b+process.c))\n")
            self.assertEqual(s.copyAndExclude([s2]).dumpPython(),"cms.Sequence(process.d)\n")
            s2 = Sequence(a+b+c)
            s = Sequence(s2*d)
            self.assertEqual(s.copyAndExclude([a]).dumpPython(),"cms.Sequence(process.b+process.c+process.d)\n")
            self.assertEqual(s.copyAndExclude([b]).dumpPython(),"cms.Sequence(process.a+process.c+process.d)\n")
            self.assertEqual(s.copyAndExclude([c]).dumpPython(),"cms.Sequence(process.a+process.b+process.d)\n")
            self.assertEqual(s.copyAndExclude([d]).dumpPython(),"cms.Sequence((process.a+process.b+process.c))\n")
            self.assertEqual(s.copyAndExclude([a,b,c]).dumpPython(),"cms.Sequence(process.d)\n")
            s = Sequence(ignore(a)+b+c+d)
            self.assertEqual(s.copyAndExclude([a]).dumpPython(),"cms.Sequence(process.b+process.c+process.d)\n")
            self.assertEqual(s.copyAndExclude([ignore(a)]).dumpPython(),"cms.Sequence(process.b+process.c+process.d)\n")
            self.assertEqual(s.copyAndExclude([b]).dumpPython(),"cms.Sequence(cms.ignore(process.a)+process.c+process.d)\n")
            self.assertEqual(s.copyAndExclude([c]).dumpPython(),"cms.Sequence(cms.ignore(process.a)+process.b+process.d)\n")
            self.assertEqual(s.copyAndExclude([d]).dumpPython(),"cms.Sequence(cms.ignore(process.a)+process.b+process.c)\n")
            s = Sequence(a+ignore(b)+c+d)
            self.assertEqual(s.copyAndExclude([a]).dumpPython(),"cms.Sequence(cms.ignore(process.b)+process.c+process.d)\n")
            self.assertEqual(s.copyAndExclude([b]).dumpPython(),"cms.Sequence(process.a+process.c+process.d)\n")
            self.assertEqual(s.copyAndExclude([c]).dumpPython(),"cms.Sequence(process.a+cms.ignore(process.b)+process.d)\n")
            self.assertEqual(s.copyAndExclude([d]).dumpPython(),"cms.Sequence(process.a+cms.ignore(process.b)+process.c)\n")
            s = Sequence(a+b+c+ignore(d))
            self.assertEqual(s.copyAndExclude([a]).dumpPython(),"cms.Sequence(process.b+process.c+cms.ignore(process.d))\n")
            self.assertEqual(s.copyAndExclude([b]).dumpPython(),"cms.Sequence(process.a+process.c+cms.ignore(process.d))\n")
            self.assertEqual(s.copyAndExclude([c]).dumpPython(),"cms.Sequence(process.a+process.b+cms.ignore(process.d))\n")
            self.assertEqual(s.copyAndExclude([d]).dumpPython(),"cms.Sequence(process.a+process.b+process.c)\n")
            s = Sequence(~a+b+c+d)
            self.assertEqual(s.copyAndExclude([a]).dumpPython(),"cms.Sequence(process.b+process.c+process.d)\n")
            self.assertEqual(s.copyAndExclude([b]).dumpPython(),"cms.Sequence(~process.a+process.c+process.d)\n")
            self.assertEqual(s.copyAndExclude([c]).dumpPython(),"cms.Sequence(~process.a+process.b+process.d)\n")
            self.assertEqual(s.copyAndExclude([d]).dumpPython(),"cms.Sequence(~process.a+process.b+process.c)\n")
            s = Sequence(a+~b+c+d)
            self.assertEqual(s.copyAndExclude([a]).dumpPython(),"cms.Sequence(~process.b+process.c+process.d)\n")
            self.assertEqual(s.copyAndExclude([b]).dumpPython(),"cms.Sequence(process.a+process.c+process.d)\n")
            self.assertEqual(s.copyAndExclude([~b]).dumpPython(),"cms.Sequence(process.a+process.c+process.d)\n")
            self.assertEqual(s.copyAndExclude([c]).dumpPython(),"cms.Sequence(process.a+~process.b+process.d)\n")
            self.assertEqual(s.copyAndExclude([d]).dumpPython(),"cms.Sequence(process.a+~process.b+process.c)\n")
            s = Sequence(a+b+c+~d)
            self.assertEqual(s.copyAndExclude([a]).dumpPython(),"cms.Sequence(process.b+process.c+~process.d)\n")
            self.assertEqual(s.copyAndExclude([b]).dumpPython(),"cms.Sequence(process.a+process.c+~process.d)\n")
            self.assertEqual(s.copyAndExclude([c]).dumpPython(),"cms.Sequence(process.a+process.b+~process.d)\n")
            self.assertEqual(s.copyAndExclude([d]).dumpPython(),"cms.Sequence(process.a+process.b+process.c)\n")
            self.assertEqual(s.copyAndExclude([a,b,c,d]).dumpPython(),"cms.Sequence()\n")

            #Task
            e = DummyModule("e")
            f = DummyModule("f")
            g = DummyModule("g")
            h = DummyModule("h")
            t1 = Task(h)
            s = Sequence(a+b+c+~d, Task(e,f,Task(g,t1)))
            self.assertEqual(s.copyAndExclude([a,h]).dumpPython(),"cms.Sequence(process.b+process.c+~process.d, cms.Task(process.e, process.f, process.g))\n")
            self.assertEqual(s.copyAndExclude([a,h]).dumpPython(),"cms.Sequence(process.b+process.c+~process.d, cms.Task(process.e, process.f, process.g))\n")
            self.assertEqual(s.copyAndExclude([a,e,h]).dumpPython(),"cms.Sequence(process.b+process.c+~process.d, cms.Task(process.f, process.g))\n")
            self.assertEqual(s.copyAndExclude([a,e,f,g,h]).dumpPython(),"cms.Sequence(process.b+process.c+~process.d)\n")
            self.assertEqual(s.copyAndExclude([a,b,c,d]).dumpPython(),"cms.Sequence(cms.Task(process.e, process.f, process.g, process.h))\n")
            self.assertEqual(s.copyAndExclude([t1]).dumpPython(),"cms.Sequence(process.a+process.b+process.c+~process.d, cms.Task(process.e, process.f, process.g))\n")
            taskList = []
            taskVisitor = TaskVisitor(taskList)
            s.visit(taskVisitor)
            self.assertEqual(len(taskList),3)
            s2 = s.copyAndExclude([g,h])
            taskList[:] = []
            s2.visit(taskVisitor)
            self.assertEqual(len(taskList),1)
            t2 = Task(t1)
            taskList[:] = []
            t2.visit(taskVisitor)
            self.assertEqual(taskList[0],t1)
            s3 = Sequence(s)
            self.assertEqual(s3.copyAndExclude([a,h]).dumpPython(),"cms.Sequence(process.b+process.c+~process.d, cms.Task(process.e, process.f, process.g))\n")
            s4 = Sequence(s)
            self.assertEqual(s4.copyAndExclude([a,b,c,d,e,f,g,h]).dumpPython(),"cms.Sequence()\n")
            t1 = Task(e,f)
            t11 = Task(a)
            t11.setLabel("t11")
            t2 = Task(g,t1,h,t11)
            t3 = t2.copyAndExclude([e,h])
            self.assertTrue(t3.dumpPython() == "cms.Task(process.f, process.g, process.t11)\n")
            t4 = t2.copyAndExclude([e,f,g,h,a])
            self.assertTrue(t4.dumpPython() == "cms.Task()\n")
            #ConditionalTask
            t1 = ConditionalTask(h)
            s = Sequence(a+b+c+~d, ConditionalTask(e,f,ConditionalTask(g,t1)))
            self.assertEqual(s.copyAndExclude([a,h]).dumpPython(),"cms.Sequence(process.b+process.c+~process.d, cms.ConditionalTask(process.e, process.f, process.g))\n")
            self.assertEqual(s.copyAndExclude([a,h]).dumpPython(),"cms.Sequence(process.b+process.c+~process.d, cms.ConditionalTask(process.e, process.f, process.g))\n")
            self.assertEqual(s.copyAndExclude([a,e,h]).dumpPython(),"cms.Sequence(process.b+process.c+~process.d, cms.ConditionalTask(process.f, process.g))\n")
            self.assertEqual(s.copyAndExclude([a,e,f,g,h]).dumpPython(),"cms.Sequence(process.b+process.c+~process.d)\n")
            self.assertEqual(s.copyAndExclude([a,b,c,d]).dumpPython(),"cms.Sequence(cms.ConditionalTask(process.e, process.f, process.g, process.h))\n")
            self.assertEqual(s.copyAndExclude([t1]).dumpPython(),"cms.Sequence(process.a+process.b+process.c+~process.d, cms.ConditionalTask(process.e, process.f, process.g))\n")
            taskList = []
            taskVisitor = ConditionalTaskVisitor(taskList)
            s.visit(taskVisitor)
            self.assertEqual(len(taskList),3)
            s2 = s.copyAndExclude([g,h])
            taskList[:] = []
            s2.visit(taskVisitor)
            self.assertEqual(len(taskList),1)
            t2 = ConditionalTask(t1)
            taskList[:] = []
            t2.visit(taskVisitor)
            self.assertEqual(taskList[0],t1)
            s3 = Sequence(s)
            self.assertEqual(s3.copyAndExclude([a,h]).dumpPython(),"cms.Sequence(process.b+process.c+~process.d, cms.ConditionalTask(process.e, process.f, process.g))\n")
            s4 = Sequence(s)
            self.assertEqual(s4.copyAndExclude([a,b,c,d,e,f,g,h]).dumpPython(),"cms.Sequence()\n")
            t1 = ConditionalTask(e,f)
            t11 = ConditionalTask(a)
            t11.setLabel("t11")
            t2 = ConditionalTask(g,t1,h,t11)
            t3 = t2.copyAndExclude([e,h])
            self.assertEqual(t3.dumpPython(), "cms.ConditionalTask(process.f, process.g, process.t11)\n")
            t4 = t2.copyAndExclude([e,f,g,h,a])
            self.assertEqual(t4.dumpPython(), "cms.ConditionalTask()\n")

        def testSequenceTypeChecks(self):
            m1 = DummyModule("m1")
            m2 = DummyModule("m2")
            s1 = Sequence(m1*m2)
            def testRaise():
                s1.something = 1
            self.assertRaises(AttributeError,testRaise)
            def testRaise2():
                s2 = Sequence(m1*None)
            self.assertRaises(TypeError,testRaise2)
        def testCopy(self):
            a = DummyModule("a")
            b = DummyModule("b")
            c = DummyModule("c")
            p1 = Path(a+b+c)
            p2 = p1.copy()
            e = DummyModule("e")
            p2.replace(b,e)
            self.assertEqual(p1.dumpPython(),"cms.Path(process.a+process.b+process.c)\n")
            self.assertEqual(p2.dumpPython(),"cms.Path(process.a+process.e+process.c)\n")
            p1 = Path(a+b+c)
            p2 = p1.copy()
            p1 += e
            self.assertEqual(p1.dumpPython(),"cms.Path(process.a+process.b+process.c+process.e)\n")
            self.assertEqual(p2.dumpPython(),"cms.Path(process.a+process.b+process.c)\n")
            #Task
            t1 = Task(a, b)
            t2 = t1.copy()
            self.assertTrue(t1.dumpPython() == t2.dumpPython())
            t1Contents = list(t1._collection)
            t2Contents = list(t2._collection)
            self.assertTrue(id(t1Contents[0]) == id(t2Contents[0]))
            self.assertTrue(id(t1Contents[1]) == id(t2Contents[1]))
            self.assertTrue(id(t1._collection) != id(t2._collection))
            #ConditionalTask
            t1 = ConditionalTask(a, b)
            t2 = t1.copy()
            self.assertTrue(t1.dumpPython() == t2.dumpPython())
            t1Contents = list(t1._collection)
            t2Contents = list(t2._collection)
            self.assertTrue(id(t1Contents[0]) == id(t2Contents[0]))
            self.assertTrue(id(t1Contents[1]) == id(t2Contents[1]))
            self.assertTrue(id(t1._collection) != id(t2._collection))

        def testCopyAndAdd(self):
            a = DummyModule("a")
            b = DummyModule("b")
            c = DummyModule("c")
            d = DummyModule("d")
            e = DummyModule("e")
            #Task
            t1 = Task(a, b, c)
            self.assertEqual(t1.dumpPython(), "cms.Task(process.a, process.b, process.c)\n")
            t2 = t1.copyAndAdd(d, e)
            self.assertEqual(t1.dumpPython(), "cms.Task(process.a, process.b, process.c)\n")
            self.assertEqual(t2.dumpPython(), "cms.Task(process.a, process.b, process.c, process.d, process.e)\n")
            t3 = t2.copyAndExclude([b])
            self.assertEqual(t1.dumpPython(), "cms.Task(process.a, process.b, process.c)\n")
            self.assertEqual(t2.dumpPython(), "cms.Task(process.a, process.b, process.c, process.d, process.e)\n")
            self.assertEqual(t3.dumpPython(), "cms.Task(process.a, process.c, process.d, process.e)\n")
            t4 = t1.copyAndExclude([b]).copyAndAdd(d)
            self.assertEqual(t4.dumpPython(), "cms.Task(process.a, process.c, process.d)\n")
            t5 = t2.copyAndExclude([b]).copyAndAdd(d)
            self.assertEqual(t5.dumpPython(), "cms.Task(process.a, process.c, process.d, process.e)\n")
            t6 = t4.copyAndAdd(Task(b))
            self.assertEqual(t6.dumpPython(), "cms.Task(process.a, process.b, process.c, process.d)\n")
            #ConditionalTask
            t1 = ConditionalTask(a, b, c)
            self.assertEqual(t1.dumpPython(), "cms.ConditionalTask(process.a, process.b, process.c)\n")
            t2 = t1.copyAndAdd(d, e)
            self.assertEqual(t1.dumpPython(), "cms.ConditionalTask(process.a, process.b, process.c)\n")
            self.assertEqual(t2.dumpPython(), "cms.ConditionalTask(process.a, process.b, process.c, process.d, process.e)\n")
            t3 = t2.copyAndExclude([b])
            self.assertEqual(t1.dumpPython(), "cms.ConditionalTask(process.a, process.b, process.c)\n")
            self.assertEqual(t2.dumpPython(), "cms.ConditionalTask(process.a, process.b, process.c, process.d, process.e)\n")
            self.assertEqual(t3.dumpPython(), "cms.ConditionalTask(process.a, process.c, process.d, process.e)\n")
            t4 = t1.copyAndExclude([b]).copyAndAdd(d)
            self.assertEqual(t4.dumpPython(), "cms.ConditionalTask(process.a, process.c, process.d)\n")
            t5 = t2.copyAndExclude([b]).copyAndAdd(d)
            self.assertEqual(t5.dumpPython(), "cms.ConditionalTask(process.a, process.c, process.d, process.e)\n")
            t6 = t4.copyAndAdd(Task(b))
            self.assertEqual(t6.dumpPython(), "cms.ConditionalTask(process.a, process.b, process.c, process.d)\n")

        def testInsertInto(self):
            from FWCore.ParameterSet.Types import vstring
            class TestPSet(object):
                def __init__(self):
                    self._dict = dict()
                def addVString(self,isTracked,label,value):
                    self._dict[label]=value
            a = DummyModule("a")
            b = DummyModule("b")
            c = DummyModule("c")
            d = DummyModule("d")
            p = Path(a+b+c+d)
            decoratedList = []
            lister = DecoratedNodeNameVisitor(decoratedList)
            p.visit(lister)
            ps = TestPSet()
            p.insertInto(ps,"p",decoratedList)
            self.assertEqual(ps._dict, {"p":vstring("a","b","c","d")})
            s = Sequence(b+c)
            p = Path(a+s+d)
            decoratedList[:] = []
            p.visit(lister)
            ps = TestPSet()
            p.insertInto(ps,"p",decoratedList)
            self.assertEqual(ps._dict, {"p":vstring("a","b","c","d")})

    unittest.main()
