1 package com.intellij.ide.favoritesTreeView;
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;
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);
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);
47 public void listAdded(String listName) {
48 FavoritesListener[] listeners = myListeners.toArray(new FavoritesListener[myListeners.size()]);
49 for (FavoritesListener listener : listeners) {
50 listener.listAdded(listName);
54 public void listRemoved(String listName) {
55 FavoritesListener[] listeners = myListeners.toArray(new FavoritesListener[myListeners.size()]);
56 for (FavoritesListener listener : listeners) {
57 listener.listRemoved(listName);
62 public synchronized void addFavoritesListener(FavoritesListener listener) {
63 myListeners.add(listener);
65 public synchronized void removeFavoritesListener(FavoritesListener listener) {
66 myListeners.remove(listener);
69 public static FavoritesManager getInstance(Project project) {
70 return project.getComponent(FavoritesManager.class);
73 public FavoritesManager(Project project) {
77 @NotNull public String[] getAvailableFavoritesLists(){
78 final Set<String> keys = myName2FavoritesRoots.keySet();
79 return ArrayUtil.toStringArray(keys);
82 public synchronized void createNewList(@NotNull String name){
83 myName2FavoritesRoots.put(name, new ArrayList<Pair<AbstractUrl, String>>());
84 fireListeners.listAdded(name);
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);
94 public List<Pair<AbstractUrl,String>> getFavoritesListRootUrls(@NotNull String name) {
95 return myName2FavoritesRoots.get(name);
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);
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);
110 list.add(Pair.create(url, className));
113 fireListeners.rootsChanged(name);
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())) {
127 fireListeners.rootsChanged(name);
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);
142 public void initComponent() {
145 public void disposeComponent() {}
147 public void projectOpened() {
148 StartupManager.getInstance(myProject).registerPostStartupActivity(new Runnable() {
150 if (myName2FavoritesRoots.isEmpty()) {
151 final String name = myProject.getName();
154 PsiManager.getInstance(myProject).addPsiTreeChangeListener(myPsiTreeChangeAdapter);
159 public void projectClosed() {
160 PsiManager.getInstance(myProject).removePsiTreeChangeListener(myPsiTreeChangeAdapter);
164 public String getComponentName() {
165 return "FavoritesManager";
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);
175 DefaultJDOMExternalizer.readExternal(this, element);
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));
194 private static final ArrayList<AbstractUrl> ourAbstractUrlProviders = new ArrayList<AbstractUrl>();
196 ourAbstractUrlProviders.add(new ModuleUrl(null, null));
197 ourAbstractUrlProviders.add(new DirectoryUrl(null, null));
199 ourAbstractUrlProviders.add(new ModuleGroupUrl(null));
201 ourAbstractUrlProviders.add(new PsiFileUrl(null, null));
202 ourAbstractUrlProviders.add(new LibraryModuleGroupUrl(null));
203 ourAbstractUrlProviders.add(new NamedLibraryUrl(null, null));
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";
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);
215 for(FavoriteNodeProvider nodeProvider: Extensions.getExtensions(FavoriteNodeProvider.EP_NAME, project)) {
216 if (nodeProvider.getFavoriteTypeId().equals(type)) {
217 return new AbstractUrlFavoriteAdapter(urlValue, moduleName, nodeProvider);
221 for (AbstractUrl urlProvider : ourAbstractUrlProviders) {
222 AbstractUrl url = urlProvider.createUrl(type, moduleName, urlValue);
223 if (url != null) return url;
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);
236 DefaultJDOMExternalizer.writeExternal(this, element);
239 private static @Nullable AbstractUrl createUrlByElement(Object element, final Project project) {
240 if (element instanceof SmartPsiElementPointer) element = ((SmartPsiElementPointer)element).getElement();
242 for(FavoriteNodeProvider nodeProvider: Extensions.getExtensions(FavoriteNodeProvider.EP_NAME, project)) {
243 String url = nodeProvider.getElementUrl(element);
245 return new AbstractUrlFavoriteAdapter(url, nodeProvider.getElementModuleName(element), nodeProvider);
249 for (AbstractUrl urlProvider : ourAbstractUrlProviders) {
250 AbstractUrl url = urlProvider.createUrlByElement(element);
251 if (url != null) return url;
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);
262 list.setAttribute(CLASS_NAME, root.getSecond());
263 element.addContent(list);
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);
280 List<Pair<AbstractUrl, String>> urls = getFavoritesListRootUrls(name);
281 for (Pair<AbstractUrl, String> pair : urls) {
282 AbstractUrl abstractUrl = pair.getFirst();
283 if (abstractUrl == null) {
286 final Object[] path = abstractUrl.createPath(myProject);
287 if (path == null || path.length < 1 || path[0] == null) {
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())) {
297 if (!virtualFile.isDirectory()) {
300 projectFileIndex.iterateContentUnderDirectory(virtualFile, contentIterator);
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())){
309 if (!virtualFile.isDirectory()){
312 projectFileIndex.iterateContentUnderDirectory(virtualFile, contentIterator);
314 if (element instanceof Module){
315 ModuleRootManager.getInstance((Module)element).getFileIndex().iterateContent(contentIterator);
317 if (element instanceof LibraryGroupElement){
318 final boolean inLibrary =
319 ModuleRootManager.getInstance(((LibraryGroupElement)element).getModule()).getFileIndex().isInContent(vFile) &&
320 projectFileIndex.isInLibraryClasses(vFile);
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){
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);
341 for(FavoriteNodeProvider provider: Extensions.getExtensions(FavoriteNodeProvider.EP_NAME, myProject)) {
342 if (provider.elementContainsFile(element, vFile)) {
347 if (!find.isEmpty()){
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) {
365 new PsiFileUrl(((PsiDirectory)newParent).getVirtualFile().getUrl() + "/" + ((PsiFile)child).getName(), module.getName());
367 else if (child instanceof PsiDirectory) {
369 new DirectoryUrl(((PsiDirectory)newParent).getVirtualFile().getUrl() + "/" + ((PsiDirectory)child).getName(), module.getName());
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) {
379 final Object element = path[path.length - 1];
380 if (element == child && childUrl != null) {
381 newRoots.add(Pair.create(childUrl, root.second));
384 if (element == oldParent) {
385 newRoots.add(Pair.create(root.first.createUrlByElement(newParent), root.second));
390 myName2FavoritesRoots.put(listName, newRoots);
395 public void beforePropertyChange(final PsiTreeChangeEvent event) {
396 if (event.getPropertyName().equals(PsiTreeChangeEvent.PROP_FILE_NAME) || event.getPropertyName().equals(PsiTreeChangeEvent.PROP_DIRECTORY_NAME)) {
397 final PsiElement psiElement = event.getChild();
398 if (psiElement instanceof PsiFile || psiElement instanceof PsiDirectory) {
399 final Module module = ModuleUtil.findModuleForPsiElement(psiElement);
400 if (module == null) return;
401 final String url = ((PsiDirectory)psiElement.getParent()).getVirtualFile().getUrl() + "/" + event.getNewValue();
402 final AbstractUrl childUrl = psiElement instanceof PsiFile ? new PsiFileUrl(url, module.getName()) : new DirectoryUrl(url, module.getName());
403 for (String listName : myName2FavoritesRoots.keySet()) {
404 final List<Pair<AbstractUrl, String>> roots = myName2FavoritesRoots.get(listName);
405 final List<Pair<AbstractUrl, String>> newRoots = new ArrayList<Pair<AbstractUrl, String>>();
406 for (Pair<AbstractUrl, String> root : roots) {
407 final Object[] path = root.first.createPath(myProject);
408 if (path == null || path.length < 1 || path[0] == null) {
411 final Object element = path[path.length - 1];
412 if (element == psiElement && psiElement instanceof PsiFile) {
413 newRoots.add(Pair.create(childUrl, root.second));
418 myName2FavoritesRoots.put(listName, newRoots);