private Collection<Pair<List<AbstractState>, List<ARGState>>> computeCartesianProduct(
      final List<List<ARGState>> pSuccessorsForEdge,
      final Map<String, Integer> pStateToPos,
      final List<AbstractState> pInitialStates)
      throws InterruptedException, CPAException {
    // compute number of successors
    int count = 0;
    for (List<ARGState> successor : pSuccessorsForEdge) {
      if (successor.size() > 0) {
        count = count == 0 ? successor.size() : count * successor.size();
      }
    }

    // no successor in every of the ARGs
    if (count == 0) {
      return Collections.emptySet();
    }

    Collection<Pair<List<AbstractState>, List<ARGState>>> result = new ArrayList<>(count);

    // compute cartesian product
    int[] indices = new int[pSuccessorsForEdge.size()];
    int nextIndex = 0;
    boolean restart;
    int lastSize = pSuccessorsForEdge.get(pSuccessorsForEdge.size() - 1).size();

    if (lastSize == 0) {
      lastSize = 1;
    }

    while (indices[indices.length - 1] < lastSize) {
      shutdown.shutdownIfNecessary();

      final List<ARGState> argSuccessors = new ArrayList<>(pSuccessorsForEdge.size());

      // collect ARG successors
      for (int index = 0; index < indices.length; index++) {
        if (pSuccessorsForEdge.get(index).size() > 0) {
          argSuccessors.add(
              getUncoveredSuccessor(pSuccessorsForEdge.get(index).get(indices[index])));
        }
      }

      // combine ARG states to get one cartesian product element, assume top state if no explicit
      // state information available
      result.add(
          Pair.of(combineARGStates(argSuccessors, pStateToPos, pInitialStates), argSuccessors));

      // compute indices for elements of next cartesian element
      indices[nextIndex]++;
      restart = false;
      while (indices[nextIndex] >= pSuccessorsForEdge.get(nextIndex).size()
          && nextIndex < indices.length - 1) {
        nextIndex++;
        indices[nextIndex]++;
        restart = true;
      }

      while (restart && nextIndex > 0) {
        indices[--nextIndex] = 0;
      }
    }

    return result;
  }
  private Pair<Map<String, Integer>, List<AbstractState>>
      identifyCompositeStateTypesAndTheirInitialInstances(Collection<ARGState> rootNodes)
          throws InterruptedException, CPAException {
    logger.log(Level.FINE, "Derive composite state structure of combined ARG");

    List<AbstractState> initialState = new ArrayList<>();
    Map<String, Integer> stateToPos = new HashMap<>();
    List<String> automataStateNames = new ArrayList<>();

    String name;
    int nextId = 0;
    Iterable<AbstractState> wrapped;

    logger.log(Level.FINE, "Add non-automaton states");
    for (ARGState root : rootNodes) {
      shutdown.shutdownIfNecessary();

      wrapped = getWrappedStates(root);

      for (AbstractState innerWrapped : wrapped) {
        shutdown.shutdownIfNecessary();

        if (innerWrapped instanceof AssumptionStorageState) {
          continue;
        }

        name = getName(innerWrapped);

        if (stateToPos.containsKey(name)) {
          if (!initialState.get(stateToPos.get(name)).equals(innerWrapped)) {
            logger.log(
                Level.WARNING,
                "Abstract state ",
                innerWrapped.getClass(),
                " is used by multiple configurations, but cannot check that always start in the same initial state as it is assumed");
          }
        } else {
          assert (initialState.size() == nextId);

          if (innerWrapped instanceof AutomatonState) {
            automataStateNames.add(name);
          } else {
            stateToPos.put(name, nextId);
            initialState.add(innerWrapped);
            nextId++;
          }
        }
      }
    }

    logger.log(Level.FINE, "Add automaton states related to specification");
    Collections.sort(automataStateNames);

    int numRootStates = rootNodes.size();
    Set<String> commonAutomataStates = new TreeSet<>();
    for (int i = 1, j = 0; i < automataStateNames.size(); i++) {
      assert (j < i && j >= 0);
      if (automataStateNames.get(j).equals(automataStateNames.get(i))) {
        if (j + numRootStates - 1 == i) {
          // automaton states commonly used
          commonAutomataStates.add(automataStateNames.get(j));
        }
      } else {
        j = i;
      }
    }

    // assume root is the root node of the first ARG constructed
    ARGState root = rootNodes.iterator().next();

    if (root.getWrappedState() instanceof AbstractWrapperState) {
      wrapped = ((AbstractWrapperState) root.getWrappedState()).getWrappedStates();
    } else {
      wrapped = Collections.singleton(root.getWrappedState());
    }

    for (AbstractState innerWrapped : wrapped) {
      shutdown.shutdownIfNecessary();

      name = getName(innerWrapped);
      if (commonAutomataStates.contains(name)) {
        assert (initialState.size() == nextId);

        stateToPos.put(name, nextId);
        if (!automatonARGBuilderSupport.registerAutomaton((AutomatonState) innerWrapped)) {
          logger.log(
              Level.SEVERE,
              "Property specification, given by automata specification, is ambigous.");
          throw new CPAException(
              "Ambigious property specification,  automata specification contains automata with same name or same state names");
        }
        initialState.add(
            automatonARGBuilderSupport.replaceStateByStateInAutomatonOfSameInstance(
                (AutomatonState) innerWrapped));
        nextId++;
      }
    }

    return Pair.of(stateToPos, initialState);
  }
  @Override
  public Collection<? extends AbstractState> getAbstractSuccessorsForEdge(
      AbstractState pElement, Precision pPrecision, CFAEdge pCfaEdge) throws CPATransferException {

    Preconditions.checkArgument(pElement instanceof AutomatonState);

    if (pElement instanceof AutomatonUnknownState) {
      // the last CFA edge could not be processed properly
      // (strengthen was not called on the AutomatonUnknownState or the strengthen operation had not
      // enough information to determine a new following state.)
      AutomatonState top = cpa.getTopState();
      return Collections.singleton(top);
    }

    if (!(pCfaEdge instanceof MultiEdge)) {
      Collection<? extends AbstractState> result =
          getAbstractSuccessors0((AutomatonState) pElement, pPrecision, pCfaEdge);
      automatonSuccessors.setNextValue(result.size());
      return result;
    }

    final List<CFAEdge> edges = ((MultiEdge) pCfaEdge).getEdges();
    checkArgument(!edges.isEmpty());

    // As long as each transition produces only 0 or 1 successors,
    // we can just iterate through the edges.
    AutomatonState currentState = (AutomatonState) pElement;
    Collection<AutomatonState> currentSuccessors = null;
    int edgeIndex;
    for (edgeIndex = 0; edgeIndex < edges.size(); edgeIndex++) {
      CFAEdge edge = edges.get(edgeIndex);
      currentSuccessors = getAbstractSuccessors0(currentState, pPrecision, edge);
      if (currentSuccessors.isEmpty()) {
        automatonSuccessors.setNextValue(0);
        return currentSuccessors; // bottom
      } else if (currentSuccessors.size() == 1) {
        automatonSuccessors.setNextValue(1);
        currentState = Iterables.getOnlyElement(currentSuccessors);
      } else { // currentSuccessors.size() > 1
        break;
      }
    }

    if (edgeIndex == edges.size()) {
      automatonSuccessors.setNextValue(currentSuccessors.size());
      return currentSuccessors;
    }

    // If there are two or more successors once, we use a waitlist algorithm.
    Deque<Pair<AutomatonState, Integer>> queue = new ArrayDeque<>(1);
    for (AutomatonState successor : currentSuccessors) {
      queue.addLast(Pair.of(successor, edgeIndex));
    }
    currentSuccessors.clear();

    List<AutomatonState> results = new ArrayList<>();
    while (!queue.isEmpty()) {
      Pair<AutomatonState, Integer> entry = queue.pollFirst();
      AutomatonState state = entry.getFirst();
      edgeIndex = entry.getSecond();
      CFAEdge edge = edges.get(edgeIndex);
      Integer successorIndex = edgeIndex + 1;

      if (successorIndex == edges.size()) {
        // last iteration
        results.addAll(getAbstractSuccessors0(state, pPrecision, edge));

      } else {
        for (AutomatonState successor : getAbstractSuccessors0(state, pPrecision, edge)) {
          queue.addLast(Pair.of(successor, successorIndex));
        }
      }
    }

    automatonSuccessors.setNextValue(results.size());
    return results;
  }
  private boolean combineARGs(
      List<ARGState> roots,
      ForwardingReachedSet pReceivedReachedSet,
      HistoryForwardingReachedSet pForwaredReachedSet)
      throws InterruptedException, CPAException {
    Pair<Map<String, Integer>, List<AbstractState>> initStates =
        identifyCompositeStateTypesAndTheirInitialInstances(roots);

    Map<String, Integer> stateToPos = initStates.getFirst();
    List<AbstractState> initialStates = initStates.getSecond();

    try {
      pReceivedReachedSet.setDelegate(new ReachedSetFactory(config, logger).create());
    } catch (InvalidConfigurationException e) {
      logger.log(Level.SEVERE, "Creating reached set which should contain combined ARG fails.");
      return false;
    }

    shutdown.shutdownIfNecessary();

    // combined root
    ARGState combinedRoot = new ARGState(new CompositeState(initialStates), null);

    CFANode locPred;
    ARGState composedState, composedSuccessor;
    Collection<ARGState> components;

    List<List<ARGState>> successorsForEdge = new ArrayList<>(initialStates.size());
    EdgeSuccessor edgeSuccessorIdentifier = new EdgeSuccessor();

    Map<Pair<List<AbstractState>, List<ARGState>>, ARGState> constructedCombinedStates =
        Maps.newHashMap();
    Deque<Pair<List<ARGState>, ARGState>> toVisit = new ArrayDeque<>();
    toVisit.add(Pair.of(roots, combinedRoot));

    // traverse through ARGs and construct combined ARG
    // assume that states in initial states are most general, represent top state (except for
    // automaton CPAs)
    while (!toVisit.isEmpty()) {
      shutdown.shutdownIfNecessary();

      components = toVisit.peek().getFirst();
      composedState = toVisit.poll().getSecond();

      // add composed state to reached set
      pReceivedReachedSet.add(composedState, SingletonPrecision.getInstance());
      pReceivedReachedSet.removeOnlyFromWaitlist(composedState);

      // identify possible successor edges
      locPred = AbstractStates.extractLocation(composedState);
      nextEdge:
      for (CFAEdge succEdge : CFAUtils.allLeavingEdges(locPred)) {
        shutdown.shutdownIfNecessary();

        successorsForEdge.clear();
        edgeSuccessorIdentifier.setCFAEdge(succEdge);

        for (ARGState component : components) {
          // get the successors of ARG state for this edge succEdge
          edgeSuccessorIdentifier.setPredecessor(component);
          successorsForEdge.add(
              Lists.newArrayList(
                  Iterables.filter(component.getChildren(), edgeSuccessorIdentifier)));
          // check if stopped because no concrete successors exists, then do not
          if (successorsForEdge.get(successorsForEdge.size() - 1).isEmpty()
              && noConcreteSuccessorExist(component, succEdge, pForwaredReachedSet)) {
            continue nextEdge;
          }
        }

        // construct successors for each identified combination
        for (Pair<List<AbstractState>, List<ARGState>> combinedSuccessor :
            computeCartesianProduct(successorsForEdge, stateToPos, initialStates)) {
          if (constructedCombinedStates.containsKey(combinedSuccessor)) {
            // handle coverage
            constructedCombinedStates.get(combinedSuccessor).addParent(composedState);
          } else {
            // construct and register composed successor
            composedSuccessor =
                new ARGState(new CompositeState(combinedSuccessor.getFirst()), composedState);
            constructedCombinedStates.put(combinedSuccessor, composedSuccessor);

            // add successor for further exploration
            toVisit.add(Pair.of(combinedSuccessor.getSecond(), composedSuccessor));
          }
        }
      }
    }
    return true;
  }
  /**
   * Returns the <code>AutomatonStates</code> that follow this State in the ControlAutomatonCPA. If
   * the passed <code>AutomatonExpressionArguments</code> are not sufficient to determine the
   * following state this method returns a <code>AutomatonUnknownState</code> that contains this as
   * previous State. The strengthen method of the <code>AutomatonUnknownState</code> should be used
   * once enough Information is available to determine the correct following State.
   *
   * <p>If the state is a NonDet-State multiple following states may be returned. If the only
   * following state is BOTTOM an empty set is returned.
   *
   * @throws CPATransferException
   */
  private Collection<AutomatonState> getFollowStates(
      AutomatonState state,
      List<AbstractState> otherElements,
      CFAEdge edge,
      boolean failOnUnknownMatch)
      throws CPATransferException {
    Preconditions.checkArgument(!(state instanceof AutomatonUnknownState));
    if (state == cpa.getBottomState()) {
      return Collections.emptySet();
    }

    if (collectTokenInformation) {
      SourceLocationMapper.getKnownToEdge(edge);
    }

    if (state.getInternalState().getTransitions().isEmpty()) {
      // shortcut
      return Collections.singleton(state);
    }

    Collection<AutomatonState> lSuccessors = Sets.newHashSetWithExpectedSize(2);
    AutomatonExpressionArguments exprArgs =
        new AutomatonExpressionArguments(state, state.getVars(), otherElements, edge, logger);
    boolean edgeMatched = false;
    int failedMatches = 0;
    boolean nonDetState = state.getInternalState().isNonDetState();

    // these transitions cannot be evaluated until last, because they might have sideeffects on
    // other CPAs (dont want to execute them twice)
    // the transitionVariables have to be cached (produced during the match operation)
    // the list holds a Transition and the TransitionVariables generated during its match
    List<Pair<AutomatonTransition, Map<Integer, String>>> transitionsToBeTaken = new ArrayList<>(2);

    for (AutomatonTransition t : state.getInternalState().getTransitions()) {
      exprArgs.clearTransitionVariables();

      matchTime.start();
      ResultValue<Boolean> match = t.match(exprArgs);
      matchTime.stop();

      //      System.out.println("----------------------");
      //      System.out.println(t.getTrigger());
      //      System.out.println(t.getFollowState().getName());
      //      System.out.println(edge.getPredecessor().getNodeNumber());
      //      System.out.println(edge.getCode());
      //      System.out.println(match.getValue());

      if (match.canNotEvaluate()) {
        if (failOnUnknownMatch) {
          throw new CPATransferException(
              "Automaton transition condition could not be evaluated: "
                  + match.getFailureMessage());
        }
        // if one transition cannot be evaluated the evaluation must be postponed until enough
        // information is available
        return Collections.<AutomatonState>singleton(new AutomatonUnknownState(state));
      } else {
        if (match.getValue()) {
          edgeMatched = true;
          assertionsTime.start();
          ResultValue<Boolean> assertionsHold = t.assertionsHold(exprArgs);
          assertionsTime.stop();

          if (assertionsHold.canNotEvaluate()) {
            if (failOnUnknownMatch) {
              throw new CPATransferException(
                  "Automaton transition assertions could not be evaluated: "
                      + assertionsHold.getFailureMessage());
            }
            // cannot yet be evaluated
            return Collections.<AutomatonState>singleton(new AutomatonUnknownState(state));

          } else if (assertionsHold.getValue()) {
            if (!t.canExecuteActionsOn(exprArgs)) {
              if (failOnUnknownMatch) {
                throw new CPATransferException("Automaton transition action could not be executed");
              }
              // cannot yet execute, goto UnknownState
              return Collections.<AutomatonState>singleton(new AutomatonUnknownState(state));
            }

            // delay execution as described above
            Map<Integer, String> transitionVariables =
                ImmutableMap.copyOf(exprArgs.getTransitionVariables());
            transitionsToBeTaken.add(Pair.of(t, transitionVariables));

          } else {
            // matching transitions, but unfulfilled assertions: goto error state
            AutomatonState errorState =
                AutomatonState.automatonStateFactory(
                    Collections.<String, AutomatonVariable>emptyMap(),
                    AutomatonInternalState.ERROR,
                    cpa,
                    0,
                    0,
                    "");
            logger.log(
                Level.INFO,
                "Automaton going to ErrorState on edge \"" + edge.getDescription() + "\"");
            lSuccessors.add(errorState);
          }

          if (!nonDetState) {
            // not a nondet State, break on the first matching edge
            break;
          }
        } else {
          // do nothing if the edge did not match
          failedMatches++;
        }
      }
    }

    if (edgeMatched) {
      // execute Transitions
      for (Pair<AutomatonTransition, Map<Integer, String>> pair : transitionsToBeTaken) {
        // this transition will be taken. copy the variables
        AutomatonTransition t = pair.getFirst();
        Map<Integer, String> transitionVariables = pair.getSecond();
        actionTime.start();
        Map<String, AutomatonVariable> newVars = deepCloneVars(state.getVars());
        exprArgs.setAutomatonVariables(newVars);
        exprArgs.putTransitionVariables(transitionVariables);
        t.executeActions(exprArgs);
        actionTime.stop();
        String violatedPropertyDescription = null;
        if (t.getFollowState().isTarget()) {
          violatedPropertyDescription = t.getViolatedPropertyDescription(exprArgs);
        }
        AutomatonState lSuccessor =
            AutomatonState.automatonStateFactory(
                newVars,
                t.getFollowState(),
                cpa,
                t.getAssumptions(),
                state.getMatches() + 1,
                state.getFailedMatches(),
                violatedPropertyDescription);
        if (!(lSuccessor instanceof AutomatonState.BOTTOM)) {
          lSuccessors.add(lSuccessor);
        } else {
          // add nothing
        }
      }
      return lSuccessors;
    } else {
      // stay in same state, no transitions to be executed here (no transition matched)
      AutomatonState stateNewCounters =
          AutomatonState.automatonStateFactory(
              state.getVars(),
              state.getInternalState(),
              cpa,
              state.getMatches(),
              state.getFailedMatches() + failedMatches,
              null);
      if (collectTokenInformation) {
        stateNewCounters.addNoMatchTokens(state.getTokensSinceLastMatch());
        if (edge.getEdgeType() != CFAEdgeType.DeclarationEdge) {
          stateNewCounters.addNoMatchTokens(
              SourceLocationMapper.getAbsoluteTokensFromCFAEdge(edge, true));
        }
      }
      return Collections.singleton(stateNewCounters);
    }
  }