public static @NotNull <E extends ArrangementEntry> List<E> arrange(@NotNull Collection<? extends E> entries,
                                                             @NotNull List<ArrangementSectionRule> sectionRules,
                                                             @NotNull List<? extends ArrangementMatchRule> rulesByPriority,
                                                             @Nullable Map<E, ArrangementSectionRule> entryToSection)
  {
    List<E> arranged = new ArrayList<>();
    Set<E> unprocessed = new LinkedHashSet<>();
    List<Pair<Set<ArrangementEntry>, E>> dependent = new ArrayList<>();
    for (E entry : entries) {
      List<? extends ArrangementEntry> dependencies = entry.getDependencies();
      if (dependencies == null) {
        unprocessed.add(entry);
      }
      else {
        if (dependencies.size() == 1 && dependencies.get(0) == entry.getParent()) {
          // Handle a situation when the entry is configured to be at the first parent's children.
          arranged.add(entry);
        }
        else {
          Set<ArrangementEntry> first = new HashSet<>(dependencies);
          dependent.add(Pair.create(first, entry));
        }
      }
    }

    Set<E> matched = new HashSet<>();

    MultiMap<ArrangementMatchRule, E> elementsByRule = new MultiMap<>();
    for (ArrangementMatchRule rule : rulesByPriority) {
      matched.clear();
      for (E entry : unprocessed) {
        if (entry.canBeMatched() && rule.getMatcher().isMatched(entry)) {
          elementsByRule.putValue(rule, entry);
          matched.add(entry);
        }
      }
      unprocessed.removeAll(matched);
    }

    for (ArrangementSectionRule sectionRule : sectionRules) {
      for (ArrangementMatchRule rule : sectionRule.getMatchRules()) {
        final Collection<E> arrangedEntries = arrangeByRule(arranged, elementsByRule, rule);

        if (entryToSection != null && arrangedEntries != null) {
          for (E entry : arrangedEntries) {
            entryToSection.put(entry, sectionRule);
          }
        }
      }
    }
    arranged.addAll(unprocessed);

    for (int i = 0; i < arranged.size() && !dependent.isEmpty(); i++) {
      E e = arranged.get(i);
      List<E> shouldBeAddedAfterCurrentElement = new ArrayList<>();

      for (Iterator<Pair<Set<ArrangementEntry>, E>> iterator = dependent.iterator(); iterator.hasNext(); ) {
        Pair<Set<ArrangementEntry>, E> pair = iterator.next();
        pair.first.remove(e);
        if (pair.first.isEmpty()) {
          iterator.remove();
          shouldBeAddedAfterCurrentElement.add(pair.second);
        }
      }

      // add dependent entries to the same section as main entry
      if (entryToSection != null && entryToSection.containsKey(e)) {
        final ArrangementSectionRule rule = entryToSection.get(e);
        for (E e1 : shouldBeAddedAfterCurrentElement) {
          entryToSection.put(e1, rule);
        }
      }
      arranged.addAll(i + 1, shouldBeAddedAfterCurrentElement);
    }

    return arranged;
  }
public static @NotNull <E extends ArrangementEntry> List<E> arrange(@NotNull Collection<? extends E> entries,
                                                             @NotNull List<ArrangementSectionRule> sectionRules,
                                                             @NotNull List<? extends ArrangementMatchRule> rulesByPriority,
                                                             @Nullable Map<E, ArrangementSectionRule> entryToSection)