garbage reduced
[idea/community.git] / lang-impl / src / com / intellij / ide / favoritesTreeView / FavoritesManager.java
1 package com.intellij.ide.favoritesTreeView;
2
3 import com.intellij.ide.favoritesTreeView.actions.AddToFavoritesAction;
4 import com.intellij.ide.projectView.ViewSettings;
5 import com.intellij.ide.projectView.impl.*;
6 import com.intellij.ide.projectView.impl.nodes.LibraryGroupElement;
7 import com.intellij.ide.projectView.impl.nodes.NamedLibraryElement;
8 import com.intellij.ide.util.treeView.AbstractTreeNode;
9 import com.intellij.openapi.components.ProjectComponent;
10 import com.intellij.openapi.extensions.Extensions;
11 import com.intellij.openapi.module.Module;
12 import com.intellij.openapi.module.ModuleUtil;
13 import com.intellij.openapi.project.Project;
14 import com.intellij.openapi.roots.*;
15 import com.intellij.openapi.startup.StartupManager;
16 import com.intellij.openapi.util.*;
17 import com.intellij.openapi.vfs.VirtualFile;
18 import com.intellij.psi.*;
19 import com.intellij.psi.util.PsiUtilBase;
20 import com.intellij.util.ArrayUtil;
21 import org.jdom.Element;
22 import org.jetbrains.annotations.NonNls;
23 import org.jetbrains.annotations.NotNull;
24 import org.jetbrains.annotations.Nullable;
25
26 import java.util.*;
27
28 public class FavoritesManager implements ProjectComponent, JDOMExternalizable {
29   // fav list name -> list of (root: root url, root class)
30   private Map<String, List<Pair<AbstractUrl,String>>> myName2FavoritesRoots = new LinkedHashMap<String, List<Pair<AbstractUrl, String>>>();
31   private final Project myProject;
32   private final MyRootsChangeAdapter myPsiTreeChangeAdapter = new MyRootsChangeAdapter();
33   private final List<FavoritesListener> myListeners = new ArrayList<FavoritesListener>();
34   public interface FavoritesListener {
35     void rootsChanged(String listName);
36     void listAdded(String listName);
37     void listRemoved(String listName);
38   }
39   private final FavoritesListener fireListeners = new FavoritesListener() {
40     public void rootsChanged(String listName) {
41       FavoritesListener[] listeners = myListeners.toArray(new FavoritesListener[myListeners.size()]);
42       for (FavoritesListener listener : listeners) {
43         listener.rootsChanged(listName);
44       }
45     }
46
47     public void listAdded(String listName) {
48       FavoritesListener[] listeners = myListeners.toArray(new FavoritesListener[myListeners.size()]);
49       for (FavoritesListener listener : listeners) {
50         listener.listAdded(listName);
51       }
52     }
53
54     public void listRemoved(String listName) {
55       FavoritesListener[] listeners = myListeners.toArray(new FavoritesListener[myListeners.size()]);
56       for (FavoritesListener listener : listeners) {
57         listener.listRemoved(listName);
58       }
59     }
60   };
61
62   public synchronized void addFavoritesListener(FavoritesListener listener) {
63     myListeners.add(listener);
64   }
65   public synchronized void removeFavoritesListener(FavoritesListener listener) {
66     myListeners.remove(listener);
67   }
68
69   public static FavoritesManager getInstance(Project project) {
70     return project.getComponent(FavoritesManager.class);
71   }
72
73   public FavoritesManager(Project project) {
74     myProject = project;
75   }
76
77   @NotNull public String[] getAvailableFavoritesLists(){
78     final Set<String> keys = myName2FavoritesRoots.keySet();
79     return ArrayUtil.toStringArray(keys);
80   }
81
82   public synchronized void createNewList(@NotNull String name){
83     myName2FavoritesRoots.put(name, new ArrayList<Pair<AbstractUrl, String>>());
84     fireListeners.listAdded(name);
85   }
86
87   public synchronized boolean removeFavoritesList(@NotNull String name){
88     if (name.equals(myProject.getName())) return false;
89     boolean result = myName2FavoritesRoots.remove(name) != null;
90     fireListeners.listRemoved(name);
91     return result;
92   }
93
94   public List<Pair<AbstractUrl,String>> getFavoritesListRootUrls(@NotNull String name) {
95     return myName2FavoritesRoots.get(name);
96   }
97
98   public synchronized boolean addRoots(@NotNull String name, Module moduleContext, @NotNull Object elements) {
99     Collection<AbstractTreeNode> nodes = AddToFavoritesAction.createNodes(myProject, moduleContext, elements, true, ViewSettings.DEFAULT);
100     return !nodes.isEmpty() && addRoots(name, nodes);
101   }
102
103   public boolean addRoots(final String name, final Collection<AbstractTreeNode> nodes) {
104     final List<Pair<AbstractUrl, String>> list = getFavoritesListRootUrls(name);
105     for (AbstractTreeNode node : nodes) {
106       final String className = node.getClass().getName();
107       final Object value = node.getValue();
108       final AbstractUrl url = createUrlByElement(value, myProject);
109       if (url != null) {
110         list.add(Pair.create(url, className));
111       }
112     }
113     fireListeners.rootsChanged(name);
114     return true;
115   }
116
117   public synchronized boolean removeRoot(@NotNull String name, @NotNull Object element) {
118     AbstractUrl url = createUrlByElement(element, myProject);
119     if (url == null) return false;
120     List<Pair<AbstractUrl, String>> list = getFavoritesListRootUrls(name);
121     for (Pair<AbstractUrl, String> pair : list) {
122       if (url.equals(pair.getFirst())) {
123         list.remove(pair);
124         break;
125       }
126     }
127     fireListeners.rootsChanged(name);
128     return true;
129   }
130
131   public synchronized boolean renameFavoritesList(@NotNull String oldName, @NotNull String newName) {
132     List<Pair<AbstractUrl, String>> list = myName2FavoritesRoots.remove(oldName);
133     if (list != null && newName.length() > 0) {
134       myName2FavoritesRoots.put(newName, list);
135       fireListeners.listRemoved(oldName);
136       fireListeners.listAdded(newName);
137       return true;
138     }
139     return false;
140   }
141
142   public void initComponent() {
143   }
144
145   public void disposeComponent() {}
146
147   public void projectOpened() {
148     StartupManager.getInstance(myProject).registerPostStartupActivity(new Runnable() {
149       public void run() {
150         if (myName2FavoritesRoots.isEmpty()) {
151           final String name = myProject.getName();
152           createNewList(name);
153         }
154         PsiManager.getInstance(myProject).addPsiTreeChangeListener(myPsiTreeChangeAdapter);
155       }
156     });
157   }
158
159   public void projectClosed() {
160     PsiManager.getInstance(myProject).removePsiTreeChangeListener(myPsiTreeChangeAdapter);
161   }
162
163   @NotNull
164   public String getComponentName() {
165     return "FavoritesManager";
166   }
167
168   public void readExternal(Element element) throws InvalidDataException {
169     myName2FavoritesRoots.clear();
170     for (Object list : element.getChildren(ELEMENT_FAVORITES_LIST)) {
171       final String name = ((Element)list).getAttributeValue(ATTRIBUTE_NAME);
172       List<Pair<AbstractUrl, String>> roots = readRoots((Element)list, myProject);
173       myName2FavoritesRoots.put(name, roots);
174     }
175     DefaultJDOMExternalizer.readExternal(this, element);
176   }
177
178   @NonNls private static final String CLASS_NAME = "klass";
179   @NonNls private static final String FAVORITES_ROOT = "favorite_root";
180   @NonNls private static final String ELEMENT_FAVORITES_LIST = "favorites_list";
181   @NonNls private static final String ATTRIBUTE_NAME = "name";
182   private static List<Pair<AbstractUrl, String>> readRoots(final Element list, Project project) {
183     List<Pair<AbstractUrl, String>> result = new ArrayList<Pair<AbstractUrl, String>>();
184     for (Object favorite : list.getChildren(FAVORITES_ROOT)) {
185       final String className = ((Element)favorite).getAttributeValue(CLASS_NAME);
186       final AbstractUrl abstractUrl = readUrlFromElement(((Element)favorite), project);
187       if (abstractUrl != null) {
188         result.add(Pair.create(abstractUrl, className));
189       }
190     }
191     return result;
192   }
193
194   private static final ArrayList<AbstractUrl> ourAbstractUrlProviders = new ArrayList<AbstractUrl>();
195   static {
196     ourAbstractUrlProviders.add(new ModuleUrl(null, null));
197     ourAbstractUrlProviders.add(new DirectoryUrl(null, null));
198     
199     ourAbstractUrlProviders.add(new ModuleGroupUrl(null));
200
201     ourAbstractUrlProviders.add(new PsiFileUrl(null, null));
202     ourAbstractUrlProviders.add(new LibraryModuleGroupUrl(null));
203     ourAbstractUrlProviders.add(new NamedLibraryUrl(null, null));
204   }
205   @NonNls private static final String ATTRIBUTE_TYPE = "type";
206   @NonNls private static final String ATTRIBUTE_URL = "url";
207   @NonNls private static final String ATTRIBUTE_MODULE = "module";
208
209   @Nullable
210   private static AbstractUrl readUrlFromElement(Element element, Project project) {
211     final String type = element.getAttributeValue(ATTRIBUTE_TYPE);
212     final String urlValue = element.getAttributeValue(ATTRIBUTE_URL);
213     final String moduleName = element.getAttributeValue(ATTRIBUTE_MODULE);
214
215     for(FavoriteNodeProvider nodeProvider: Extensions.getExtensions(FavoriteNodeProvider.EP_NAME, project)) {
216       if (nodeProvider.getFavoriteTypeId().equals(type)) {
217         return new AbstractUrlFavoriteAdapter(urlValue, moduleName, nodeProvider);
218       }
219     }
220
221     for (AbstractUrl urlProvider : ourAbstractUrlProviders) {
222       AbstractUrl url = urlProvider.createUrl(type, moduleName, urlValue);
223       if (url != null) return url;
224     }
225     return null;
226   }
227
228
229   public void writeExternal(Element element) throws WriteExternalException {
230     for (final String name : myName2FavoritesRoots.keySet()) {
231       Element list = new Element(ELEMENT_FAVORITES_LIST);
232       list.setAttribute(ATTRIBUTE_NAME, name);
233       writeRoots(list, myName2FavoritesRoots.get(name));
234       element.addContent(list);
235     }
236     DefaultJDOMExternalizer.writeExternal(this, element);
237   }
238
239   private static @Nullable AbstractUrl createUrlByElement(Object element, final Project project) {
240     if (element instanceof SmartPsiElementPointer) element = ((SmartPsiElementPointer)element).getElement();
241                                                                                                                                                
242     for(FavoriteNodeProvider nodeProvider: Extensions.getExtensions(FavoriteNodeProvider.EP_NAME, project)) {
243       String url = nodeProvider.getElementUrl(element);
244       if (url != null) {
245         return new AbstractUrlFavoriteAdapter(url, nodeProvider.getElementModuleName(element), nodeProvider);
246       }
247     }
248
249     for (AbstractUrl urlProvider : ourAbstractUrlProviders) {
250       AbstractUrl url = urlProvider.createUrlByElement(element);
251       if (url != null) return url;
252     }
253     return null;
254   }
255
256   private static void writeRoots(Element element, List<Pair<AbstractUrl, String>> roots) throws WriteExternalException {
257     for (Pair<AbstractUrl, String> root : roots) {
258       final AbstractUrl url = root.getFirst();
259       if (url == null) continue;
260       final Element list = new Element(FAVORITES_ROOT);
261       url.write(list);
262       list.setAttribute(CLASS_NAME, root.getSecond());
263       element.addContent(list);
264     }
265   }
266
267
268   public boolean contains(@NotNull String name, @NotNull final VirtualFile vFile){
269     final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
270     final Set<Boolean> find = new HashSet<Boolean>();
271     final ContentIterator contentIterator = new ContentIterator() {
272       public boolean processFile(VirtualFile fileOrDir) {
273         if (fileOrDir != null && fileOrDir.getPath().equals(vFile.getPath())) {
274           find.add(Boolean.TRUE);
275         }
276         return true;
277       }
278     };
279
280     List<Pair<AbstractUrl, String>> urls = getFavoritesListRootUrls(name);
281     for (Pair<AbstractUrl, String> pair : urls) {
282       AbstractUrl abstractUrl = pair.getFirst();
283       if (abstractUrl == null) {
284         continue;
285       }
286       final Object[] path = abstractUrl.createPath(myProject);
287       if (path == null || path.length < 1 || path[0] == null) {
288         continue;
289       }
290       Object element = path[path.length - 1];
291       if (element instanceof SmartPsiElementPointer) {
292         final VirtualFile virtualFile = PsiUtilBase.getVirtualFile(((SmartPsiElementPointer)element).getElement());
293         if (virtualFile == null) continue;
294         if (vFile.getPath().equals(virtualFile.getPath())) {
295           return true;
296         }
297         if (!virtualFile.isDirectory()) {
298           continue;
299         }
300         projectFileIndex.iterateContentUnderDirectory(virtualFile, contentIterator);
301       }
302
303       if (element instanceof PsiElement) {
304         final VirtualFile virtualFile = PsiUtilBase.getVirtualFile((PsiElement)element);
305         if (virtualFile == null) continue;
306         if (vFile.getPath().equals(virtualFile.getPath())){
307           return true;
308         }
309         if (!virtualFile.isDirectory()){
310           continue;
311         }
312         projectFileIndex.iterateContentUnderDirectory(virtualFile, contentIterator);
313       }
314       if (element instanceof Module){
315         ModuleRootManager.getInstance((Module)element).getFileIndex().iterateContent(contentIterator);
316       }
317       if (element instanceof LibraryGroupElement){
318         final boolean inLibrary =
319           ModuleRootManager.getInstance(((LibraryGroupElement)element).getModule()).getFileIndex().isInContent(vFile) &&
320           projectFileIndex.isInLibraryClasses(vFile);
321         if (inLibrary){
322           return true;
323         }
324       }
325       if (element instanceof NamedLibraryElement){
326         NamedLibraryElement namedLibraryElement = (NamedLibraryElement)element;
327         final VirtualFile[] files = namedLibraryElement.getOrderEntry().getFiles(OrderRootType.CLASSES);
328         if (files != null && ArrayUtil.find(files, vFile) > -1){
329           return true;
330         }
331       }
332       if (element instanceof ModuleGroup){
333         ModuleGroup group = (ModuleGroup) element;
334         final Collection<Module> modules = group.modulesInGroup(myProject, true);
335         for (Module module : modules) {
336           ModuleRootManager.getInstance(module).getFileIndex().iterateContent(contentIterator);
337         }
338       }
339
340
341       for(FavoriteNodeProvider provider: Extensions.getExtensions(FavoriteNodeProvider.EP_NAME, myProject)) {
342         if (provider.elementContainsFile(element, vFile)) {
343           return true;
344         }
345       }
346
347       if (!find.isEmpty()){
348         return true;
349       }
350     }
351     return false;
352   }
353
354   private class MyRootsChangeAdapter extends PsiTreeChangeAdapter {
355     public void beforeChildMovement(final PsiTreeChangeEvent event) {
356       final PsiElement oldParent = event.getOldParent();
357       final PsiElement newParent = event.getNewParent();
358       final PsiElement child = event.getChild();
359       if (newParent instanceof PsiDirectory) {
360         final Module module = ModuleUtil.findModuleForPsiElement(newParent);
361         if (module == null) return;
362         AbstractUrl childUrl = null;
363         if (child instanceof PsiFile) {
364           childUrl =
365             new PsiFileUrl(((PsiDirectory)newParent).getVirtualFile().getUrl() + "/" + ((PsiFile)child).getName(), module.getName());
366         }
367         else if (child instanceof PsiDirectory) {
368           childUrl =
369             new DirectoryUrl(((PsiDirectory)newParent).getVirtualFile().getUrl() + "/" + ((PsiDirectory)child).getName(), module.getName());
370         }
371         for (String listName : myName2FavoritesRoots.keySet()) {
372           final List<Pair<AbstractUrl, String>> roots = myName2FavoritesRoots.get(listName);
373           final List<Pair<AbstractUrl, String>> newRoots = new ArrayList<Pair<AbstractUrl, String>>();
374           for (Pair<AbstractUrl, String> root : roots) {
375             final Object[] path = root.first.createPath(myProject);
376             if (path == null || path.length < 1 || path[0] == null) {
377               continue;
378             }
379             final Object element = path[path.length - 1];
380             if (element == child && childUrl != null) {
381               newRoots.add(Pair.create(childUrl, root.second));
382             }
383             else if (element == oldParent) {
384               newRoots.add(Pair.create(root.first.createUrlByElement(newParent), root.second));
385             }
386           }
387           myName2FavoritesRoots.put(listName, newRoots);
388         }
389       }
390     }
391
392     public void beforePropertyChange(final PsiTreeChangeEvent event) {
393       if (event.getPropertyName().equals(PsiTreeChangeEvent.PROP_FILE_NAME) || event.getPropertyName().equals(PsiTreeChangeEvent.PROP_DIRECTORY_NAME)) {
394         final PsiElement psiElement = event.getChild();
395         if (psiElement instanceof PsiFile || psiElement instanceof PsiDirectory) {
396           final Module module = ModuleUtil.findModuleForPsiElement(psiElement);
397           if (module == null) return;
398           final String url = ((PsiDirectory)psiElement.getParent()).getVirtualFile().getUrl() + "/" + event.getNewValue();
399           final AbstractUrl childUrl = psiElement instanceof PsiFile ? new PsiFileUrl(url, module.getName()) : new DirectoryUrl(url, module.getName());
400           for (String listName : myName2FavoritesRoots.keySet()) {
401             final List<Pair<AbstractUrl, String>> roots = myName2FavoritesRoots.get(listName);
402             final List<Pair<AbstractUrl, String>> newRoots = new ArrayList<Pair<AbstractUrl, String>>();
403             for (Pair<AbstractUrl, String> root : roots) {
404               final Object[] path = root.first.createPath(myProject);
405               if (path == null || path.length < 1 || path[0] == null) {
406                 continue;
407               }
408               final Object element = path[path.length - 1];
409               if (element == psiElement && psiElement instanceof PsiFile) {
410                 newRoots.add(Pair.create(childUrl, root.second));
411               } else {
412                 newRoots.add(root);
413               }
414             }
415             myName2FavoritesRoots.put(listName, newRoots);
416           }
417         }
418       }
419     }
420   }
421 }