Do not store virtual file pointers in library order entry, use library for that
[fedora-idea.git] / platform / lang-impl / src / com / intellij / openapi / roots / impl / ProjectRootManagerImpl.java
blob491a4fd2c53e2b43be53be4aa98d5165f6e3e362
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.openapi.roots.impl;
19 import com.intellij.AppTopics;
20 import com.intellij.ProjectTopics;
21 import com.intellij.ide.caches.CacheUpdater;
22 import com.intellij.openapi.Disposable;
23 import com.intellij.openapi.application.ApplicationAdapter;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.components.ProjectComponent;
26 import com.intellij.openapi.components.impl.stores.BatchUpdateListener;
27 import com.intellij.openapi.diagnostic.Logger;
28 import com.intellij.openapi.extensions.Extensions;
29 import com.intellij.openapi.fileTypes.FileTypeEvent;
30 import com.intellij.openapi.fileTypes.FileTypeListener;
31 import com.intellij.openapi.fileTypes.FileTypeManager;
32 import com.intellij.openapi.module.ModifiableModuleModel;
33 import com.intellij.openapi.module.Module;
34 import com.intellij.openapi.module.ModuleManager;
35 import com.intellij.openapi.module.impl.ModuleImpl;
36 import com.intellij.openapi.module.impl.scopes.JdkScope;
37 import com.intellij.openapi.module.impl.scopes.LibraryRuntimeClasspathScope;
38 import com.intellij.openapi.project.DumbServiceImpl;
39 import com.intellij.openapi.project.Project;
40 import com.intellij.openapi.project.ex.ProjectEx;
41 import com.intellij.openapi.projectRoots.ProjectJdkTable;
42 import com.intellij.openapi.projectRoots.Sdk;
43 import com.intellij.openapi.roots.*;
44 import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
45 import com.intellij.openapi.roots.libraries.Library;
46 import com.intellij.openapi.roots.libraries.LibraryTable;
47 import com.intellij.openapi.startup.StartupManager;
48 import com.intellij.openapi.util.InvalidDataException;
49 import com.intellij.openapi.util.JDOMExternalizable;
50 import com.intellij.openapi.util.WriteExternalException;
51 import com.intellij.openapi.vfs.*;
52 import com.intellij.openapi.vfs.ex.VirtualFileManagerAdapter;
53 import com.intellij.openapi.vfs.pointers.VirtualFilePointer;
54 import com.intellij.openapi.vfs.pointers.VirtualFilePointerListener;
55 import com.intellij.psi.search.GlobalSearchScope;
56 import com.intellij.util.EventDispatcher;
57 import com.intellij.util.containers.ConcurrentHashMap;
58 import com.intellij.util.containers.HashMap;
59 import com.intellij.util.containers.HashSet;
60 import com.intellij.util.messages.MessageBusConnection;
61 import org.jdom.Element;
62 import org.jetbrains.annotations.NonNls;
63 import org.jetbrains.annotations.NotNull;
65 import java.util.*;
67 /**
68 * @author max
70 public class ProjectRootManagerImpl extends ProjectRootManagerEx implements ProjectComponent, JDOMExternalizable {
71 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.projectRoots.impl.ProjectRootManagerImpl");
73 @NonNls private static final String PROJECT_JDK_NAME_ATTR = "project-jdk-name";
74 @NonNls private static final String PROJECT_JDK_TYPE_ATTR = "project-jdk-type";
76 private final ProjectEx myProject;
77 private final ProjectFileIndex myProjectFileIndex;
79 private final EventDispatcher<ProjectJdkListener> myProjectJdkEventDispatcher = EventDispatcher.create(ProjectJdkListener.class);
81 private final MyVirtualFilePointerListener myVirtualFilePointerListener = new MyVirtualFilePointerListener();
83 private ApplicationListener myApplicationListener;
85 private String myProjectJdkName;
86 private String myProjectJdkType;
88 private final List<CacheUpdater> myRootsChangeUpdaters = new ArrayList<CacheUpdater>();
89 private final List<CacheUpdater> myRefreshCacheUpdaters = new ArrayList<CacheUpdater>();
91 private long myModificationCount = 0;
92 private Set<LocalFileSystem.WatchRequest> myRootsToWatch = new HashSet<LocalFileSystem.WatchRequest>();
93 @NonNls private static final String ATTRIBUTE_VERSION = "version";
95 private final Map<List<Module>, GlobalSearchScope> myLibraryScopes = new ConcurrentHashMap<List<Module>, GlobalSearchScope>();
96 private final Map<String, GlobalSearchScope> myJdkScopes = new HashMap<String, GlobalSearchScope>();
98 private boolean myStartupActivityPerformed = false;
100 private final MessageBusConnection myConnection;
101 private final VirtualFileManagerAdapter myVFSListener;
102 private final BatchUpdateListener myHandler;
104 class BatchSession {
105 int myBatchLevel = 0;
106 boolean myChanged = false;
108 final boolean myFileTypes;
110 protected BatchSession(final boolean fileTypes) {
111 myFileTypes = fileTypes;
114 void levelUp() {
115 if (myBatchLevel == 0) {
116 myChanged = false;
118 myBatchLevel += 1;
121 public void levelDown() {
122 myBatchLevel -= 1;
123 if (myChanged && myBatchLevel == 0) {
124 try {
125 fireChange();
127 finally {
128 myChanged = false;
133 boolean fireChange() {
134 return fireRootsChanged(myFileTypes);
137 public void beforeRootsChanged() {
138 if (myBatchLevel == 0 || !myChanged) {
139 if (fireBeforeRootsChanged(myFileTypes)) {
140 myChanged = true;
145 public void rootsChanged(boolean force) {
146 if (myBatchLevel == 0) {
147 if (fireChange()) {
148 myChanged = false;
154 private final BatchSession myRootsChanged = new BatchSession(false);
155 private final BatchSession myFileTypesChanged = new BatchSession(true);
157 public static ProjectRootManagerImpl getInstanceImpl(Project project) {
158 return (ProjectRootManagerImpl)getInstance(project);
161 //private final Stack<String> myRootsChangedEvents = new Stack<String>();
163 /*private void putRootsChangedStachTrace() {
164 ByteArrayOutputStream out = new ByteArrayOutputStream();
165 PrintStream stream = new PrintStream(out);
166 new RuntimeException().printStackTrace(stream);
167 stream.close();
168 myRootsChangedEvents.push(new String(out.toByteArray()));
169 } */
171 public ProjectRootManagerImpl(Project project,
172 FileTypeManager fileTypeManager,
173 DirectoryIndex directoryIndex,
174 StartupManager startupManager) {
175 myProject = (ProjectEx)project;
176 myConnection = project.getMessageBus().connect();
177 myConnection.subscribe(AppTopics.FILE_TYPES, new FileTypeListener() {
178 public void beforeFileTypesChanged(FileTypeEvent event) {
179 beforeRootsChange(true);
182 public void fileTypesChanged(FileTypeEvent event) {
183 rootsChanged(true);
187 myVFSListener = new VirtualFileManagerAdapter() {
188 @Override
189 public void afterRefreshFinish(boolean asynchonous) {
190 doUpdateOnRefresh();
193 VirtualFileManager.getInstance().addVirtualFileManagerListener(myVFSListener);
195 myProjectFileIndex = new ProjectFileIndexImpl(myProject, directoryIndex, fileTypeManager);
196 startupManager.registerStartupActivity(new Runnable() {
197 public void run() {
198 myStartupActivityPerformed = true;
202 myHandler = new BatchUpdateListener() {
203 public void onBatchUpdateStarted() {
204 myRootsChanged.levelUp();
205 myFileTypesChanged.levelUp();
208 public void onBatchUpdateFinished() {
209 myRootsChanged.levelDown();
210 myFileTypesChanged.levelDown();
215 public void registerRootsChangeUpdater(CacheUpdater updater) {
216 myRootsChangeUpdaters.add(updater);
219 public void unregisterRootsChangeUpdater(CacheUpdater updater) {
220 boolean removed = myRootsChangeUpdaters.remove(updater);
221 LOG.assertTrue(removed);
224 @Override
225 public void registerRefreshUpdater(CacheUpdater updater) {
226 myRefreshCacheUpdaters.add(updater);
229 @Override
230 public void unregisterRefreshUpdater(CacheUpdater updater) {
231 boolean removed = myRefreshCacheUpdaters.remove(updater);
232 LOG.assertTrue(removed);
235 public void multiCommit(ModifiableRootModel[] rootModels) {
236 ModuleRootManagerImpl.multiCommit(rootModels, ModuleManager.getInstance(myProject).getModifiableModel());
239 public void multiCommit(ModifiableModuleModel moduleModel, ModifiableRootModel[] rootModels) {
240 ModuleRootManagerImpl.multiCommit(rootModels, moduleModel);
243 public VirtualFilePointerListener getVirtualFilePointerListener() {
244 return myVirtualFilePointerListener;
247 @NotNull
248 public ProjectFileIndex getFileIndex() {
249 return myProjectFileIndex;
252 private final Map<ModuleRootListener, MessageBusConnection> myListenerAdapters = new HashMap<ModuleRootListener, MessageBusConnection>();
254 public void addModuleRootListener(final ModuleRootListener listener) {
255 final MessageBusConnection connection = myProject.getMessageBus().connect();
256 myListenerAdapters.put(listener, connection);
257 connection.subscribe(ProjectTopics.PROJECT_ROOTS, listener);
260 public void addModuleRootListener(ModuleRootListener listener, Disposable parentDisposable) {
261 final MessageBusConnection connection = myProject.getMessageBus().connect(parentDisposable);
262 connection.subscribe(ProjectTopics.PROJECT_ROOTS, listener);
265 public void removeModuleRootListener(ModuleRootListener listener) {
266 final MessageBusConnection connection = myListenerAdapters.remove(listener);
267 if (connection != null) {
268 connection.disconnect();
272 @NotNull
273 public VirtualFile[] getContentRoots() {
274 ArrayList<VirtualFile> result = new ArrayList<VirtualFile>();
275 final Module[] modules = getModuleManager().getModules();
276 for (Module module : modules) {
277 final VirtualFile[] contentRoots = ModuleRootManager.getInstance(module).getContentRoots();
278 result.addAll(Arrays.asList(contentRoots));
280 return VfsUtil.toVirtualFileArray(result);
283 public VirtualFile[] getContentSourceRoots() {
284 ArrayList<VirtualFile> result = new ArrayList<VirtualFile>();
285 final Module[] modules = getModuleManager().getModules();
286 for (Module module : modules) {
287 final VirtualFile[] sourceRoots = ModuleRootManager.getInstance(module).getSourceRoots();
288 result.addAll(Arrays.asList(sourceRoots));
290 return VfsUtil.toVirtualFileArray(result);
293 public VirtualFile[] getFilesFromAllModules(OrderRootType type) {
294 List<VirtualFile> result = new ArrayList<VirtualFile>();
295 final Module[] modules = getModuleManager().getSortedModules();
296 for (Module module : modules) {
297 final VirtualFile[] files = ModuleRootManager.getInstance(module).getFiles(type);
298 result.addAll(Arrays.asList(files));
300 return VfsUtil.toVirtualFileArray(result);
303 public VirtualFile[] getContentRootsFromAllModules() {
304 List<VirtualFile> result = new ArrayList<VirtualFile>();
305 final Module[] modules = getModuleManager().getSortedModules();
306 for (Module module : modules) {
307 final VirtualFile[] files = ModuleRootManager.getInstance(module).getContentRoots();
308 result.addAll(Arrays.asList(files));
310 result.add(myProject.getBaseDir());
311 return VfsUtil.toVirtualFileArray(result);
314 public Sdk getProjectJdk() {
315 if (myProjectJdkName != null) {
316 return ProjectJdkTable.getInstance().findJdk(myProjectJdkName, myProjectJdkType);
318 else {
319 return null;
323 public String getProjectJdkName() {
324 return myProjectJdkName;
327 public void setProjectJdk(Sdk projectJdk) {
328 ApplicationManager.getApplication().assertWriteAccessAllowed();
329 if (projectJdk != null) {
330 myProjectJdkName = projectJdk.getName();
331 myProjectJdkType = projectJdk.getSdkType().getName();
333 else {
334 myProjectJdkName = null;
335 myProjectJdkType = null;
337 mergeRootsChangesDuring(new Runnable() {
338 public void run() {
339 myProjectJdkEventDispatcher.getMulticaster().projectJdkChanged();
344 public void setProjectJdkName(String name) {
345 ApplicationManager.getApplication().assertWriteAccessAllowed();
346 myProjectJdkName = name;
348 mergeRootsChangesDuring(new Runnable() {
349 public void run() {
350 myProjectJdkEventDispatcher.getMulticaster().projectJdkChanged();
355 public void addProjectJdkListener(ProjectJdkListener listener) {
356 myProjectJdkEventDispatcher.addListener(listener);
359 public void removeProjectJdkListener(ProjectJdkListener listener) {
360 myProjectJdkEventDispatcher.removeListener(listener);
363 public void projectOpened() {
364 addRootsToWatch();
365 myApplicationListener = new ApplicationListener();
366 ApplicationManager.getApplication().addApplicationListener(myApplicationListener);
369 public void projectClosed() {
370 LocalFileSystem.getInstance().removeWatchedRoots(myRootsToWatch);
371 ApplicationManager.getApplication().removeApplicationListener(myApplicationListener);
374 @NotNull
375 public String getComponentName() {
376 return "ProjectRootManager";
379 public void initComponent() {
380 myConnection.subscribe(BatchUpdateListener.TOPIC, myHandler);
383 public void disposeComponent() {
384 VirtualFileManager.getInstance().removeVirtualFileManagerListener(myVFSListener);
385 if (myJdkTableMultilistener != null) {
386 myJdkTableMultilistener.uninstallListner(false);
387 myJdkTableMultilistener = null;
389 myConnection.disconnect();
392 public void readExternal(Element element) throws InvalidDataException {
393 for (ProjectExtension extension : Extensions.getExtensions(ProjectExtension.EP_NAME, myProject)) {
394 extension.readExternal(element);
396 myProjectJdkName = element.getAttributeValue(PROJECT_JDK_NAME_ATTR);
397 myProjectJdkType = element.getAttributeValue(PROJECT_JDK_TYPE_ATTR);
400 public void writeExternal(Element element) throws WriteExternalException {
401 element.setAttribute(ATTRIBUTE_VERSION, "2");
402 for (ProjectExtension extension : Extensions.getExtensions(ProjectExtension.EP_NAME, myProject)) {
403 extension.writeExternal(element);
405 if (myProjectJdkName != null) {
406 element.setAttribute(PROJECT_JDK_NAME_ATTR, myProjectJdkName);
408 if (myProjectJdkType != null) {
409 element.setAttribute(PROJECT_JDK_TYPE_ATTR, myProjectJdkType);
413 private boolean myMergedCallStarted = false;
414 private boolean myMergedCallHasRootChange = false;
415 private int myRootsChangesDepth = 0;
417 public void mergeRootsChangesDuring(@NotNull Runnable runnable) {
418 if (getBatchSession(false).myBatchLevel == 0 && !myMergedCallStarted) {
419 LOG.assertTrue(myRootsChangesDepth == 0,
420 "Merged rootsChanged not allowed inside rootsChanged, rootsChanged level == " + myRootsChangesDepth);
421 myMergedCallStarted = true;
422 myMergedCallHasRootChange = false;
423 try {
424 runnable.run();
426 finally {
427 if (myMergedCallHasRootChange) {
428 LOG.assertTrue(myRootsChangesDepth == 1, "myMergedCallDepth = " + myRootsChangesDepth);
429 getBatchSession(false).rootsChanged(true);
431 myMergedCallStarted = false;
432 myMergedCallHasRootChange = false;
435 else {
436 runnable.run();
440 private void clearScopesCaches() {
441 clearScopesCachesForModules();
442 myJdkScopes.clear();
443 myLibraryScopes.clear();
446 public void clearScopesCachesForModules() {
447 Module[] modules = ModuleManager.getInstance(myProject).getModules();
448 for (Module module : modules) {
449 ((ModuleRootManagerImpl)ModuleRootManager.getInstance(module)).dropCaches();
450 ((ModuleImpl)module).clearScopesCache();
454 public void beforeRootsChange(boolean filetypes) {
455 if (myProject.isDisposed()) return;
456 getBatchSession(filetypes).beforeRootsChanged();
459 public void makeRootsChange(@NotNull Runnable runnable, boolean filetypes, boolean fireEvents) {
460 if (myProject.isDisposed()) return;
461 BatchSession session = getBatchSession(filetypes);
462 if (fireEvents) session.beforeRootsChanged();
463 try {
464 runnable.run();
466 finally {
467 if (fireEvents) session.rootsChanged(false);
471 public void rootsChanged(boolean filetypes) {
472 getBatchSession(filetypes).rootsChanged(false);
475 private BatchSession getBatchSession(final boolean filetypes) {
476 return filetypes ? myFileTypesChanged : myRootsChanged;
479 private boolean isFiringEvent = false;
481 private boolean fireBeforeRootsChanged(boolean filetypes) {
482 ApplicationManager.getApplication().assertWriteAccessAllowed();
484 LOG.assertTrue(!isFiringEvent, "Do not use API that changes roots from roots events. Try using invoke later or something else.");
486 if (myMergedCallStarted) {
487 LOG.assertTrue(!filetypes, "Filetypes change is not supported inside merged call");
490 if (myRootsChangesDepth++ == 0) {
491 if (myMergedCallStarted) {
492 myMergedCallHasRootChange = true;
493 myRootsChangesDepth++; // blocks all firing until finishRootsChangedOnDemand
495 isFiringEvent = true;
496 try {
497 myProject.getMessageBus()
498 .syncPublisher(ProjectTopics.PROJECT_ROOTS)
499 .beforeRootsChange(new ModuleRootEventImpl(myProject, filetypes));
501 finally {
502 isFiringEvent= false;
504 return true;
507 return false;
510 private boolean fireRootsChanged(boolean filetypes) {
511 if (myProject.isDisposed()) return false;
513 ApplicationManager.getApplication().assertWriteAccessAllowed();
515 LOG.assertTrue(!isFiringEvent, "Do not use API that changes roots from roots events. Try using invoke later or something else.");
517 if (myMergedCallStarted) {
518 LOG.assertTrue(!filetypes, "Filetypes change is not supported inside merged call");
521 myRootsChangesDepth--;
522 if (myRootsChangesDepth > 0) return false;
524 clearScopesCaches();
526 myModificationCount++;
528 isFiringEvent = true;
529 try {
530 myProject.getMessageBus()
531 .syncPublisher(ProjectTopics.PROJECT_ROOTS)
532 .rootsChanged(new ModuleRootEventImpl(myProject, filetypes));
534 finally {
535 isFiringEvent = false;
538 doSynchronizeRoots();
540 addRootsToWatch();
542 return true;
545 public Project getProject() {
546 return myProject;
549 private static class LibrariesOnlyScope extends GlobalSearchScope {
550 private final GlobalSearchScope myOriginal;
552 public LibrariesOnlyScope(final GlobalSearchScope original) {
553 super(original.getProject());
554 myOriginal = original;
557 public boolean contains(VirtualFile file) {
558 return myOriginal.contains(file);
561 public int compare(VirtualFile file1, VirtualFile file2) {
562 return myOriginal.compare(file1, file2);
565 public boolean isSearchInModuleContent(@NotNull Module aModule) {
566 return false;
569 public boolean isSearchInLibraries() {
570 return true;
574 public GlobalSearchScope getScopeForLibraryUsedIn(List<Module> modulesLibraryIsUsedIn) {
575 GlobalSearchScope scope = myLibraryScopes.get(modulesLibraryIsUsedIn);
576 if (scope == null) {
577 if (!modulesLibraryIsUsedIn.isEmpty()) {
578 scope = new LibraryRuntimeClasspathScope(myProject, modulesLibraryIsUsedIn);
580 else {
581 scope = new LibrariesOnlyScope(GlobalSearchScope.allScope(myProject));
583 myLibraryScopes.put(modulesLibraryIsUsedIn, scope);
585 return scope;
588 public GlobalSearchScope getScopeForJdk(final JdkOrderEntry jdkOrderEntry) {
589 final String jdk = jdkOrderEntry.getJdkName();
590 if (jdk == null) return GlobalSearchScope.allScope(myProject);
591 GlobalSearchScope scope = myJdkScopes.get(jdk);
592 if (scope == null) {
593 scope = new JdkScope(myProject, jdkOrderEntry);
594 myJdkScopes.put(jdk, scope);
596 return scope;
599 private void doSynchronizeRoots() {
600 if (!myStartupActivityPerformed) return;
601 DumbServiceImpl.getInstance(myProject).queueCacheUpdate(myRootsChangeUpdaters);
604 private void doUpdateOnRefresh() {
605 if (!myStartupActivityPerformed) return;
606 DumbServiceImpl.getInstance(myProject).queueCacheUpdate(myRefreshCacheUpdaters);
609 private void addRootsToWatch() {
610 if (myProject.isDefault()) {
611 return;
613 final Set<String> rootPaths = new HashSet<String>();
614 Module[] modules = ModuleManager.getInstance(myProject).getModules();
615 for (Module module : modules) {
616 final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
617 final String[] contentRootUrls = moduleRootManager.getContentRootUrls();
618 for (String url : contentRootUrls) {
619 rootPaths.add(extractLocalPath(url));
621 rootPaths.add(module.getModuleFilePath());
624 for (WatchedRootsProvider extension : Extensions.getExtensions(WatchedRootsProvider.EP_NAME, myProject)) {
625 rootPaths.addAll(extension.getRootsToWatch());
628 final String projectFile = myProject.getStateStore().getProjectFilePath();
629 rootPaths.add(projectFile);
630 final VirtualFile baseDir = myProject.getBaseDir();
631 if (baseDir != null) {
632 rootPaths.add(baseDir.getPath());
634 // No need to add workspace file separately since they're definetely on same directory with ipr.
636 for (Module module : modules) {
637 final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
638 final OrderEntry[] orderEntries = moduleRootManager.getOrderEntries();
639 for (OrderEntry entry : orderEntries) {
640 if (entry instanceof LibraryOrderEntry) {
641 final Library library = ((LibraryOrderEntry)entry).getLibrary();
642 for (OrderRootType orderRootType : OrderRootType.getAllTypes()) {
643 rootPaths.addAll(getRootsToTrack(library, orderRootType));
646 else if (entry instanceof JdkOrderEntry) {
647 for (OrderRootType orderRootType : OrderRootType.getAllTypes()) {
648 rootPaths.addAll(getRootsToTrack(entry, orderRootType));
654 for (Module module : modules) {
655 final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
656 final String explodedDirectory = moduleRootManager.getExplodedDirectoryUrl();
657 if (explodedDirectory != null) {
658 rootPaths.add(extractLocalPath(explodedDirectory));
662 final Set<LocalFileSystem.WatchRequest> newRootsToWatch = LocalFileSystem.getInstance().addRootsToWatch(rootPaths, true);
664 //remove old requests after adding new ones, helps avoiding unnecessary synchronizations
665 LocalFileSystem.getInstance().removeWatchedRoots(myRootsToWatch);
666 myRootsToWatch = newRootsToWatch;
669 private static Collection<String> getRootsToTrack(final Library library, final OrderRootType rootType) {
670 return library != null ? getRootsToTrack(library.getUrls(rootType)) : Collections.<String>emptyList();
673 private static Collection<String> getRootsToTrack(final OrderEntry library, final OrderRootType rootType) {
674 return library != null ? getRootsToTrack(library.getUrls(rootType)) : Collections.<String>emptyList();
677 private static List<String> getRootsToTrack(final String[] urls) {
678 List<String> result = new ArrayList<String>();
679 for (String url : urls) {
680 if (url != null) {
681 String path = extractLocalPath(url);
682 result.add(path);
686 return result;
689 public static String extractLocalPath(final String url) {
690 final String path = VfsUtil.urlToPath(url);
691 final int jarSeparatorIndex = path.indexOf(JarFileSystem.JAR_SEPARATOR);
692 if (jarSeparatorIndex > 0) {
693 return path.substring(0, jarSeparatorIndex);
695 return path;
698 private ModuleManager getModuleManager() {
699 return ModuleManager.getInstance(myProject);
702 void addRootSetChangedListener(RootProvider.RootSetChangedListener rootSetChangedListener, final RootProvider provider) {
703 RootSetChangedMulticaster multicaster = myRegisteredRootProviderListeners.get(provider);
704 if (multicaster == null) {
705 multicaster = new RootSetChangedMulticaster(provider);
707 multicaster.addListener(rootSetChangedListener);
710 void removeRootSetChangedListener(RootProvider.RootSetChangedListener rootSetChangedListener, final RootProvider provider) {
711 RootSetChangedMulticaster multicaster = myRegisteredRootProviderListeners.get(provider);
712 if (multicaster != null) {
713 multicaster.removeListener(rootSetChangedListener);
717 private class MyVirtualFilePointerListener implements VirtualFilePointerListener {
718 public void beforeValidityChanged(VirtualFilePointer[] pointers) {
719 if (!myProject.isDisposed()) {
720 if (myInsideRefresh == 0) {
721 beforeRootsChange(false);
723 else if (!myPointerChangesDetected) {
724 //this is the first pointer changing validity
725 myPointerChangesDetected = true;
726 myProject.getMessageBus().syncPublisher(ProjectTopics.PROJECT_ROOTS).beforeRootsChange(new ModuleRootEventImpl(myProject, false));
731 public void validityChanged(VirtualFilePointer[] pointers) {
732 if (!myProject.isDisposed()) {
733 if (myInsideRefresh > 0) {
734 clearScopesCaches();
736 else {
737 rootsChanged(false);
743 private int myInsideRefresh = 0;
744 private boolean myPointerChangesDetected = false;
746 private class ApplicationListener extends ApplicationAdapter {
747 public void beforeWriteActionStart(Object action) {
748 myInsideRefresh++;
751 public void writeActionFinished(Object action) {
752 if (--myInsideRefresh == 0) {
753 if (myPointerChangesDetected) {
754 myPointerChangesDetected = false;
755 myProject.getMessageBus().syncPublisher(ProjectTopics.PROJECT_ROOTS).rootsChanged(new ModuleRootEventImpl(myProject, false));
757 doSynchronizeRoots();
759 addRootsToWatch();
765 void addListenerForTable(LibraryTable.Listener libraryListener,
766 final LibraryTable libraryTable) {
767 LibraryTableMultilistener multilistener = myLibraryTableMultilisteners.get(libraryTable);
768 if (multilistener == null) {
769 multilistener = new LibraryTableMultilistener(libraryTable);
771 multilistener.addListener(libraryListener);
774 void removeListenerForTable(LibraryTable.Listener libraryListener,
775 final LibraryTable libraryTable) {
776 LibraryTableMultilistener multilistener = myLibraryTableMultilisteners.get(libraryTable);
777 if (multilistener == null) {
778 multilistener = new LibraryTableMultilistener(libraryTable);
780 multilistener.removeListener(libraryListener);
783 private final Map<LibraryTable, LibraryTableMultilistener> myLibraryTableMultilisteners
784 = new HashMap<LibraryTable, LibraryTableMultilistener>();
786 private class LibraryTableMultilistener implements LibraryTable.Listener {
787 final Set<LibraryTable.Listener> myListeners = new HashSet<LibraryTable.Listener>();
788 private final LibraryTable myLibraryTable;
790 private LibraryTableMultilistener(LibraryTable libraryTable) {
791 myLibraryTable = libraryTable;
792 myLibraryTable.addListener(this);
793 myLibraryTableMultilisteners.put(myLibraryTable, this);
796 private void addListener(LibraryTable.Listener listener) {
797 myListeners.add(listener);
800 private void removeListener(LibraryTable.Listener listener) {
801 myListeners.remove(listener);
802 if (myListeners.isEmpty()) {
803 myLibraryTable.removeListener(this);
804 myLibraryTableMultilisteners.remove(myLibraryTable);
808 public void afterLibraryAdded(final Library newLibrary) {
809 mergeRootsChangesDuring(new Runnable() {
810 public void run() {
811 for (LibraryTable.Listener listener : myListeners) {
812 listener.afterLibraryAdded(newLibrary);
818 public void afterLibraryRenamed(final Library library) {
819 mergeRootsChangesDuring(new Runnable() {
820 public void run() {
821 for (LibraryTable.Listener listener : myListeners) {
822 listener.afterLibraryRenamed(library);
828 public void beforeLibraryRemoved(final Library library) {
829 mergeRootsChangesDuring(new Runnable() {
830 public void run() {
831 for (LibraryTable.Listener listener : myListeners) {
832 listener.beforeLibraryRemoved(library);
838 public void afterLibraryRemoved(final Library library) {
839 mergeRootsChangesDuring(new Runnable() {
840 public void run() {
841 for (LibraryTable.Listener listener : myListeners) {
842 listener.afterLibraryRemoved(library);
849 private JdkTableMultilistener myJdkTableMultilistener = null;
851 private class JdkTableMultilistener implements ProjectJdkTable.Listener {
852 final EventDispatcher<ProjectJdkTable.Listener> myDispatcher = EventDispatcher.create(ProjectJdkTable.Listener.class);
853 final Set<ProjectJdkTable.Listener> myListeners = new HashSet<ProjectJdkTable.Listener>();
855 private JdkTableMultilistener() {
856 ProjectJdkTable.getInstance().addListener(this);
859 private void addListener(ProjectJdkTable.Listener listener) {
860 myDispatcher.addListener(listener);
861 myListeners.add(listener);
864 private void removeListener(ProjectJdkTable.Listener listener) {
865 myDispatcher.removeListener(listener);
866 myListeners.remove(listener);
867 uninstallListner(true);
870 public void jdkAdded(final Sdk jdk) {
871 mergeRootsChangesDuring(new Runnable() {
872 public void run() {
873 myDispatcher.getMulticaster().jdkAdded(jdk);
878 public void jdkRemoved(final Sdk jdk) {
879 mergeRootsChangesDuring(new Runnable() {
880 public void run() {
881 myDispatcher.getMulticaster().jdkRemoved(jdk);
886 public void jdkNameChanged(final Sdk jdk, final String previousName) {
887 mergeRootsChangesDuring(new Runnable() {
888 public void run() {
889 myDispatcher.getMulticaster().jdkNameChanged(jdk, previousName);
892 String currentName = getProjectJdkName();
893 if (previousName != null && previousName.equals(currentName)) {
894 // if already had jdk name and that name was the name of the jdk just changed
895 myProjectJdkName = jdk.getName();
896 myProjectJdkType = jdk.getSdkType().getName();
900 public void uninstallListner(boolean soft) {
901 if (!soft || !myDispatcher.hasListeners()) {
902 ProjectJdkTable.getInstance().removeListener(this);
907 private final Map<RootProvider, RootSetChangedMulticaster> myRegisteredRootProviderListeners
908 = new HashMap<RootProvider, RootSetChangedMulticaster>();
910 void addJdkTableListener(ProjectJdkTable.Listener jdkTableListener) {
911 getJdkTableMultiListener().addListener(jdkTableListener);
914 private JdkTableMultilistener getJdkTableMultiListener() {
915 if (myJdkTableMultilistener == null) {
916 myJdkTableMultilistener = new JdkTableMultilistener();
918 return myJdkTableMultilistener;
921 void removeJdkTableListener(ProjectJdkTable.Listener jdkTableListener) {
922 if (myJdkTableMultilistener == null) return;
923 myJdkTableMultilistener.removeListener(jdkTableListener);
926 private class RootSetChangedMulticaster implements RootProvider.RootSetChangedListener {
927 private final EventDispatcher<RootProvider.RootSetChangedListener> myDispatcher = EventDispatcher.create(RootProvider.RootSetChangedListener.class);
928 private final RootProvider myProvider;
930 private RootSetChangedMulticaster(RootProvider provider) {
931 myProvider = provider;
932 provider.addRootSetChangedListener(this);
933 myRegisteredRootProviderListeners.put(myProvider, this);
936 private void addListener(RootProvider.RootSetChangedListener listener) {
937 myDispatcher.addListener(listener);
940 private void removeListener(RootProvider.RootSetChangedListener listener) {
941 myDispatcher.removeListener(listener);
942 if (!myDispatcher.hasListeners()) {
943 myProvider.removeRootSetChangedListener(this);
944 myRegisteredRootProviderListeners.remove(myProvider);
948 public void rootSetChanged(final RootProvider wrapper) {
949 LOG.assertTrue(myProvider.equals(wrapper));
950 Runnable runnable = new Runnable() {
951 public void run() {
952 myDispatcher.getMulticaster().rootSetChanged(wrapper);
955 mergeRootsChangesDuring(runnable);
959 public long getModificationCount() {
960 return myModificationCount;