@Override
  public <S> List<S> filterUniqueRoots(
      final List<S> in, final Convertor<S, VirtualFile> convertor) {
    Collections.sort(
        in, new ComparatorDelegate<S, VirtualFile>(convertor, FilePathComparator.getInstance()));

    for (int i = 1; i < in.size(); i++) {
      final S sChild = in.get(i);
      final VirtualFile child = convertor.convert(sChild);
      final VirtualFile childRoot = HgUtil.getHgRootOrNull(myProject, child);
      if (childRoot == null) {
        continue;
      }
      for (int j = i - 1; j >= 0; --j) {
        final S sParent = in.get(j);
        final VirtualFile parent = convertor.convert(sParent);
        // if the parent is an ancestor of the child and that they share common root, the child is
        // removed
        if (VfsUtil.isAncestor(parent, child, false)
            && VfsUtil.isAncestor(childRoot, parent, false)) {
          in.remove(i);
          //noinspection AssignmentToForLoopParameter
          --i;
          break;
        }
      }
    }
    return in;
  }
  @Override
  public <S> List<S> filterUniqueRoots(
      final List<S> in, final Convertor<S, VirtualFile> convertor) {
    Collections.sort(
        in, new ComparatorDelegate<S, VirtualFile>(convertor, FilePathComparator.getInstance()));

    for (int i = 1; i < in.size(); i++) {
      final S sChild = in.get(i);
      final VirtualFile child = convertor.convert(sChild);
      final VirtualFile childRoot = GitUtil.gitRootOrNull(child);
      if (childRoot == null) {
        // non-git file actually, skip it
        continue;
      }
      for (int j = i - 1; j >= 0; --j) {
        final S sParent = in.get(j);
        final VirtualFile parent = convertor.convert(sParent);
        // the method check both that parent is an ancestor of the child and that they share common
        // git root
        if (VfsUtilCore.isAncestor(parent, child, false)
            && VfsUtilCore.isAncestor(childRoot, parent, false)) {
          in.remove(i);
          //noinspection AssignmentToForLoopParameter
          --i;
          break;
        }
      }
    }
    return in;
  }
 @Override
 protected String getElementText(Object element) {
   TreePath path = (TreePath) element;
   String string = myToStringConvertor.convert(path);
   if (string == null) return TO_STRING.convert(path);
   return string;
 }
  private <T> boolean findListToRemoveFrom(
      @NotNull String name,
      @NotNull final List<T> elements,
      final Convertor<T, AbstractUrl> convertor) {
    Collection<TreeItem<Pair<AbstractUrl, String>>> list = getFavoritesListRootUrls(name);
    if (elements.size() > 1) {
      final List<T> sublist = elements.subList(0, elements.size() - 1);
      for (T obj : sublist) {
        AbstractUrl objUrl = convertor.convert(obj);
        final TreeItem<Pair<AbstractUrl, String>> item = findNextItem(objUrl, list);
        if (item == null || item.getChildren() == null) return false;
        list = item.getChildren();
      }
    }

    TreeItem<Pair<AbstractUrl, String>> found = null;
    AbstractUrl url = convertor.convert(elements.get(elements.size() - 1));
    if (url == null) return false;
    for (TreeItem<Pair<AbstractUrl, String>> pair : list) {
      if (url.equals(pair.getData().getFirst())) {
        found = pair;
        break;
      }
    }

    if (found != null) {
      list.remove(found);
      fireListeners.rootsChanged(name);
      return true;
    }
    return false;
  }
  public int search(final List<S> list, final T item) {
    if (list.isEmpty()) return 0;
    myFake = list.get(0);
    myFakeConverted = myTZConvertor.convert(item);
    if (myZComparator.compare(mySZConvertor.convert(myFake), myTZConvertor.convert(item)) >= 0) {
      return 0;
    }

    final int idx = Collections.binarySearch(list.subList(1, list.size()), myFake, myComparator);
    if (idx >= 0) {
      return 1 + idx;
    } else {
      return -idx;
    }
  }
 @Override
 public String convert(TreePath path) {
   final DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
   final Object userObject = node.getUserObject();
   if (userObject instanceof NodeDescriptor) {
     NodeDescriptor descr = (NodeDescriptor) userObject;
     return descr.toString();
   }
   return TO_STRING.convert(path);
 }
 public static <T, U, S extends U> List<S> convert(
     @NotNull final Collection<T> in,
     final Convertor<T, S> convertor,
     @Nullable final NotNullFunction<U, Boolean> outFilter) {
   final List<S> out = new ArrayList<S>();
   for (T t : in) {
     final S converted = convertor.convert(t);
     if ((outFilter != null) && (!Boolean.TRUE.equals(outFilter.fun(converted)))) continue;
     out.add(converted);
   }
   return out;
 }
  public static void processFilesRecursively(
      @NotNull VirtualFile root,
      @NotNull Processor<VirtualFile> processor,
      @NotNull Convertor<VirtualFile, Boolean> directoryFilter) {
    if (!processor.process(root)) return;

    if (root.isDirectory() && directoryFilter.convert(root)) {
      final LinkedList<VirtualFile[]> queue = new LinkedList<VirtualFile[]>();

      queue.add(root.getChildren());

      do {
        final VirtualFile[] files = queue.removeFirst();

        for (VirtualFile file : files) {
          if (!processor.process(file)) return;
          if (file.isDirectory() && directoryFilter.convert(file)) {
            queue.add(file.getChildren());
          }
        }
      } while (!queue.isEmpty());
    }
  }
  @Override
  public <S> List<S> filterUniqueRoots(
      final List<S> in, final Convertor<S, VirtualFile> convertor) {
    if (in.size() <= 1) return in;

    final List<MyPair<S>> infos = new ArrayList<MyPair<S>>(in.size());
    final SvnFileUrlMappingImpl mapping = (SvnFileUrlMappingImpl) getSvnFileUrlMapping();
    final List<S> notMatched = new LinkedList<S>();
    for (S s : in) {
      final VirtualFile vf = convertor.convert(s);
      if (vf == null) continue;

      final File ioFile = new File(vf.getPath());
      SVNURL url = mapping.getUrlForFile(ioFile);
      if (url == null) {
        url = SvnUtil.getUrl(this, ioFile);
        if (url == null) {
          notMatched.add(s);
          continue;
        }
      }
      infos.add(new MyPair<S>(vf, url.toString(), s));
    }
    final List<MyPair<S>> filtered = new ArrayList<MyPair<S>>(infos.size());
    ForNestedRootChecker.filterOutSuperfluousChildren(this, infos, filtered);

    final List<S> converted =
        ObjectsConvertor.convert(
            filtered,
            new Convertor<MyPair<S>, S>() {
              @Override
              public S convert(final MyPair<S> o) {
                return o.getSrc();
              }
            });
    if (!notMatched.isEmpty()) {
      // potential bug is here: order is not kept. but seems it only occurs for cases where result
      // is sorted after filtering so ok
      converted.addAll(notMatched);
    }
    return converted;
  }
 /** @param removeProcessor parent, child */
 public static <T> Collection<T> removeAncestors(
     final Collection<T> files,
     final Convertor<T, String> convertor,
     final PairProcessor<T, T> removeProcessor) {
   if (files.isEmpty()) return files;
   final TreeMap<String, T> paths = new TreeMap<String, T>();
   for (T file : files) {
     final String path = convertor.convert(file);
     assert path != null;
     final String canonicalPath = toCanonicalPath(path);
     paths.put(canonicalPath, file);
   }
   final List<Map.Entry<String, T>> ordered =
       new ArrayList<Map.Entry<String, T>>(paths.entrySet());
   final List<T> result = new ArrayList<T>(ordered.size());
   result.add(ordered.get(0).getValue());
   for (int i = 1; i < ordered.size(); i++) {
     final Map.Entry<String, T> entry = ordered.get(i);
     final String child = entry.getKey();
     boolean parentNotFound = true;
     for (int j = i - 1; j >= 0; j--) {
       // possible parents
       final String parent = ordered.get(j).getKey();
       if (parent == null) continue;
       if (startsWith(child, parent)
           && removeProcessor.process(ordered.get(j).getValue(), entry.getValue())) {
         parentNotFound = false;
         break;
       }
     }
     if (parentNotFound) {
       result.add(entry.getValue());
     }
   }
   return result;
 }