@Override
  public void collectDifferencesWith(@NotNull Entry right, @NotNull BiConsumer<Entry, Entry> consumer) {
    DirectoryEntry e = (DirectoryEntry)right;

    if (!getPath().equals(e.getPath())) {
      consumer.accept(this, e);
    }

    // most often we have the same children, so try processing it directly
    int commonIndex = 0;
    final int myChildrenSize = myChildren.size();
    final int rightChildrenSize = e.myChildren.size();
    final int minChildrenSize = Math.min(myChildrenSize, rightChildrenSize);

    while (commonIndex < minChildrenSize) {
      Entry childEntry = myChildren.get(commonIndex);
      Entry rightChildEntry = e.myChildren.get(commonIndex);

      if (childEntry.getNameId() == rightChildEntry.getNameId() && childEntry.isDirectory() == rightChildEntry.isDirectory()) {
        childEntry.collectDifferencesWith(rightChildEntry, consumer);
      }
      else {
        break;
      }
      ++commonIndex;
    }

    if (commonIndex == myChildrenSize && commonIndex == rightChildrenSize) return;

    Int2ObjectMap<Entry> uniqueNameIdToMyChildEntries = new Int2ObjectOpenHashMap<>(myChildrenSize - commonIndex);
    for (int i = commonIndex; i < myChildrenSize; ++i) {
      Entry childEntry = myChildren.get(i);
      uniqueNameIdToMyChildEntries.put(childEntry.getNameId(), childEntry);
    }

    Int2ObjectMap<Entry> uniqueNameIdToRightChildEntries = new Int2ObjectOpenHashMap<>(rightChildrenSize - commonIndex);
    Int2ObjectMap<Entry> myNameIdToRightChildEntries = new Int2ObjectOpenHashMap<>(rightChildrenSize - commonIndex);

    for (int i = commonIndex; i < rightChildrenSize; ++i) {
      Entry rightChildEntry = e.myChildren.get(i);
      int rightChildEntryNameId = rightChildEntry.getNameId();
      Entry myChildEntry = uniqueNameIdToMyChildEntries.get(rightChildEntryNameId);

      if (myChildEntry != null && myChildEntry.isDirectory() == rightChildEntry.isDirectory()) {
        uniqueNameIdToMyChildEntries.remove(rightChildEntryNameId);
        myNameIdToRightChildEntries.put(rightChildEntryNameId, rightChildEntry);
      }
      else {
        uniqueNameIdToRightChildEntries.put(rightChildEntryNameId, rightChildEntry);
      }
    }

    if (!Paths.isCaseSensitive() && !uniqueNameIdToMyChildEntries.isEmpty() && !uniqueNameIdToRightChildEntries.isEmpty()) {
      Map<String, Entry> nameToEntryMap = CollectionFactory.createCaseInsensitiveStringMap(uniqueNameIdToMyChildEntries.size());
      for (Entry entry : uniqueNameIdToMyChildEntries.values()) {
        nameToEntryMap.put(entry.getName(), entry);
      }

      for (ObjectIterator<Entry> rightChildEntryIterator = uniqueNameIdToRightChildEntries.values().iterator();
           rightChildEntryIterator.hasNext(); ) {
        Entry rightChildEntry = rightChildEntryIterator.next();
        Entry myChildEntry = nameToEntryMap.get(rightChildEntry.getName());
        if (myChildEntry != null && rightChildEntry.isDirectory() == myChildEntry.isDirectory()) {
          myNameIdToRightChildEntries.put(myChildEntry.getNameId(), rightChildEntry);
          uniqueNameIdToMyChildEntries.remove(myChildEntry.getNameId());
          rightChildEntryIterator.remove();
        }
      }
    }

    for (Entry child : e.myChildren) {
      if (uniqueNameIdToRightChildEntries.containsKey(child.getNameId())) {
        child.collectCreatedDifferences(consumer);
      }
    }

    for (Entry child : myChildren) {
      if (uniqueNameIdToMyChildEntries.containsKey(child.getNameId())) {
        child.collectDeletedDifferences(consumer);
      }
      else {
        Entry itsChild = myNameIdToRightChildEntries.get(child.getNameId());
        if (itsChild != null) child.collectDifferencesWith(itsChild, consumer);
      }
    }
  }
@Override
  public void collectDifferencesWith(@NotNull Entry right, @NotNull BiConsumer<Entry, Entry> consumer)