com.intellij.ide.favoritesTreeView.FavoritesManager.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.ide.favoritesTreeView.FavoritesManager.java

Source

/*
 * Copyright 2000-2011 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.intellij.ide.favoritesTreeView;

import com.intellij.ide.IdeBundle;
import com.intellij.ide.favoritesTreeView.actions.AddToFavoritesAction;
import com.intellij.ide.projectView.impl.*;
import com.intellij.ide.projectView.impl.nodes.LibraryGroupElement;
import com.intellij.ide.projectView.impl.nodes.NamedLibraryElement;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ProjectComponent;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.DumbAwareRunnable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.types.BinariesOrderRootType;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.ui.InputValidator;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.*;
import com.intellij.openapi.vcs.ObjectsConvertor;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtilBase;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Consumer;
import com.intellij.util.TreeItem;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Convertor;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

import static com.intellij.ide.favoritesTreeView.FavoritesListProvider.EP_NAME;

public class FavoritesManager implements ProjectComponent, JDOMExternalizable {
    private final ArrayList<String> myListOrder = new ArrayList<String>();
    // fav list name -> list of (root: root url, root class)
    private final Map<String, List<TreeItem<Pair<AbstractUrl, String>>>> myName2FavoritesRoots = new TreeMap<String, List<TreeItem<Pair<AbstractUrl, String>>>>(
            new Comparator<String>() {
                @Override
                public int compare(String o1, String o2) {
                    return myListOrder.indexOf(o1) - myListOrder.indexOf(o2);
                }
            });
    private final Map<String, String> myDescriptions = new HashMap<String, String>();
    private final Project myProject;
    private final List<FavoritesListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
    private final FavoritesViewSettings myViewSettings = new FavoritesViewSettings();
    private final Map<String, FavoritesListProvider> myProviders = new HashMap<String, FavoritesListProvider>();

    private void rootsChanged() {
        for (FavoritesListener listener : myListeners) {
            listener.rootsChanged();
        }
    }

    private void listAdded(String listName) {
        for (FavoritesListener listener : myListeners) {
            listener.listAdded(listName);
        }
    }

    private void listRemoved(String listName) {
        for (FavoritesListener listener : myListeners) {
            listener.listRemoved(listName);
        }
    }

    public void renameList(final Project project, @NotNull String listName) {
        final String newName = Messages.showInputDialog(project,
                IdeBundle.message("prompt.input.favorites.list.new.name", listName),
                IdeBundle.message("title.rename.favorites.list"), Messages.getInformationIcon(), listName,
                new InputValidator() {
                    @Override
                    public boolean checkInput(String inputString) {
                        return inputString != null && inputString.trim().length() > 0;
                    }

                    @Override
                    public boolean canClose(String inputString) {
                        inputString = inputString.trim();
                        if (myName2FavoritesRoots.keySet().contains(inputString)
                                || myProviders.keySet().contains(inputString)) {
                            Messages.showErrorDialog(project,
                                    IdeBundle.message("error.favorites.list.already.exists", inputString.trim()),
                                    IdeBundle.message("title.unable.to.add.favorites.list"));
                            return false;
                        }
                        return !inputString.isEmpty();
                    }
                });

        if (newName != null && renameFavoritesList(listName, newName)) {
            rootsChanged();
        }
    }

    public void addFavoritesListener(FavoritesListener listener) {
        myListeners.add(listener);
        listener.rootsChanged();
    }

    public void removeFavoritesListener(FavoritesListener listener) {
        myListeners.remove(listener);
    }

    List<AbstractTreeNode> createRootNodes() {
        List<AbstractTreeNode> result = new ArrayList<AbstractTreeNode>();
        for (String listName : myName2FavoritesRoots.keySet()) {
            result.add(new FavoritesListNode(myProject, listName, myDescriptions.get(listName)));
        }
        ArrayList<FavoritesListProvider> providers = new ArrayList<FavoritesListProvider>(myProviders.values());
        Collections.sort(providers);
        for (FavoritesListProvider provider : providers) {
            result.add(provider.createFavoriteListNode(myProject));
        }
        return result;
    }

    public static FavoritesManager getInstance(Project project) {
        return project.getComponent(FavoritesManager.class);
    }

    public FavoritesManager(Project project) {
        myProject = project;
    }

    @NotNull
    public List<String> getAvailableFavoritesListNames() {
        return new ArrayList<String>(myName2FavoritesRoots.keySet());
    }

    public synchronized void createNewList(@NotNull String listName) {
        myListOrder.add(listName);
        myName2FavoritesRoots.put(listName, new ArrayList<TreeItem<Pair<AbstractUrl, String>>>());
        listAdded(listName);
    }

    public synchronized void fireListeners(@NotNull final String listName) {
        rootsChanged();
    }

    public FavoritesViewSettings getViewSettings() {
        return myViewSettings;
    }

    public synchronized boolean removeFavoritesList(@NotNull String name) {
        boolean result = myName2FavoritesRoots.remove(name) != null;
        myListOrder.remove(name);
        myDescriptions.remove(name);
        listRemoved(name);
        return result;
    }

    @NotNull
    public List<TreeItem<Pair<AbstractUrl, String>>> getFavoritesListRootUrls(@NotNull String name) {
        final List<TreeItem<Pair<AbstractUrl, String>>> pairs = myName2FavoritesRoots.get(name);
        return pairs == null ? new ArrayList<TreeItem<Pair<AbstractUrl, String>>>() : pairs;
    }

    public synchronized boolean addRoots(@NotNull String name, Module moduleContext, @NotNull Object elements) {
        Collection<AbstractTreeNode> nodes = AddToFavoritesAction.createNodes(myProject, moduleContext, elements,
                true, getViewSettings());
        return !nodes.isEmpty() && addRoots(name, nodes);
    }

    public synchronized Comparator<FavoritesTreeNodeDescriptor> getCustomComparator(@NotNull final String name) {
        return myProviders.get(name);
    }

    private Pair<AbstractUrl, String> createPairForNode(AbstractTreeNode node) {
        final String className = node.getClass().getName();
        final Object value = node.getValue();
        final AbstractUrl url = createUrlByElement(value, myProject);
        if (url == null)
            return null;
        return Pair.create(url, className);
    }

    public boolean addRoots(final String name, final Collection<AbstractTreeNode> nodes) {
        final Collection<TreeItem<Pair<AbstractUrl, String>>> list = getFavoritesListRootUrls(name);

        final HashSet<AbstractUrl> set = new HashSet<AbstractUrl>(
                ObjectsConvertor.convert(list, new Convertor<TreeItem<Pair<AbstractUrl, String>>, AbstractUrl>() {
                    @Override
                    public AbstractUrl convert(TreeItem<Pair<AbstractUrl, String>> o) {
                        return o.getData().getFirst();
                    }
                }));
        for (AbstractTreeNode node : nodes) {
            final Pair<AbstractUrl, String> pair = createPairForNode(node);
            if (pair != null) {
                if (set.contains(pair.getFirst()))
                    continue;
                final TreeItem<Pair<AbstractUrl, String>> treeItem = new TreeItem<Pair<AbstractUrl, String>>(pair);
                list.add(treeItem);
                set.add(pair.getFirst());
                appendChildNodes(node, treeItem);
            }
        }
        rootsChanged();
        return true;
    }

    private void appendChildNodes(AbstractTreeNode node, TreeItem<Pair<AbstractUrl, String>> treeItem) {
        final Collection<? extends AbstractTreeNode> children = node.getChildren();
        for (AbstractTreeNode child : children) {
            final TreeItem<Pair<AbstractUrl, String>> childTreeItem = new TreeItem<Pair<AbstractUrl, String>>(
                    createPairForNode(child));
            treeItem.addChild(childTreeItem);
            appendChildNodes(child, childTreeItem);
        }
    }

    public synchronized boolean addRoot(@NotNull String name, @NotNull List<AbstractTreeNode> parentElements,
            final AbstractTreeNode newElement, @Nullable AbstractTreeNode sibling) {
        final List<TreeItem<Pair<AbstractUrl, String>>> items = myName2FavoritesRoots.get(name);
        if (items == null)
            return false;
        AbstractUrl url = createUrlByElement(newElement.getValue(), myProject);
        if (url == null)
            return false;
        final TreeItem<Pair<AbstractUrl, String>> newItem = new TreeItem<Pair<AbstractUrl, String>>(
                Pair.create(url, newElement.getClass().getName()));

        if (parentElements.isEmpty()) {
            // directly to list
            if (sibling != null) {
                TreeItem<Pair<AbstractUrl, String>> after = null;
                AbstractUrl siblingUrl = createUrlByElement(sibling.getValue(), myProject);
                int idx = -1;
                for (int i = 0; i < items.size(); i++) {
                    TreeItem<Pair<AbstractUrl, String>> item = items.get(i);
                    if (item.getData().getFirst().equals(siblingUrl)) {
                        idx = i;
                        break;
                    }
                }
                if (idx != -1) {
                    items.add(idx, newItem);
                } else {
                    items.add(newItem);
                }
            } else {
                items.add(newItem);
            }

            rootsChanged();
            return true;
        }

        Collection<TreeItem<Pair<AbstractUrl, String>>> list = items;
        TreeItem<Pair<AbstractUrl, String>> item = null;
        for (AbstractTreeNode obj : parentElements) {
            AbstractUrl objUrl = createUrlByElement(obj.getValue(), myProject);
            item = findNextItem(objUrl, list);
            if (item == null)
                return false;
            list = item.getChildren();
        }

        if (sibling != null) {
            TreeItem<Pair<AbstractUrl, String>> after = null;
            AbstractUrl siblingUrl = createUrlByElement(sibling.getValue(), myProject);
            for (TreeItem<Pair<AbstractUrl, String>> treeItem : list) {
                if (treeItem.getData().getFirst().equals(siblingUrl)) {
                    after = treeItem;
                    break;
                }
            }
            if (after == null) {
                item.addChild(newItem);
            } else {
                item.addChildAfter(newItem, after);
            }
        } else {
            item.addChild(newItem);
        }
        rootsChanged();
        return true;
    }

    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);
            rootsChanged();
            return true;
        }
        return false;
    }

    public synchronized boolean removeRoot(@NotNull String name, @NotNull List<AbstractTreeNode> elements) {
        final Convertor<AbstractTreeNode, AbstractUrl> convertor = new Convertor<AbstractTreeNode, AbstractUrl>() {
            @Override
            public AbstractUrl convert(AbstractTreeNode obj) {
                return createUrlByElement(obj.getValue(), myProject);
            }
        };
        boolean result = true;
        for (AbstractTreeNode element : elements) {
            final List<AbstractTreeNode> path = TaskDefaultFavoriteListProvider.getPathToUsualNode(element);
            result &= findListToRemoveFrom(name, path.subList(1, path.size()), convertor);
        }
        return result;
    }

    private TreeItem<Pair<AbstractUrl, String>> findNextItem(AbstractUrl url,
            Collection<TreeItem<Pair<AbstractUrl, String>>> list) {
        for (TreeItem<Pair<AbstractUrl, String>> pair : list) {
            if (url.equals(pair.getData().getFirst())) {
                return pair;
            }
        }
        return null;
    }

    private boolean renameFavoritesList(@NotNull String oldName, @NotNull String newName) {
        List<TreeItem<Pair<AbstractUrl, String>>> list = myName2FavoritesRoots.remove(oldName);
        if (list != null && newName.length() > 0) {
            int index = myListOrder.indexOf(oldName);
            if (index == -1) {
                index = myListOrder.size();
            }
            myListOrder.set(index, newName);
            myName2FavoritesRoots.put(newName, list);
            String description = myDescriptions.remove(oldName);
            if (description != null) {
                myDescriptions.put(newName, description);
            }
            rootsChanged();
            return true;
        }
        return false;
    }

    @Override
    public void initComponent() {
    }

    @Override
    public void disposeComponent() {
    }

    @Override
    public void projectOpened() {
        if (!ApplicationManager.getApplication().isUnitTestMode()) {
            StartupManager.getInstance(myProject).registerPostStartupActivity(new DumbAwareRunnable() {
                @Override
                public void run() {
                    final FavoritesListProvider[] providers = Extensions.getExtensions(EP_NAME, myProject);
                    for (FavoritesListProvider provider : providers) {
                        myProviders.put(provider.getListName(myProject), provider);
                    }
                    final MyRootsChangeAdapter myPsiTreeChangeAdapter = new MyRootsChangeAdapter();

                    PsiManager.getInstance(myProject).addPsiTreeChangeListener(myPsiTreeChangeAdapter, myProject);
                    if (myName2FavoritesRoots.isEmpty()) {
                        myDescriptions.put(myProject.getName(), "auto-added");
                        createNewList(myProject.getName());
                    }
                }
            });
        }
    }

    @Override
    public void projectClosed() {
    }

    @Override
    @NotNull
    public String getComponentName() {
        return "FavoritesManager";
    }

    @Nullable
    public FavoritesListProvider getListProvider(@Nullable String name) {
        return myProviders.get(name);
    }

    @Override
    public void readExternal(Element element) throws InvalidDataException {
        myName2FavoritesRoots.clear();
        for (Object list : element.getChildren(ELEMENT_FAVORITES_LIST)) {
            final String name = ((Element) list).getAttributeValue(ATTRIBUTE_NAME);
            List<TreeItem<Pair<AbstractUrl, String>>> roots = readRoots((Element) list, myProject);
            myListOrder.add(name);
            myName2FavoritesRoots.put(name, roots);
        }
        DefaultJDOMExternalizer.readExternal(this, element);
    }

    @NonNls
    private static final String CLASS_NAME = "klass";
    @NonNls
    private static final String FAVORITES_ROOT = "favorite_root";
    @NonNls
    private static final String ELEMENT_FAVORITES_LIST = "favorites_list";
    @NonNls
    private static final String ATTRIBUTE_NAME = "name";

    private static List<TreeItem<Pair<AbstractUrl, String>>> readRoots(final Element list, Project project) {
        List<TreeItem<Pair<AbstractUrl, String>>> result = new ArrayList<TreeItem<Pair<AbstractUrl, String>>>();
        readFavoritesOneLevel(list, project, result);
        return result;
    }

    private static void readFavoritesOneLevel(Element list, Project project,
            Collection<TreeItem<Pair<AbstractUrl, String>>> result) {
        final List listChildren = list.getChildren(FAVORITES_ROOT);
        if (listChildren == null || listChildren.isEmpty())
            return;

        for (Object favorite : listChildren) {
            final Element favoriteElement = (Element) favorite;
            final String className = favoriteElement.getAttributeValue(CLASS_NAME);
            final AbstractUrl abstractUrl = readUrlFromElement(favoriteElement, project);
            if (abstractUrl != null) {
                final TreeItem<Pair<AbstractUrl, String>> treeItem = new TreeItem<Pair<AbstractUrl, String>>(
                        Pair.create(abstractUrl, className));
                result.add(treeItem);
                readFavoritesOneLevel(favoriteElement, project, treeItem.getChildren());
            }
        }
    }

    private static final ArrayList<AbstractUrl> ourAbstractUrlProviders = new ArrayList<AbstractUrl>();

    static {
        ourAbstractUrlProviders.add(new ModuleUrl(null, null));
        ourAbstractUrlProviders.add(new DirectoryUrl(null, null));

        ourAbstractUrlProviders.add(new ModuleGroupUrl(null));

        ourAbstractUrlProviders.add(new PsiFileUrl(null));
        ourAbstractUrlProviders.add(new LibraryModuleGroupUrl(null));
        ourAbstractUrlProviders.add(new NamedLibraryUrl(null, null));
    }

    @NonNls
    private static final String ATTRIBUTE_TYPE = "type";
    @NonNls
    private static final String ATTRIBUTE_URL = "url";
    @NonNls
    private static final String ATTRIBUTE_MODULE = "module";

    @Nullable
    private static AbstractUrl readUrlFromElement(Element element, Project project) {
        final String type = element.getAttributeValue(ATTRIBUTE_TYPE);
        final String urlValue = element.getAttributeValue(ATTRIBUTE_URL);
        final String moduleName = element.getAttributeValue(ATTRIBUTE_MODULE);

        for (FavoriteNodeProvider nodeProvider : Extensions.getExtensions(FavoriteNodeProvider.EP_NAME, project)) {
            if (nodeProvider.getFavoriteTypeId().equals(type)) {
                return new AbstractUrlFavoriteAdapter(urlValue, moduleName, nodeProvider);
            }
        }

        for (AbstractUrl urlProvider : ourAbstractUrlProviders) {
            AbstractUrl url = urlProvider.createUrl(type, moduleName, urlValue);
            if (url != null)
                return url;
        }
        return null;
    }

    @Override
    public void writeExternal(Element element) throws WriteExternalException {
        for (final String name : myName2FavoritesRoots.keySet()) {
            Element list = new Element(ELEMENT_FAVORITES_LIST);
            list.setAttribute(ATTRIBUTE_NAME, name);
            writeRoots(list, myName2FavoritesRoots.get(name));
            element.addContent(list);
        }
        DefaultJDOMExternalizer.writeExternal(this, element);
    }

    @Nullable
    public static AbstractUrl createUrlByElement(Object element, final Project project) {
        if (element instanceof SmartPsiElementPointer)
            element = ((SmartPsiElementPointer) element).getElement();

        for (FavoriteNodeProvider nodeProvider : Extensions.getExtensions(FavoriteNodeProvider.EP_NAME, project)) {
            String url = nodeProvider.getElementUrl(element);
            if (url != null) {
                return new AbstractUrlFavoriteAdapter(url, nodeProvider.getElementModuleName(element),
                        nodeProvider);
            }
        }

        for (AbstractUrl urlProvider : ourAbstractUrlProviders) {
            AbstractUrl url = urlProvider.createUrlByElement(element);
            if (url != null)
                return url;
        }
        return null;
    }

    private static void writeRoots(Element element, Collection<TreeItem<Pair<AbstractUrl, String>>> roots) {
        for (TreeItem<Pair<AbstractUrl, String>> root : roots) {
            final AbstractUrl url = root.getData().getFirst();
            if (url == null)
                continue;
            final Element list = new Element(FAVORITES_ROOT);
            url.write(list);
            list.setAttribute(CLASS_NAME, root.getData().getSecond());
            element.addContent(list);
            final List<TreeItem<Pair<AbstractUrl, String>>> children = root.getChildren();
            if (children != null && !children.isEmpty()) {
                writeRoots(list, children);
            }
        }
    }

    public String getFavoriteListName(@Nullable final String currentSubId, @NotNull final VirtualFile vFile) {
        if (currentSubId != null && contains(currentSubId, vFile)) {
            return currentSubId;
        }
        for (String listName : myName2FavoritesRoots.keySet()) {
            if (contains(listName, vFile)) {
                return listName;
            }
        }
        return null;
    }

    // currently only one level here..
    public boolean contains(@NotNull String name, @NotNull final VirtualFile vFile) {
        final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
        final Set<Boolean> find = new HashSet<Boolean>();
        final ContentIterator contentIterator = new ContentIterator() {
            @Override
            public boolean processFile(VirtualFile fileOrDir) {
                if (fileOrDir != null && fileOrDir.getPath().equals(vFile.getPath())) {
                    find.add(Boolean.TRUE);
                }
                return true;
            }
        };

        Collection<TreeItem<Pair<AbstractUrl, String>>> urls = getFavoritesListRootUrls(name);
        for (TreeItem<Pair<AbstractUrl, String>> pair : urls) {
            AbstractUrl abstractUrl = pair.getData().getFirst();
            if (abstractUrl == null) {
                continue;
            }
            final Object[] path = abstractUrl.createPath(myProject);
            if (path == null || path.length < 1 || path[0] == null) {
                continue;
            }
            Object element = path[path.length - 1];
            if (element instanceof SmartPsiElementPointer) {
                final VirtualFile virtualFile = PsiUtilBase
                        .getVirtualFile(((SmartPsiElementPointer) element).getElement());
                if (virtualFile == null)
                    continue;
                if (vFile.getPath().equals(virtualFile.getPath())) {
                    return true;
                }
                if (!virtualFile.isDirectory()) {
                    continue;
                }
                projectFileIndex.iterateContentUnderDirectory(virtualFile, contentIterator);
            }

            if (element instanceof PsiElement) {
                final VirtualFile virtualFile = PsiUtilBase.getVirtualFile((PsiElement) element);
                if (virtualFile == null)
                    continue;
                if (vFile.getPath().equals(virtualFile.getPath())) {
                    return true;
                }
                if (!virtualFile.isDirectory()) {
                    continue;
                }
                projectFileIndex.iterateContentUnderDirectory(virtualFile, contentIterator);
            }
            if (element instanceof Module) {
                ModuleRootManager.getInstance((Module) element).getFileIndex().iterateContent(contentIterator);
            }
            if (element instanceof LibraryGroupElement) {
                final boolean inLibrary = ModuleRootManager.getInstance(((LibraryGroupElement) element).getModule())
                        .getFileIndex().isInContent(vFile) && projectFileIndex.isInLibraryClasses(vFile);
                if (inLibrary) {
                    return true;
                }
            }
            if (element instanceof NamedLibraryElement) {
                NamedLibraryElement namedLibraryElement = (NamedLibraryElement) element;
                final VirtualFile[] files = namedLibraryElement.getOrderEntry()
                        .getFiles(BinariesOrderRootType.getInstance());
                if (files != null && ArrayUtil.find(files, vFile) > -1) {
                    return true;
                }
            }
            if (element instanceof ModuleGroup) {
                ModuleGroup group = (ModuleGroup) element;
                final Collection<Module> modules = group.modulesInGroup(myProject, true);
                for (Module module : modules) {
                    ModuleRootManager.getInstance(module).getFileIndex().iterateContent(contentIterator);
                }
            }

            for (FavoriteNodeProvider provider : Extensions.getExtensions(FavoriteNodeProvider.EP_NAME,
                    myProject)) {
                if (provider.elementContainsFile(element, vFile)) {
                    return true;
                }
            }

            if (!find.isEmpty()) {
                return true;
            }
        }
        return false;
    }

    private static void iterateTreeItems(final Collection<TreeItem<Pair<AbstractUrl, String>>> coll,
            Consumer<TreeItem<Pair<AbstractUrl, String>>> consumer) {
        final ArrayDeque<TreeItem<Pair<AbstractUrl, String>>> queue = new ArrayDeque<TreeItem<Pair<AbstractUrl, String>>>();
        queue.addAll(coll);
        while (!queue.isEmpty()) {
            final TreeItem<Pair<AbstractUrl, String>> item = queue.removeFirst();
            consumer.consume(item);
            final List<TreeItem<Pair<AbstractUrl, String>>> children = item.getChildren();
            if (children != null && !children.isEmpty()) {
                queue.addAll(children);
            }
        }
    }

    private class MyRootsChangeAdapter extends PsiTreeChangeAdapter {
        @Override
        public void beforeChildMovement(@NotNull final PsiTreeChangeEvent event) {
            final PsiElement oldParent = event.getOldParent();
            final PsiElement newParent = event.getNewParent();
            final PsiElement child = event.getChild();
            if (newParent instanceof PsiDirectory) {
                final Module module = ModuleUtil.findModuleForPsiElement(newParent);
                if (module == null)
                    return;
                AbstractUrl childUrl = null;
                if (child instanceof PsiFile) {
                    childUrl = new PsiFileUrl(((PsiDirectory) newParent).getVirtualFile().getUrl() + "/"
                            + ((PsiFile) child).getName());
                } else if (child instanceof PsiDirectory) {
                    childUrl = new DirectoryUrl(((PsiDirectory) newParent).getVirtualFile().getUrl() + "/"
                            + ((PsiDirectory) child).getName(), module.getName());
                }

                for (String listName : myName2FavoritesRoots.keySet()) {
                    final List<TreeItem<Pair<AbstractUrl, String>>> roots = myName2FavoritesRoots.get(listName);
                    final AbstractUrl finalChildUrl = childUrl;
                    iterateTreeItems(roots, new Consumer<TreeItem<Pair<AbstractUrl, String>>>() {
                        @Override
                        public void consume(TreeItem<Pair<AbstractUrl, String>> item) {
                            final Pair<AbstractUrl, String> root = item.getData();
                            final Object[] path = root.first.createPath(myProject);
                            if (path == null || path.length < 1 || path[0] == null) {
                                return;
                            }
                            final Object element = path[path.length - 1];
                            if (element == child && finalChildUrl != null) {
                                item.setData(Pair.create(finalChildUrl, root.second));
                            } else {
                                if (element == oldParent) {
                                    item.setData(
                                            Pair.create(root.first.createUrlByElement(newParent), root.second));
                                }
                            }
                        }
                    });
                }
            }
        }

        @Override
        public void beforePropertyChange(@NotNull final PsiTreeChangeEvent event) {
            if (event.getPropertyName().equals(PsiTreeChangeEvent.PROP_FILE_NAME)
                    || event.getPropertyName().equals(PsiTreeChangeEvent.PROP_DIRECTORY_NAME)) {
                final PsiElement psiElement = event.getChild();
                if (psiElement instanceof PsiFile || psiElement instanceof PsiDirectory) {
                    final Module module = ModuleUtil.findModuleForPsiElement(psiElement);
                    if (module == null)
                        return;
                    final String url = ((PsiDirectory) psiElement.getParent()).getVirtualFile().getUrl() + "/"
                            + event.getNewValue();
                    final AbstractUrl childUrl = psiElement instanceof PsiFile ? new PsiFileUrl(url)
                            : new DirectoryUrl(url, module.getName());

                    for (String listName : myName2FavoritesRoots.keySet()) {
                        final List<TreeItem<Pair<AbstractUrl, String>>> roots = myName2FavoritesRoots.get(listName);
                        iterateTreeItems(roots, new Consumer<TreeItem<Pair<AbstractUrl, String>>>() {
                            @Override
                            public void consume(TreeItem<Pair<AbstractUrl, String>> item) {
                                final Pair<AbstractUrl, String> root = item.getData();
                                final Object[] path = root.first.createPath(myProject);
                                if (path == null || path.length < 1 || path[0] == null) {
                                    return;
                                }
                                final Object element = path[path.length - 1];
                                if (element == psiElement && psiElement instanceof PsiFile) {
                                    item.setData(Pair.create(childUrl, root.second));
                                } else {
                                    item.setData(root);
                                }
                            }
                        });
                    }
                }
            }
        }
    }
}