private void shiftOffsets(int shift, int changeOffset) {
   for (int i = myWrappers.size() - 1; i >= 0; i--) {
     ArrangementEntryWrapper<E> wrapper = myWrappers.get(i);
     if (wrapper.getStartOffset() < changeOffset) {
       break;
     }
     wrapper.applyShift(shift);
   }
 }
 @Override
 public void prepare(
     @NotNull List<ArrangementEntryWrapper<E>> toArrange, @NotNull Context<E> context) {
   myWrappers.clear();
   myWrappers.addAll(toArrange);
   for (ArrangementEntryWrapper<E> wrapper : toArrange) {
     wrapper.updateBlankLines(myDocument);
   }
 }
 @Override
 public void prepare(
     @NotNull List<ArrangementEntryWrapper<E>> toArrange, @NotNull Context<E> context) {
   ArrangementEntryWrapper<E> parent = toArrange.get(0).getParent();
   if (parent == null) {
     myParentText = context.document.getText();
     myParentShift = 0;
   } else {
     myParentText =
         context
             .document
             .getCharsSequence()
             .subSequence(parent.getStartOffset(), parent.getEndOffset())
             .toString();
     myParentShift = parent.getStartOffset();
   }
 }
  @SuppressWarnings("unchecked")
  private static <E extends ArrangementEntry> void doArrange(
      @NotNull List<ArrangementEntryWrapper<E>> wrappers, @NotNull Context<E> context) {
    if (wrappers.isEmpty()) {
      return;
    }

    Map<E, ArrangementEntryWrapper<E>> map = ContainerUtilRt.newHashMap();
    List<E> arranged = ContainerUtilRt.newArrayList();
    List<E> toArrange = ContainerUtilRt.newArrayList();
    for (ArrangementEntryWrapper<E> wrapper : wrappers) {
      E entry = wrapper.getEntry();
      map.put(wrapper.getEntry(), wrapper);
      if (!entry.canBeMatched()) {
        // Split entries to arrange by 'can not be matched' rules.
        // See IDEA-104046 for a problem use-case example.
        if (toArrange.isEmpty()) {
          arranged.addAll(arrange(toArrange, context.rules, context.rulesByPriority));
        }
        arranged.add(entry);
        toArrange.clear();
      } else {
        toArrange.add(entry);
      }
    }
    if (!toArrange.isEmpty()) {
      arranged.addAll(arrange(toArrange, context.rules, context.rulesByPriority));
    }

    context.changer.prepare(wrappers, context);
    // We apply changes from the last position to the first position in order not to bother with
    // offsets shifts.
    for (int i = arranged.size() - 1; i >= 0; i--) {
      ArrangementEntryWrapper<E> arrangedWrapper = map.get(arranged.get(i));
      ArrangementEntryWrapper<E> initialWrapper = wrappers.get(i);
      context.changer.replace(
          arrangedWrapper, initialWrapper, i > 0 ? map.get(arranged.get(i - 1)) : null, context);
    }
  }
 public static <T extends ArrangementEntry> Context<T> from(
     @NotNull Rearranger<T> rearranger,
     @NotNull Document document,
     @NotNull PsiElement root,
     @NotNull Collection<TextRange> ranges,
     @NotNull ArrangementSettings arrangementSettings,
     @NotNull CodeStyleSettings codeStyleSettings) {
   Collection<T> entries = rearranger.parse(root, document, ranges, arrangementSettings);
   Collection<ArrangementEntryWrapper<T>> wrappers = new ArrayList<ArrangementEntryWrapper<T>>();
   ArrangementEntryWrapper<T> previous = null;
   for (T entry : entries) {
     ArrangementEntryWrapper<T> wrapper = new ArrangementEntryWrapper<T>(entry);
     if (previous != null) {
       previous.setNext(wrapper);
       wrapper.setPrevious(previous);
     }
     wrappers.add(wrapper);
     previous = wrapper;
   }
   Changer changer;
   if (document instanceof DocumentEx) {
     changer = new RangeMarkerAwareChanger<T>((DocumentEx) document);
   } else {
     changer = new DefaultChanger();
   }
   final List<? extends ArrangementMatchRule> rulesByPriority =
       ArrangementUtil.getRulesSortedByPriority(arrangementSettings);
   return new Context<T>(
       rearranger,
       wrappers,
       document,
       arrangementSettings.getRules(),
       rulesByPriority,
       codeStyleSettings,
       changer);
 }
    @SuppressWarnings("AssignmentToForLoopParameter")
    @Override
    public void replace(
        @NotNull ArrangementEntryWrapper<E> newWrapper,
        @NotNull ArrangementEntryWrapper<E> oldWrapper,
        @Nullable ArrangementEntryWrapper<E> previous,
        @NotNull Context<E> context) {
      // Calculate blank lines before the arrangement.
      int blankLinesBefore = oldWrapper.getBlankLinesBefore();

      ArrangementEntryWrapper<E> parentWrapper = oldWrapper.getParent();
      int desiredBlankLinesNumber =
          context.rearranger.getBlankLines(
              context.settings,
              parentWrapper == null ? null : parentWrapper.getEntry(),
              previous == null ? null : previous.getEntry(),
              newWrapper.getEntry());
      if ((desiredBlankLinesNumber < 0 || desiredBlankLinesNumber == blankLinesBefore)
          && newWrapper.equals(oldWrapper)) {
        return;
      }

      int lineFeedsDiff = desiredBlankLinesNumber - blankLinesBefore;
      int insertionOffset = oldWrapper.getStartOffset();
      if (oldWrapper.getStartOffset() > newWrapper.getStartOffset()) {
        insertionOffset -= newWrapper.getEndOffset() - newWrapper.getStartOffset();
      }
      if (newWrapper.getStartOffset() != oldWrapper.getStartOffset()
          || !newWrapper.equals(oldWrapper)) {
        context.addMoveInfo(
            newWrapper.getStartOffset(), newWrapper.getEndOffset(), oldWrapper.getStartOffset());
        myDocument.moveText(
            newWrapper.getStartOffset(), newWrapper.getEndOffset(), oldWrapper.getStartOffset());
        for (int i = myWrappers.size() - 1; i >= 0; i--) {
          ArrangementEntryWrapper<E> w = myWrappers.get(i);
          if (w == newWrapper) {
            continue;
          }
          if (w.getStartOffset() >= oldWrapper.getStartOffset()
              && w.getStartOffset() < newWrapper.getStartOffset()) {
            w.applyShift(newWrapper.getEndOffset() - newWrapper.getStartOffset());
          } else if (w.getStartOffset() < oldWrapper.getStartOffset()
              && w.getStartOffset() > newWrapper.getStartOffset()) {
            w.applyShift(newWrapper.getStartOffset() - newWrapper.getEndOffset());
          }
        }
      }

      if (desiredBlankLinesNumber >= 0 && lineFeedsDiff > 0) {
        myDocument.insertString(insertionOffset, StringUtil.repeat("\n", lineFeedsDiff));
        shiftOffsets(lineFeedsDiff, insertionOffset);
      }

      if (desiredBlankLinesNumber >= 0 && lineFeedsDiff < 0) {
        // Cut exceeding blank lines.
        int replacementStartOffset = getBlankLineOffset(-lineFeedsDiff, insertionOffset);
        myDocument.deleteString(replacementStartOffset, insertionOffset);
        shiftOffsets(replacementStartOffset - insertionOffset, insertionOffset);
      }

      // Update wrapper ranges.
      if (desiredBlankLinesNumber < 0 || lineFeedsDiff == 0 || parentWrapper == null) {
        return;
      }

      Deque<ArrangementEntryWrapper<E>> parents = new ArrayDeque<ArrangementEntryWrapper<E>>();
      do {
        parents.add(parentWrapper);
        parentWrapper.setEndOffset(parentWrapper.getEndOffset() + lineFeedsDiff);
        parentWrapper = parentWrapper.getParent();
      } while (parentWrapper != null);

      while (!parents.isEmpty()) {
        for (ArrangementEntryWrapper<E> wrapper = parents.removeLast().getNext();
            wrapper != null;
            wrapper = wrapper.getNext()) {
          wrapper.applyShift(lineFeedsDiff);
        }
      }
    }
    @SuppressWarnings("AssignmentToForLoopParameter")
    @Override
    public void replace(
        @NotNull ArrangementEntryWrapper<E> newWrapper,
        @NotNull ArrangementEntryWrapper<E> oldWrapper,
        @Nullable ArrangementEntryWrapper<E> previous,
        @NotNull Context<E> context) {
      // Calculate blank lines before the arrangement.
      int blankLinesBefore = 0;
      TIntArrayList lineFeedOffsets = new TIntArrayList();
      int oldStartLine = context.document.getLineNumber(oldWrapper.getStartOffset());
      if (oldStartLine > 0) {
        int lastLineFeed = context.document.getLineStartOffset(oldStartLine) - 1;
        lineFeedOffsets.add(lastLineFeed);
        for (int i = lastLineFeed - 1 - myParentShift; i >= 0; i--) {
          i = CharArrayUtil.shiftBackward(myParentText, i, " \t");
          if (myParentText.charAt(i) == '\n') {
            blankLinesBefore++;
            lineFeedOffsets.add(i + myParentShift);
          } else {
            break;
          }
        }
      }

      ArrangementEntryWrapper<E> parentWrapper = oldWrapper.getParent();
      int desiredBlankLinesNumber =
          context.rearranger.getBlankLines(
              context.settings,
              parentWrapper == null ? null : parentWrapper.getEntry(),
              previous == null ? null : previous.getEntry(),
              newWrapper.getEntry());
      if (desiredBlankLinesNumber == blankLinesBefore && newWrapper.equals(oldWrapper)) {
        return;
      }

      String newEntryText =
          myParentText.substring(
              newWrapper.getStartOffset() - myParentShift,
              newWrapper.getEndOffset() - myParentShift);
      int lineFeedsDiff = desiredBlankLinesNumber - blankLinesBefore;
      if (lineFeedsDiff == 0 || desiredBlankLinesNumber < 0) {
        context.addMoveInfo(
            newWrapper.getStartOffset() - myParentShift,
            newWrapper.getEndOffset() - myParentShift,
            oldWrapper.getStartOffset());
        context.document.replaceString(
            oldWrapper.getStartOffset(), oldWrapper.getEndOffset(), newEntryText);
        return;
      }

      if (lineFeedsDiff > 0) {
        // Insert necessary number of blank lines.
        StringBuilder buffer = new StringBuilder(StringUtil.repeat("\n", lineFeedsDiff));
        buffer.append(newEntryText);
        context.document.replaceString(
            oldWrapper.getStartOffset(), oldWrapper.getEndOffset(), buffer);
      } else {
        // Cut exceeding blank lines.
        int replacementStartOffset = lineFeedOffsets.get(-lineFeedsDiff) + 1;
        context.document.replaceString(
            replacementStartOffset, oldWrapper.getEndOffset(), newEntryText);
      }

      // Update wrapper ranges.
      ArrangementEntryWrapper<E> parent = oldWrapper.getParent();
      if (parent == null) {
        return;
      }

      Deque<ArrangementEntryWrapper<E>> parents = new ArrayDeque<ArrangementEntryWrapper<E>>();
      do {
        parents.add(parent);
        parent.setEndOffset(parent.getEndOffset() + lineFeedsDiff);
        parent = parent.getParent();
      } while (parent != null);

      while (!parents.isEmpty()) {

        for (ArrangementEntryWrapper<E> wrapper = parents.removeLast().getNext();
            wrapper != null;
            wrapper = wrapper.getNext()) {
          wrapper.applyShift(lineFeedsDiff);
        }
      }
    }
  @SuppressWarnings("unchecked")
  private static <E extends ArrangementEntry> void doArrange(Context<E> context) {
    // The general idea is to process entries bottom-up where every processed group belongs to the
    // same parent. We may not bother
    // with entries text ranges then. We use a list and a stack for achieving that than.
    //
    // Example:
    //            Entry1              Entry2
    //            /    \              /    \
    //      Entry11   Entry12    Entry21  Entry22
    //
    //    --------------------------
    //    Stage 1:
    //      list: Entry1 Entry2    <-- entries to process
    //      stack: [0, 0, 2]       <-- holds current iteration info at the following format:
    //                                 (start entry index at the auxiliary list (inclusive); current
    // index; end index (exclusive))
    //    --------------------------
    //    Stage 2:
    //      list: Entry1 Entry2 Entry11 Entry12
    //      stack: [0, 1, 2]
    //             [2, 2, 4]
    //    --------------------------
    //    Stage 3:
    //      list: Entry1 Entry2 Entry11 Entry12
    //      stack: [0, 1, 2]
    //             [2, 3, 4]
    //    --------------------------
    //    Stage 4:
    //      list: Entry1 Entry2 Entry11 Entry12
    //      stack: [0, 1, 2]
    //             [2, 4, 4]
    //    --------------------------
    //      arrange 'Entry11 Entry12'
    //    --------------------------
    //    Stage 5:
    //      list: Entry1 Entry2
    //      stack: [0, 1, 2]
    //    --------------------------
    //    Stage 6:
    //      list: Entry1 Entry2 Entry21 Entry22
    //      stack: [0, 2, 2]
    //             [2, 2, 4]
    //    --------------------------
    //    Stage 7:
    //      list: Entry1 Entry2 Entry21 Entry22
    //      stack: [0, 2, 2]
    //             [2, 3, 4]
    //    --------------------------
    //    Stage 8:
    //      list: Entry1 Entry2 Entry21 Entry22
    //      stack: [0, 2, 2]
    //             [2, 4, 4]
    //    --------------------------
    //      arrange 'Entry21 Entry22'
    //    --------------------------
    //    Stage 9:
    //      list: Entry1 Entry2
    //      stack: [0, 2, 2]
    //    --------------------------
    //      arrange 'Entry1 Entry2'

    List<ArrangementEntryWrapper<E>> entries = new ArrayList<ArrangementEntryWrapper<E>>();
    Stack<StackEntry> stack = new Stack<StackEntry>();
    entries.addAll(context.wrappers);
    stack.push(new StackEntry(0, context.wrappers.size()));
    while (!stack.isEmpty()) {
      StackEntry stackEntry = stack.peek();
      if (stackEntry.current >= stackEntry.end) {
        List<ArrangementEntryWrapper<E>> subEntries =
            entries.subList(stackEntry.start, stackEntry.end);
        if (subEntries.size() > 1) {
          doArrange(subEntries, context);
        }
        subEntries.clear();
        stack.pop();
      } else {
        ArrangementEntryWrapper<E> wrapper = entries.get(stackEntry.current++);
        List<ArrangementEntryWrapper<E>> children = wrapper.getChildren();
        if (!children.isEmpty()) {
          entries.addAll(children);
          stack.push(new StackEntry(stackEntry.end, children.size()));
        }
      }
    }
  }