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
;
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
;
105 int myBatchLevel
= 0;
106 boolean myChanged
= false;
108 final boolean myFileTypes
;
110 protected BatchSession(final boolean fileTypes
) {
111 myFileTypes
= fileTypes
;
115 if (myBatchLevel
== 0) {
121 public void levelDown() {
123 if (myChanged
&& myBatchLevel
== 0) {
133 boolean fireChange() {
134 return fireRootsChanged(myFileTypes
);
137 public void beforeRootsChanged() {
138 if (myBatchLevel
== 0 || !myChanged
) {
139 if (fireBeforeRootsChanged(myFileTypes
)) {
145 public void rootsChanged(boolean force
) {
146 if (myBatchLevel
== 0) {
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);
168 myRootsChangedEvents.push(new String(out.toByteArray()));
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
) {
187 myVFSListener
= new VirtualFileManagerAdapter() {
189 public void afterRefreshFinish(boolean asynchonous
) {
193 VirtualFileManager
.getInstance().addVirtualFileManagerListener(myVFSListener
);
195 myProjectFileIndex
= new ProjectFileIndexImpl(myProject
, directoryIndex
, fileTypeManager
);
196 startupManager
.registerStartupActivity(new Runnable() {
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
);
225 public void registerRefreshUpdater(CacheUpdater updater
) {
226 myRefreshCacheUpdaters
.add(updater
);
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
;
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();
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
);
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();
334 myProjectJdkName
= null;
335 myProjectJdkType
= null;
337 mergeRootsChangesDuring(new Runnable() {
339 myProjectJdkEventDispatcher
.getMulticaster().projectJdkChanged();
344 public void setProjectJdkName(String name
) {
345 ApplicationManager
.getApplication().assertWriteAccessAllowed();
346 myProjectJdkName
= name
;
348 mergeRootsChangesDuring(new Runnable() {
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() {
365 myApplicationListener
= new ApplicationListener();
366 ApplicationManager
.getApplication().addApplicationListener(myApplicationListener
);
369 public void projectClosed() {
370 LocalFileSystem
.getInstance().removeWatchedRoots(myRootsToWatch
);
371 ApplicationManager
.getApplication().removeApplicationListener(myApplicationListener
);
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;
427 if (myMergedCallHasRootChange
) {
428 LOG
.assertTrue(myRootsChangesDepth
== 1, "myMergedCallDepth = " + myRootsChangesDepth
);
429 getBatchSession(false).rootsChanged(true);
431 myMergedCallStarted
= false;
432 myMergedCallHasRootChange
= false;
440 private void clearScopesCaches() {
441 clearScopesCachesForModules();
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();
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;
497 myProject
.getMessageBus()
498 .syncPublisher(ProjectTopics
.PROJECT_ROOTS
)
499 .beforeRootsChange(new ModuleRootEventImpl(myProject
, filetypes
));
502 isFiringEvent
= 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;
526 myModificationCount
++;
528 isFiringEvent
= true;
530 myProject
.getMessageBus()
531 .syncPublisher(ProjectTopics
.PROJECT_ROOTS
)
532 .rootsChanged(new ModuleRootEventImpl(myProject
, filetypes
));
535 isFiringEvent
= false;
538 doSynchronizeRoots();
545 public Project
getProject() {
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
) {
569 public boolean isSearchInLibraries() {
574 public GlobalSearchScope
getScopeForLibraryUsedIn(List
<Module
> modulesLibraryIsUsedIn
) {
575 GlobalSearchScope scope
= myLibraryScopes
.get(modulesLibraryIsUsedIn
);
577 if (!modulesLibraryIsUsedIn
.isEmpty()) {
578 scope
= new LibraryRuntimeClasspathScope(myProject
, modulesLibraryIsUsedIn
);
581 scope
= new LibrariesOnlyScope(GlobalSearchScope
.allScope(myProject
));
583 myLibraryScopes
.put(modulesLibraryIsUsedIn
, 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
);
593 scope
= new JdkScope(myProject
, jdkOrderEntry
);
594 myJdkScopes
.put(jdk
, 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()) {
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
) {
681 String path
= extractLocalPath(url
);
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
);
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) {
743 private int myInsideRefresh
= 0;
744 private boolean myPointerChangesDetected
= false;
746 private class ApplicationListener
extends ApplicationAdapter
{
747 public void beforeWriteActionStart(Object action
) {
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();
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() {
811 for (LibraryTable
.Listener listener
: myListeners
) {
812 listener
.afterLibraryAdded(newLibrary
);
818 public void afterLibraryRenamed(final Library library
) {
819 mergeRootsChangesDuring(new Runnable() {
821 for (LibraryTable
.Listener listener
: myListeners
) {
822 listener
.afterLibraryRenamed(library
);
828 public void beforeLibraryRemoved(final Library library
) {
829 mergeRootsChangesDuring(new Runnable() {
831 for (LibraryTable
.Listener listener
: myListeners
) {
832 listener
.beforeLibraryRemoved(library
);
838 public void afterLibraryRemoved(final Library library
) {
839 mergeRootsChangesDuring(new Runnable() {
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() {
873 myDispatcher
.getMulticaster().jdkAdded(jdk
);
878 public void jdkRemoved(final Sdk jdk
) {
879 mergeRootsChangesDuring(new Runnable() {
881 myDispatcher
.getMulticaster().jdkRemoved(jdk
);
886 public void jdkNameChanged(final Sdk jdk
, final String previousName
) {
887 mergeRootsChangesDuring(new Runnable() {
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() {
952 myDispatcher
.getMulticaster().rootSetChanged(wrapper
);
955 mergeRootsChangesDuring(runnable
);
959 public long getModificationCount() {
960 return myModificationCount
;