@Nullable
      static JoiningTerminal extractIndexBasedTerminal(@NotNull TerminalBlock terminalBlock,
                                                       @Nullable List<PsiVariable> nonFinalVariables) {
        if (nonFinalVariables != null && !nonFinalVariables.isEmpty()) return null;
        StreamApiMigrationInspection.CountingLoopSource countingLoopSource =
          terminalBlock.getLastOperation(StreamApiMigrationInspection.CountingLoopSource.class);
        if (countingLoopSource == null) return null;
        SpecialFirstIterationLoop specialFirstIterationLoop =
          SpecialFirstIterationLoop.IndexBasedLoop.extract(terminalBlock, countingLoopSource);
        if (specialFirstIterationLoop == null) return null;
        List<? extends PsiStatement> firstIterationStatements = specialFirstIterationLoop.getFirstIterationStatements();
        List<? extends PsiStatement> otherIterationStatements = specialFirstIterationLoop.getOtherIterationStatements();
        if (firstIterationStatements.isEmpty() || otherIterationStatements.isEmpty()) return null;

        int additionalPrefix = 0;
        while (additionalPrefix < firstIterationStatements.size()) {
          PsiStatement statement = firstIterationStatements.get(additionalPrefix);
          if (additionalPrefix >= otherIterationStatements.size() || statement != otherIterationStatements.get(additionalPrefix)) {
            break;
          }
          if (statement instanceof PsiExpressionStatement &&
              JoiningTerminal.tryExtractJoinPart(((PsiExpressionStatement)statement).getExpression(), new ArrayList<>())) {
            break;
          }
          additionalPrefix++;
        }
        if (additionalPrefix > 0) {
          TerminalBlock newBlock =
            TerminalBlock.fromStatements(countingLoopSource, firstIterationStatements.toArray(PsiStatement.EMPTY_ARRAY));
          int leftOver = firstIterationStatements.size() - additionalPrefix;
          while (newBlock != null && newBlock.getStatements().length < leftOver) {
            newBlock = newBlock.withoutLastOperation();
          }
          if (newBlock != null && newBlock.getStatements().length == leftOver) {
            terminalBlock = newBlock;
            firstIterationStatements = firstIterationStatements.subList(additionalPrefix, firstIterationStatements.size());
            otherIterationStatements = otherIterationStatements.subList(additionalPrefix, otherIterationStatements.size());
          }
        }

        List<PsiExpression> firstIterationJoinParts = extractJoinParts(firstIterationStatements);
        List<PsiExpression> otherIterationJoinParts = extractJoinParts(otherIterationStatements);

        if (firstIterationJoinParts == null || otherIterationJoinParts == null) return null;
        JoinData joinData = JoinData.extractLeftDelimiter(otherIterationJoinParts);
        if (!joinPartsAreEquivalent(joinData.getMainJoinParts(), firstIterationJoinParts)) return null;

        if (firstIterationStatements.isEmpty()) return null;
        PsiVariable targetBuilder = extractStringBuilder(firstIterationStatements.get(0));
        if (targetBuilder == null) return null;
        PsiStatement loop = terminalBlock.getStreamSourceStatement();
        PrefixSuffixContext context =
          PrefixSuffixContext.extractAndVerifyRefs(loop, loop, targetBuilder, terminalBlock, emptyList(), new HashSet<>(emptyList()));
        if (context == null) return null;
        return new IndexBasedJoiningTerminal(targetBuilder, terminalBlock.getVariable(), firstIterationJoinParts, context,
                                             joinData.getDelimiterJoinParts(), terminalBlock);
      }
@Nullable
      static JoiningTerminal extractIndexBasedTerminal(@NotNull TerminalBlock terminalBlock,
                                                       @Nullable List<PsiVariable> nonFinalVariables)