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
.startup
.StartupManagerEx
;
22 import com
.intellij
.openapi
.components
.ProjectComponent
;
23 import com
.intellij
.openapi
.diagnostic
.Logger
;
24 import com
.intellij
.openapi
.extensions
.Extensions
;
25 import com
.intellij
.openapi
.fileTypes
.FileTypeEvent
;
26 import com
.intellij
.openapi
.fileTypes
.FileTypeListener
;
27 import com
.intellij
.openapi
.fileTypes
.FileTypeManager
;
28 import com
.intellij
.openapi
.module
.Module
;
29 import com
.intellij
.openapi
.module
.ModuleManager
;
30 import com
.intellij
.openapi
.progress
.EmptyProgressIndicator
;
31 import com
.intellij
.openapi
.progress
.ProgressIndicator
;
32 import com
.intellij
.openapi
.progress
.ProgressManager
;
33 import com
.intellij
.openapi
.project
.Project
;
34 import com
.intellij
.openapi
.project
.ProjectBundle
;
35 import com
.intellij
.openapi
.roots
.*;
36 import com
.intellij
.openapi
.startup
.StartupManager
;
37 import com
.intellij
.openapi
.util
.Condition
;
38 import com
.intellij
.openapi
.util
.Key
;
39 import com
.intellij
.openapi
.vfs
.*;
40 import com
.intellij
.openapi
.vfs
.impl
.BulkVirtualFileListenerAdapter
;
41 import com
.intellij
.openapi
.vfs
.newvfs
.NewVirtualFile
;
42 import com
.intellij
.psi
.impl
.PsiManagerConfiguration
;
43 import com
.intellij
.util
.*;
44 import com
.intellij
.util
.containers
.*;
45 import com
.intellij
.util
.containers
.HashMap
;
46 import com
.intellij
.util
.messages
.MessageBusConnection
;
47 import gnu
.trove
.THashMap
;
48 import org
.jetbrains
.annotations
.NotNull
;
49 import org
.jetbrains
.annotations
.Nullable
;
50 import org
.jetbrains
.annotations
.TestOnly
;
53 import java
.util
.HashSet
;
55 public class DirectoryIndexImpl
extends DirectoryIndex
implements ProjectComponent
{
56 private static final Logger LOG
= Logger
.getInstance("#com.intellij.openapi.roots.impl.DirectoryIndexImpl");
58 private final Project myProject
;
60 private volatile boolean myInitialized
= false;
61 private volatile boolean myDisposed
= false;
63 private final boolean myIsLasyMode
;
65 private Map
<VirtualFile
, Set
<String
>> myExcludeRootsMap
;
66 private Set
<VirtualFile
> myProjectExcludeRoots
;
67 private Map
<VirtualFile
, DirectoryInfo
> myDirToInfoMap
= new ConcurrentHashMap
<VirtualFile
, DirectoryInfo
>();
68 private Map
<String
, VirtualFile
[]> myPackageNameToDirsMap
= new ConcurrentHashMap
<String
, VirtualFile
[]>();
70 private final DirectoryIndexExcludePolicy
[] myExcludePolicies
;
71 private final MessageBusConnection myConnection
;
73 public DirectoryIndexImpl(Project project
, PsiManagerConfiguration psiManagerConfiguration
, StartupManager startupManager
) {
75 myConnection
= project
.getMessageBus().connect(project
);
77 myIsLasyMode
= !psiManagerConfiguration
.REPOSITORY_ENABLED
;
78 ((StartupManagerEx
)startupManager
).registerPreStartupActivity(new Runnable() {
83 myExcludePolicies
= Extensions
.getExtensions(DirectoryIndexExcludePolicy
.EP_NAME
, myProject
);
87 public String
getComponentName() {
88 return "DirectoryIndex";
91 public void initComponent() {
94 public void disposeComponent() {
98 public void projectOpened() {
101 public void projectClosed() {
105 public void checkConsistency() {
106 doCheckConsistency(false);
107 doCheckConsistency(true);
111 private void doCheckConsistency(boolean reverseAllSets
) {
112 assert myInitialized
;
115 Map
<VirtualFile
, DirectoryInfo
> oldDirToInfoMap
= myDirToInfoMap
;
116 myDirToInfoMap
= new THashMap
<VirtualFile
, DirectoryInfo
>();
118 Map
<String
, VirtualFile
[]> oldPackageNameToDirsMap
= myPackageNameToDirsMap
;
119 myPackageNameToDirsMap
= new THashMap
<String
, VirtualFile
[]>();
121 doInitialize(reverseAllSets
, null);
124 Map
<VirtualFile
, DirectoryInfo
> newDirToInfoMap
= myDirToInfoMap
;
125 Map
<String
, VirtualFile
[]> newPackageNameToDirsMap
= myPackageNameToDirsMap
;
126 myDirToInfoMap
= oldDirToInfoMap
;
127 myPackageNameToDirsMap
= oldPackageNameToDirsMap
;
129 Set
<VirtualFile
> allDirsSet
= newDirToInfoMap
.keySet();
130 for (VirtualFile dir
: allDirsSet
) {
131 getInfoForDirectory(dir
);
134 myDirToInfoMap
= newDirToInfoMap
;
135 myPackageNameToDirsMap
= newPackageNameToDirsMap
;
138 Set
<VirtualFile
> keySet
= myDirToInfoMap
.keySet();
139 assert keySet
.size() == oldDirToInfoMap
.keySet().size();
140 for (VirtualFile file
: keySet
) {
141 DirectoryInfo info1
= myDirToInfoMap
.get(file
);
142 DirectoryInfo info2
= oldDirToInfoMap
.get(file
);
143 assert info1
.equals(info2
);
146 assert myPackageNameToDirsMap
.keySet().size() == oldPackageNameToDirsMap
.keySet().size();
147 for (Map
.Entry
<String
, VirtualFile
[]> entry
: myPackageNameToDirsMap
.entrySet()) {
148 String packageName
= entry
.getKey();
149 VirtualFile
[] dirs
= entry
.getValue();
150 VirtualFile
[] dirs1
= oldPackageNameToDirsMap
.get(packageName
);
152 HashSet
<VirtualFile
> set1
= new HashSet
<VirtualFile
>();
153 set1
.addAll(Arrays
.asList(dirs
));
154 HashSet
<VirtualFile
> set2
= new HashSet
<VirtualFile
>();
155 set2
.addAll(Arrays
.asList(dirs1
));
156 assert set1
.equals(set2
);
160 public boolean isInitialized() {
161 return myInitialized
;
164 public void initialize() {
166 LOG
.error("Directory index is already initialized.");
171 LOG
.error("Directory index is aleady disposed for this project");
175 myInitialized
= true;
177 subscribeToFileChanges();
181 private void subscribeToFileChanges() {
182 myConnection
.subscribe(AppTopics
.FILE_TYPES
, new FileTypeListener() {
183 public void beforeFileTypesChanged(FileTypeEvent event
) {
186 public void fileTypesChanged(FileTypeEvent event
) {
191 myConnection
.subscribe(ProjectTopics
.PROJECT_ROOTS
, new ModuleRootListener() {
192 public void beforeRootsChange(ModuleRootEvent event
) {
195 public void rootsChanged(ModuleRootEvent event
) {
200 myConnection
.subscribe(VirtualFileManager
.VFS_CHANGES
, new BulkVirtualFileListenerAdapter(new MyVirtualFileListener()));
203 private void doInitialize() {
208 doInitialize(false, null);
212 private void doInitialize(boolean reverseAllSets
/* for testing order independence*/, VirtualFile forDir
/* in LAZY_MODE only*/) {
213 ProgressIndicator progress
= ProgressManager
.getInstance().getProgressIndicator();
214 if (progress
== null) progress
= new EmptyProgressIndicator();
216 progress
.pushState();
218 progress
.checkCanceled();
219 progress
.setText(ProjectBundle
.message("project.index.scanning.files.progress"));
221 if (forDir
== null) cleapAllMaps();
222 else createMapsFor(forDir
);
224 Module
[] modules
= ModuleManager
.getInstance(myProject
).getModules();
225 if (reverseAllSets
) modules
= ArrayUtil
.reverseArray(modules
);
227 initExcludedDirMap(modules
, progress
);
229 for (Module module
: modules
) {
230 initModuleContents(module
, forDir
, reverseAllSets
, progress
);
231 initModuleSources(module
, forDir
, reverseAllSets
, progress
);
232 initLibrarySources(module
, forDir
, progress
);
233 initLibraryClasses(module
, forDir
, progress
);
236 progress
.checkCanceled();
237 progress
.setText2("");
239 for (Module module
: modules
) {
240 initOrderEntries(module
, forDir
);
246 private void cleapAllMaps() {
247 myDirToInfoMap
.clear();
248 myPackageNameToDirsMap
.clear();
251 private void createMapsFor(VirtualFile forDir
) {
252 // clear map for all ancestors to not interfer with previous results
253 VirtualFile dir
= forDir
;
255 myDirToInfoMap
.remove(dir
);
256 dir
= dir
.getParent();
261 private void initExcludedDirMap(Module
[] modules
, ProgressIndicator progress
) {
262 progress
.checkCanceled();
263 progress
.setText2(ProjectBundle
.message("project.index.building.exclude.roots.progress"));
265 // exclude roots should be merged to prevent including excluded dirs of an inner module into the outer
266 // exclude root should exclude from its content root and all outer content roots
267 Map
<VirtualFile
, Set
<String
>> result
= new THashMap
<VirtualFile
, Set
<String
>>();
268 Set
<VirtualFile
> projectExcludeRoots
= new HashSet
<VirtualFile
>();
270 for (Module module
: modules
) {
271 for (ContentEntry contentEntry
: getContentEntries(module
)) {
272 VirtualFile contentRoot
= contentEntry
.getFile();
273 if (contentRoot
== null) continue;
275 ExcludeFolder
[] excludeRoots
= contentEntry
.getExcludeFolders();
276 for (ExcludeFolder excludeRoot
: excludeRoots
) {
277 // Output paths should be excluded (if marked as such) regardless if they're under corresponding module's content root
278 if (excludeRoot
.getFile() != null) {
279 if (!contentRoot
.getUrl().startsWith(excludeRoot
.getUrl())) {
280 if (isExcludeRootForModule(module
, excludeRoot
.getFile())) {
281 putForFileAndAllAncestors(result
, excludeRoot
.getFile(), excludeRoot
.getUrl());
286 putForFileAndAllAncestors(result
, contentRoot
, excludeRoot
.getUrl());
291 for(DirectoryIndexExcludePolicy policy
: myExcludePolicies
) {
292 for(VirtualFile file
: policy
.getExcludeRootsForProject()) {
293 putForFileAndAllAncestors(result
, file
, file
.getUrl());
294 projectExcludeRoots
.add(file
);
298 myExcludeRootsMap
= result
;
299 myProjectExcludeRoots
= projectExcludeRoots
;
302 private static void putForFileAndAllAncestors(Map
<VirtualFile
, Set
<String
>> map
, VirtualFile file
, String value
) {
304 Set
<String
> set
= map
.get(file
);
306 set
= new HashSet
<String
>();
311 file
= file
.getParent();
312 if (file
== null) break;
316 private boolean isExcludeRootForModule(Module module
, VirtualFile excludeRoot
) {
317 for(DirectoryIndexExcludePolicy policy
: myExcludePolicies
) {
318 if (policy
.isExcludeRootForModule(module
, excludeRoot
)) return true;
323 private static ContentEntry
[] getContentEntries(Module module
) {
324 return ModuleRootManager
.getInstance(module
).getContentEntries();
327 private static OrderEntry
[] getOrderEntries(Module module
) {
328 return ModuleRootManager
.getInstance(module
).getOrderEntries();
331 private void initModuleContents(Module module
,
333 boolean reverseAllSets
,
334 ProgressIndicator progress
) {
335 progress
.checkCanceled();
336 progress
.setText2(ProjectBundle
.message("project.index.processing.module.content.progress", module
.getName()));
338 ModuleRootManager rootManager
= ModuleRootManager
.getInstance(module
);
339 VirtualFile
[] contentRoots
= rootManager
.getContentRoots();
340 if (reverseAllSets
) {
341 contentRoots
= ArrayUtil
.reverseArray(contentRoots
);
344 for (final VirtualFile contentRoot
: contentRoots
) {
345 fillMapWithModuleContent(contentRoot
, module
, contentRoot
, forDir
);
349 private void fillMapWithModuleContent(VirtualFile dir
,
351 VirtualFile contentRoot
,
352 VirtualFile forDir
) {
353 if (isExcluded(contentRoot
, dir
)) return;
354 if (isIgnored(dir
)) return;
356 if (forDir
!= null) {
357 if (!VfsUtil
.isAncestor(dir
, forDir
, false)) return;
360 DirectoryInfo info
= getOrCreateDirInfo(dir
);
362 if (info
.module
!= null) { // module contents overlap
363 DirectoryInfo parentInfo
= myDirToInfoMap
.get(dir
.getParent());
364 if (parentInfo
== null || !info
.module
.equals(parentInfo
.module
)) return; // content of another module is below this module's content
367 VirtualFile
[] children
= dir
.getChildren();
368 for (VirtualFile child
: children
) {
369 if (child
.isDirectory()) {
370 fillMapWithModuleContent(child
, module
, contentRoot
, forDir
);
374 // important to change module AFTER processing children - to handle overlapping modules
375 info
.module
= module
;
376 info
.contentRoot
= contentRoot
;
379 private boolean isExcluded(VirtualFile root
, VirtualFile dir
) {
380 Set
<String
> excludes
= myExcludeRootsMap
.get(root
);
381 return excludes
!= null && excludes
.contains(dir
.getUrl());
384 private void initModuleSources(Module module
,
386 boolean reverseAllSets
,
387 ProgressIndicator progress
) {
388 progress
.checkCanceled();
389 progress
.setText2(ProjectBundle
.message("project.index.processing.module.sources.progress", module
.getName()));
391 ContentEntry
[] contentEntries
= getContentEntries(module
);
393 if (reverseAllSets
) {
394 contentEntries
= ArrayUtil
.reverseArray(contentEntries
);
397 for (ContentEntry contentEntry
: contentEntries
) {
398 SourceFolder
[] sourceFolders
= contentEntry
.getSourceFolders();
399 if (reverseAllSets
) {
400 sourceFolders
= ArrayUtil
.reverseArray(sourceFolders
);
402 for (SourceFolder sourceFolder
: sourceFolders
) {
403 VirtualFile dir
= sourceFolder
.getFile();
405 fillMapWithModuleSource(dir
, module
, sourceFolder
.getPackagePrefix(), dir
, sourceFolder
.isTestSource(), forDir
);
411 private void fillMapWithModuleSource(VirtualFile dir
,
414 VirtualFile sourceRoot
,
415 boolean isTestSource
,
416 VirtualFile forDir
) {
417 DirectoryInfo info
= myDirToInfoMap
.get(dir
);
418 if (info
== null) return;
419 if (!module
.equals(info
.module
)) return;
421 if (forDir
!= null) {
422 if (!VfsUtil
.isAncestor(dir
, forDir
, false)) return;
425 if (info
.isInModuleSource
) { // module sources overlap
426 if (info
.packageName
!= null && info
.packageName
.length() == 0) return; // another source root starts here
429 info
.isInModuleSource
= true;
430 info
.isTestSource
= isTestSource
;
431 info
.sourceRoot
= sourceRoot
;
432 setPackageName(dir
, info
, packageName
);
434 VirtualFile
[] children
= dir
.getChildren();
435 for (VirtualFile child
: children
) {
436 if (child
.isDirectory()) {
437 String childPackageName
= getPackageNameForSubdir(packageName
, child
.getName());
438 fillMapWithModuleSource(child
, module
, childPackageName
, sourceRoot
, isTestSource
, forDir
);
443 private void initLibrarySources(Module module
, VirtualFile forDir
, ProgressIndicator progress
) {
444 progress
.checkCanceled();
445 progress
.setText2(ProjectBundle
.message("project.index.processing.library.sources.progress", module
.getName()));
447 for (OrderEntry orderEntry
: getOrderEntries(module
)) {
448 boolean isLibrary
= orderEntry
instanceof LibraryOrderEntry
|| orderEntry
instanceof JdkOrderEntry
;
450 VirtualFile
[] sourceRoots
= orderEntry
.getFiles(OrderRootType
.SOURCES
);
451 for (final VirtualFile sourceRoot
: sourceRoots
) {
452 fillMapWithLibrarySources(sourceRoot
, "", sourceRoot
, forDir
);
458 private void fillMapWithLibrarySources(VirtualFile dir
,
460 VirtualFile sourceRoot
,
461 VirtualFile forDir
) {
462 if (isIgnored(dir
)) return;
464 if (forDir
!= null) {
465 if (!VfsUtil
.isAncestor(dir
, forDir
, false)) return;
468 DirectoryInfo info
= getOrCreateDirInfo(dir
);
470 if (info
.isInLibrarySource
) { // library sources overlap
471 if (info
.packageName
!= null && info
.packageName
.length() == 0) return; // another library source root starts here
474 info
.isInModuleSource
= false;
475 info
.isInLibrarySource
= true;
476 info
.sourceRoot
= sourceRoot
;
477 setPackageName(dir
, info
, packageName
);
479 VirtualFile
[] children
= dir
.getChildren();
480 for (VirtualFile child
: children
) {
481 if (child
.isDirectory()) {
482 String childPackageName
= getPackageNameForSubdir(packageName
, child
.getName());
483 fillMapWithLibrarySources(child
, childPackageName
, sourceRoot
, forDir
);
488 private void initLibraryClasses(Module module
, VirtualFile forDir
, ProgressIndicator progress
) {
489 progress
.checkCanceled();
490 progress
.setText2(ProjectBundle
.message("project.index.processing.library.classes.progress", module
.getName()));
492 for (OrderEntry orderEntry
: getOrderEntries(module
)) {
493 boolean isLibrary
= orderEntry
instanceof LibraryOrderEntry
|| orderEntry
instanceof JdkOrderEntry
;
495 VirtualFile
[] classRoots
= orderEntry
.getFiles(OrderRootType
.CLASSES
);
496 for (final VirtualFile classRoot
: classRoots
) {
497 fillMapWithLibraryClasses(classRoot
, "", classRoot
, forDir
);
503 private void fillMapWithLibraryClasses(VirtualFile dir
, String packageName
, VirtualFile classRoot
, VirtualFile forDir
) {
504 if (isIgnored(dir
)) return;
506 if (forDir
!= null) {
507 if (!VfsUtil
.isAncestor(dir
, forDir
, false)) return;
510 DirectoryInfo info
= getOrCreateDirInfo(dir
);
512 if (info
.libraryClassRoot
!= null) { // library classes overlap
513 if (info
.packageName
!= null && info
.packageName
.length() == 0) return; // another library root starts here
516 info
.libraryClassRoot
= classRoot
;
518 if (!info
.isInModuleSource
&& !info
.isInLibrarySource
) {
519 setPackageName(dir
, info
, packageName
);
522 VirtualFile
[] children
= dir
.getChildren();
523 for (VirtualFile child
: children
) {
524 if (child
.isDirectory()) {
525 String childPackageName
= getPackageNameForSubdir(packageName
, child
.getName());
526 fillMapWithLibraryClasses(child
, childPackageName
, classRoot
, forDir
);
531 private void initOrderEntries(Module module
, VirtualFile forDir
) {
532 Map
<VirtualFile
, List
<OrderEntry
>> depEntries
= new HashMap
<VirtualFile
, List
<OrderEntry
>>();
533 Map
<VirtualFile
, List
<OrderEntry
>> libClassRootEntries
= new HashMap
<VirtualFile
, List
<OrderEntry
>>();
534 Map
<VirtualFile
, List
<OrderEntry
>> libSourceRootEntries
= new HashMap
<VirtualFile
, List
<OrderEntry
>>();
536 for (OrderEntry orderEntry
: getOrderEntries(module
)) {
537 if (orderEntry
instanceof ModuleOrderEntry
) {
538 VirtualFile
[] importedClassRoots
= orderEntry
.getFiles(OrderRootType
.COMPILATION_CLASSES
);
539 for (VirtualFile importedClassRoot
: importedClassRoots
) {
540 addEntryToMap(importedClassRoot
, orderEntry
, depEntries
);
542 VirtualFile
[] sourceRoots
= orderEntry
.getFiles(OrderRootType
.SOURCES
);
543 for (VirtualFile sourceRoot
: sourceRoots
) {
544 addEntryToMap(sourceRoot
, orderEntry
, depEntries
);
547 else if (orderEntry
instanceof ModuleSourceOrderEntry
) {
548 List
<OrderEntry
> oneEntryList
= Arrays
.asList(orderEntry
);
549 Module entryModule
= orderEntry
.getOwnerModule();
551 VirtualFile
[] sourceRoots
= orderEntry
.getFiles(OrderRootType
.SOURCES
);
552 for (VirtualFile sourceRoot
: sourceRoots
) {
553 fillMapWithOrderEntries(sourceRoot
, oneEntryList
, entryModule
, null, null, forDir
, null, null);
556 else if (orderEntry
instanceof LibraryOrderEntry
|| orderEntry
instanceof JdkOrderEntry
) {
557 VirtualFile
[] classRoots
= orderEntry
.getFiles(OrderRootType
.CLASSES
);
558 for (VirtualFile classRoot
: classRoots
) {
559 addEntryToMap(classRoot
, orderEntry
, libClassRootEntries
);
561 VirtualFile
[] sourceRoots
= orderEntry
.getFiles(OrderRootType
.SOURCES
);
562 for (VirtualFile sourceRoot
: sourceRoots
) {
563 addEntryToMap(sourceRoot
, orderEntry
, libSourceRootEntries
);
568 for (Map
.Entry
<VirtualFile
, List
<OrderEntry
>> mapEntry
: depEntries
.entrySet()) {
569 final VirtualFile vRoot
= mapEntry
.getKey();
570 final List
<OrderEntry
> entries
= mapEntry
.getValue();
571 fillMapWithOrderEntries(vRoot
, entries
, null, null, null, forDir
, null, null);
574 for (Map
.Entry
<VirtualFile
, List
<OrderEntry
>> mapEntry
: libClassRootEntries
.entrySet()) {
575 final VirtualFile vRoot
= mapEntry
.getKey();
576 final List
<OrderEntry
> entries
= mapEntry
.getValue();
577 fillMapWithOrderEntries(vRoot
, entries
, null, vRoot
, null, forDir
, null, null);
580 for (Map
.Entry
<VirtualFile
, List
<OrderEntry
>> mapEntry
: libSourceRootEntries
.entrySet()) {
581 final VirtualFile vRoot
= mapEntry
.getKey();
582 final List
<OrderEntry
> entries
= mapEntry
.getValue();
583 fillMapWithOrderEntries(vRoot
, entries
, null, null, vRoot
, forDir
, null, null);
587 private static void addEntryToMap(final VirtualFile vRoot
, final OrderEntry entry
, final Map
<VirtualFile
, List
<OrderEntry
>> map
) {
588 List
<OrderEntry
> list
= map
.get(vRoot
);
590 list
= new ArrayList
<OrderEntry
>();
591 map
.put(vRoot
, list
);
596 private void fillMapWithOrderEntries(VirtualFile dir
,
597 List
<OrderEntry
> orderEntries
,
599 VirtualFile libraryClassRoot
,
600 VirtualFile librarySourceRoot
,
602 DirectoryInfo parentInfo
,
603 final List
<OrderEntry
> oldParentEntries
) {
604 if (isIgnored(dir
)) return;
606 if (forDir
!= null) {
607 if (!VfsUtil
.isAncestor(dir
, forDir
, false)) return;
610 DirectoryInfo info
= myDirToInfoMap
.get(dir
); // do not create it here!
611 if (info
== null) return;
613 if (module
!= null) {
614 if (info
.module
!= module
) return;
615 if (!info
.isInModuleSource
) return;
617 else if (libraryClassRoot
!= null) {
618 if (info
.libraryClassRoot
!= libraryClassRoot
) return;
619 if (info
.isInModuleSource
) return;
621 else if (librarySourceRoot
!= null) {
622 if (!info
.isInLibrarySource
) return;
623 if (info
.sourceRoot
!= librarySourceRoot
) return;
624 if (info
.libraryClassRoot
!= null) return;
627 final List
<OrderEntry
> oldEntries
= info
.getOrderEntries();
628 info
.addOrderEntries(orderEntries
, parentInfo
, oldParentEntries
);
630 final VirtualFile
[] children
= dir
.getChildren();
631 for (VirtualFile child
: children
) {
632 if (child
.isDirectory()) {
633 fillMapWithOrderEntries(child
, orderEntries
, module
, libraryClassRoot
, librarySourceRoot
, forDir
, info
, oldEntries
);
638 private static boolean isIgnored(VirtualFile f
) {
639 return FileTypeManager
.getInstance().isFileIgnored(f
.getName());
642 public DirectoryInfo
getInfoForDirectory(VirtualFile dir
) {
644 dispatchPendingEvents();
647 DirectoryInfo info
= myDirToInfoMap
.get(dir
);
648 if (info
!= null) return info
;
649 doInitialize(false, dir
);
652 return myDirToInfoMap
.get(dir
);
656 public boolean isProjectExcludeRoot(VirtualFile dir
) {
658 return myProjectExcludeRoots
.contains(dir
);
661 private final PackageSink mySink
= new PackageSink();
663 private static final Condition
<VirtualFile
> IS_VALID
= new Condition
<VirtualFile
>() {
664 public boolean value(final VirtualFile virtualFile
) {
665 return virtualFile
.isValid();
669 private class PackageSink
extends QueryFactory
<VirtualFile
, VirtualFile
[]> {
670 private PackageSink() {
671 registerExecutor(new QueryExecutor
<VirtualFile
, VirtualFile
[]>() {
672 public boolean execute(final VirtualFile
[] allDirs
, final Processor
<VirtualFile
> consumer
) {
673 for (VirtualFile dir
: allDirs
) {
674 DirectoryInfo info
= getInfoForDirectory(dir
);
677 if (!info
.isInLibrarySource
|| info
.libraryClassRoot
!= null) {
678 if (!consumer
.process(dir
)) return false;
686 public Query
<VirtualFile
> search(@NotNull String packageName
, boolean includeLibrarySources
) {
687 VirtualFile
[] allDirs
= doGetDirectoriesByPackageName(packageName
);
688 return new FilteredQuery
<VirtualFile
>(includeLibrarySources ?
new ArrayQuery
<VirtualFile
>(allDirs
) : createQuery(allDirs
), IS_VALID
);
693 public Query
<VirtualFile
> getDirectoriesByPackageName(@NotNull String packageName
, boolean includeLibrarySources
) {
695 return mySink
.search(packageName
, includeLibrarySources
);
699 private VirtualFile
[] doGetDirectoriesByPackageName(@NotNull String packageName
) {
700 dispatchPendingEvents();
703 VirtualFile
[] dirs
= myPackageNameToDirsMap
.get(packageName
);
704 return dirs
!= null ? dirs
: VirtualFile
.EMPTY_ARRAY
;
707 VirtualFile
[] dirs
= myPackageNameToDirsMap
.get(packageName
);
708 if (dirs
!= null) return dirs
;
709 dirs
= doGetDirectoriesByPackageNameInLazyMode(packageName
);
710 myPackageNameToDirsMap
.put(packageName
, dirs
);
716 private VirtualFile
[] doGetDirectoriesByPackageNameInLazyMode(@NotNull String packageName
) {
717 ArrayList
<VirtualFile
> list
= new ArrayList
<VirtualFile
>();
719 Module
[] modules
= ModuleManager
.getInstance(myProject
).getModules();
720 for (Module module
: modules
) {
721 for (ContentEntry contentEntry
: getContentEntries(module
)) {
722 SourceFolder
[] sourceFolders
= contentEntry
.getSourceFolders();
723 for (SourceFolder sourceFolder
: sourceFolders
) {
724 VirtualFile sourceRoot
= sourceFolder
.getFile();
725 if (sourceRoot
!= null) {
726 findAndAddDirByPackageName(list
, sourceRoot
, packageName
);
731 for (OrderEntry orderEntry
: getOrderEntries(module
)) {
732 if (orderEntry
instanceof LibraryOrderEntry
|| orderEntry
instanceof JdkOrderEntry
) {
733 VirtualFile
[] libRoots
= orderEntry
.getFiles(OrderRootType
.CLASSES
);
734 for (VirtualFile libRoot
: libRoots
) {
735 findAndAddDirByPackageName(list
, libRoot
, packageName
);
738 VirtualFile
[] libSourceRoots
= orderEntry
.getFiles(OrderRootType
.SOURCES
);
739 for (VirtualFile libSourceRoot
: libSourceRoots
) {
740 findAndAddDirByPackageName(list
, libSourceRoot
, packageName
);
746 return list
.toArray(new VirtualFile
[list
.size()]);
749 private void findAndAddDirByPackageName(ArrayList
<VirtualFile
> list
, VirtualFile root
, @NotNull String packageName
) {
750 VirtualFile dir
= findDirByPackageName(root
, packageName
);
751 if (dir
== null) return;
752 DirectoryInfo info
= getInfoForDirectory(dir
);
753 if (info
== null) return;
754 if (!packageName
.equals(info
.packageName
)) return;
755 if (!list
.contains(dir
)) {
760 private static VirtualFile
findDirByPackageName(VirtualFile root
, @NotNull String packageName
) {
761 if (packageName
.length() == 0) {
765 int index
= packageName
.indexOf('.');
767 VirtualFile child
= root
.findChild(packageName
);
768 if (child
== null || !child
.isDirectory()) return null;
772 String name
= packageName
.substring(0, index
);
773 String restName
= packageName
.substring(index
+ 1);
774 VirtualFile child
= root
.findChild(name
);
775 if (child
== null || !child
.isDirectory()) return null;
776 return findDirByPackageName(child
, restName
);
781 private void dispatchPendingEvents() {
782 myConnection
.deliverImmediately();
785 private void checkAvailability() {
786 if (!myInitialized
) {
787 LOG
.error("Directory index is not initialized yet for "+myProject
);
791 LOG
.error("Directory index is aleady disposed for "+myProject
);
795 private DirectoryInfo
getOrCreateDirInfo(VirtualFile dir
) {
796 DirectoryInfo info
= myDirToInfoMap
.get(dir
);
798 info
= new DirectoryInfo(dir
);
799 myDirToInfoMap
.put(dir
, info
);
804 private void setPackageName(VirtualFile dir
, DirectoryInfo info
, String newPackageName
) {
808 String oldPackageName
= info
.packageName
;
809 if (oldPackageName
!= null) {
810 VirtualFile
[] oldPackageDirs
= myPackageNameToDirsMap
.get(oldPackageName
);
811 assert oldPackageDirs
!= null;
812 assert oldPackageDirs
.length
> 0;
813 if (oldPackageDirs
.length
!= 1) {
814 VirtualFile
[] dirs
= new VirtualFile
[oldPackageDirs
.length
- 1];
816 boolean found
= false;
817 for (int i
= 0; i
< oldPackageDirs
.length
; i
++) {
818 VirtualFile oldDir
= oldPackageDirs
[i
];
819 if (oldDir
.equals(dir
)) {
823 dirs
[found ? i
- 1 : i
] = oldDir
;
828 myPackageNameToDirsMap
.put(oldPackageName
, dirs
);
831 assert dir
.equals(oldPackageDirs
[0]);
832 myPackageNameToDirsMap
.remove(oldPackageName
);
837 if (newPackageName
!= null) {
838 VirtualFile
[] newPackageDirs
= myPackageNameToDirsMap
.get(newPackageName
);
840 if (newPackageDirs
== null) {
841 dirs
= new VirtualFile
[]{dir
};
844 dirs
= new VirtualFile
[newPackageDirs
.length
+ 1];
845 System
.arraycopy(newPackageDirs
, 0, dirs
, 0, newPackageDirs
.length
);
846 dirs
[newPackageDirs
.length
] = dir
;
848 myPackageNameToDirsMap
.put(newPackageName
, dirs
);
852 if (info
.packageName
!= null) {
853 myPackageNameToDirsMap
.remove(info
.packageName
);
855 if (newPackageName
!= null) {
856 myPackageNameToDirsMap
.remove(newPackageName
);
860 info
.packageName
= newPackageName
;
864 private static String
getPackageNameForSubdir(String parentPackageName
, String subdirName
) {
865 if (parentPackageName
== null) return null;
866 return parentPackageName
.length() > 0 ? parentPackageName
+ "." + subdirName
: subdirName
;
869 private class MyVirtualFileListener
extends VirtualFileAdapter
{
870 private final Key
<List
<VirtualFile
>> FILES_TO_RELEASE_KEY
= Key
.create("DirectoryIndexImpl.MyVirtualFileListener.FILES_TO_RELEASE_KEY");
872 public void fileCreated(VirtualFileEvent event
) {
873 if (myIsLasyMode
) return;
875 VirtualFile file
= event
.getFile();
877 if (!file
.isDirectory()) return;
879 VirtualFile parent
= file
.getParent();
880 if (parent
== null) return;
882 if (isIgnored(file
)) return;
884 DirectoryInfo parentInfo
= myDirToInfoMap
.get(parent
);
885 if (parentInfo
== null) return;
887 Module module
= parentInfo
.module
;
889 for(DirectoryIndexExcludePolicy policy
: myExcludePolicies
) {
890 if (policy
.isExcludeRoot(file
)) return;
893 fillMapWithModuleContent(file
, module
, parentInfo
.contentRoot
, null);
895 if (module
!= null) {
896 if (parentInfo
.isInModuleSource
) {
897 String newDirPackageName
= getPackageNameForSubdir(parentInfo
.packageName
, file
.getName());
898 fillMapWithModuleSource(file
, module
, newDirPackageName
, parentInfo
.sourceRoot
, parentInfo
.isTestSource
, null);
902 if (parentInfo
.libraryClassRoot
!= null) {
903 String newDirPackageName
= getPackageNameForSubdir(parentInfo
.packageName
, file
.getName());
904 fillMapWithLibraryClasses(file
, newDirPackageName
, parentInfo
.libraryClassRoot
, null);
907 if (parentInfo
.isInLibrarySource
) {
908 String newDirPackageName
= getPackageNameForSubdir(parentInfo
.packageName
, file
.getName());
909 fillMapWithLibrarySources(file
, newDirPackageName
, parentInfo
.sourceRoot
, null);
912 if (!parentInfo
.getOrderEntries().isEmpty()) {
913 fillMapWithOrderEntries(file
, parentInfo
.getOrderEntries(), null, null, null, null, parentInfo
, null);
917 public void beforeFileDeletion(VirtualFileEvent event
) {
918 VirtualFile file
= event
.getFile();
919 if (!file
.isDirectory()) return;
920 if (!myDirToInfoMap
.containsKey(file
)) return;
922 ArrayList
<VirtualFile
> list
= new ArrayList
<VirtualFile
>();
923 addDirsRecursively(list
, file
);
924 file
.putUserData(FILES_TO_RELEASE_KEY
, list
);
927 private void addDirsRecursively(ArrayList
<VirtualFile
> list
, VirtualFile dir
) {
928 if (!myDirToInfoMap
.containsKey(dir
) || !(dir
instanceof NewVirtualFile
)) return;
932 for (VirtualFile child
: ((NewVirtualFile
)dir
).getCachedChildren()) {
933 if (child
.isDirectory()) {
934 addDirsRecursively(list
, child
);
939 public void fileDeleted(VirtualFileEvent event
) {
940 VirtualFile file
= event
.getFile();
941 List
<VirtualFile
> list
= file
.getUserData(FILES_TO_RELEASE_KEY
);
942 if (list
== null) return;
944 for (VirtualFile dir
: list
) {
945 DirectoryInfo info
= myDirToInfoMap
.remove(dir
);
947 setPackageName(dir
, info
, null);
952 public void fileMoved(VirtualFileMoveEvent event
) {
953 VirtualFile file
= event
.getFile();
955 if (file
.isDirectory()) {
960 public void propertyChanged(VirtualFilePropertyEvent event
) {
961 if (VirtualFile
.PROP_NAME
.equals(event
.getPropertyName())) {
962 VirtualFile file
= event
.getFile();
964 if (file
.isDirectory()) {