@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;
  }
  /**
   * 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);
    }
  }