8b0441dee432ee0f1f353165979b5fa7e9bb1b2c
[idea/community.git] / platform / projectModel-impl / src / com / intellij / openapi / roots / impl / RootIndex.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.openapi.roots.impl;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.extensions.Extensions;
20 import com.intellij.openapi.fileTypes.FileTypeRegistry;
21 import com.intellij.openapi.module.Module;
22 import com.intellij.openapi.module.ModuleManager;
23 import com.intellij.openapi.project.Project;
24 import com.intellij.openapi.roots.*;
25 import com.intellij.openapi.roots.impl.libraries.LibraryEx;
26 import com.intellij.openapi.roots.libraries.Library;
27 import com.intellij.openapi.util.Pair;
28 import com.intellij.openapi.util.text.StringUtil;
29 import com.intellij.openapi.vfs.VfsUtilCore;
30 import com.intellij.openapi.vfs.VirtualFile;
31 import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
32 import com.intellij.util.CollectionQuery;
33 import com.intellij.util.Query;
34 import com.intellij.util.containers.ContainerUtil;
35 import com.intellij.util.containers.MultiMap;
36 import com.intellij.util.containers.SLRUMap;
37 import gnu.trove.TObjectIntHashMap;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
40 import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
41
42 import java.util.*;
43
44 public class RootIndex {
45   public static final Comparator<OrderEntry> BY_OWNER_MODULE = (o1, o2) -> {
46     String name1 = o1.getOwnerModule().getName();
47     String name2 = o2.getOwnerModule().getName();
48     return name1.compareTo(name2);
49   };
50
51   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.RootIndex");
52   private static final FileTypeRegistry ourFileTypes = FileTypeRegistry.getInstance();
53
54   private final Map<VirtualFile, String> myPackagePrefixByRoot = ContainerUtil.newHashMap();
55
56   private final InfoCache myInfoCache;
57   private final List<JpsModuleSourceRootType<?>> myRootTypes = ContainerUtil.newArrayList();
58   private final TObjectIntHashMap<JpsModuleSourceRootType<?>> myRootTypeId = new TObjectIntHashMap<JpsModuleSourceRootType<?>>();
59   @NotNull private final Project myProject;
60   private final PackageDirectoryCache myPackageDirectoryCache;
61   private OrderEntryGraph myOrderEntryGraph;
62
63   // made public for Upsource
64   public RootIndex(@NotNull Project project, @NotNull InfoCache cache) {
65     myProject = project;
66     myInfoCache = cache;
67     final RootInfo info = buildRootInfo(project);
68
69     MultiMap<String, VirtualFile> rootsByPackagePrefix = MultiMap.create();
70     Set<VirtualFile> allRoots = info.getAllRoots();
71     for (VirtualFile root : allRoots) {
72       List<VirtualFile> hierarchy = getHierarchy(root, allRoots, info);
73       Pair<DirectoryInfo, String> pair = hierarchy != null
74                                          ? calcDirectoryInfo(root, hierarchy, info)
75                                          : new Pair<DirectoryInfo, String>(NonProjectDirectoryInfo.IGNORED, null);
76       cacheInfos(root, root, pair.first);
77       rootsByPackagePrefix.putValue(pair.second, root);
78       myPackagePrefixByRoot.put(root, pair.second);
79     }
80     myPackageDirectoryCache = new PackageDirectoryCache(rootsByPackagePrefix) {
81       @Override
82       protected boolean isPackageDirectory(@NotNull VirtualFile dir, @NotNull String packageName) {
83         return getInfoForFile(dir).isInProject() && packageName.equals(getPackageName(dir));
84       }
85     };
86   }
87
88   @NotNull
89   private RootInfo buildRootInfo(@NotNull Project project) {
90     final RootInfo info = new RootInfo();
91     for (final Module module : ModuleManager.getInstance(project).getModules()) {
92       final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
93
94       for (final VirtualFile contentRoot : moduleRootManager.getContentRoots()) {
95         if (!info.contentRootOf.containsKey(contentRoot)) {
96           info.contentRootOf.put(contentRoot, module);
97         }
98       }
99
100       for (ContentEntry contentEntry : moduleRootManager.getContentEntries()) {
101         if (!(contentEntry instanceof ContentEntryImpl) || !((ContentEntryImpl)contentEntry).isDisposed()) {
102           for (VirtualFile excludeRoot : contentEntry.getExcludeFolderFiles()) {
103             info.excludedFromModule.put(excludeRoot, module);
104           }
105         }
106
107         // Init module sources
108         for (final SourceFolder sourceFolder : contentEntry.getSourceFolders()) {
109           final VirtualFile sourceFolderRoot = sourceFolder.getFile();
110           if (sourceFolderRoot != null) {
111             info.rootTypeId.put(sourceFolderRoot, getRootTypeId(sourceFolder.getRootType()));
112             info.classAndSourceRoots.add(sourceFolderRoot);
113             info.sourceRootOf.putValue(sourceFolderRoot, module);
114             info.packagePrefix.put(sourceFolderRoot, sourceFolder.getPackagePrefix());
115           }
116         }
117       }
118
119       for (OrderEntry orderEntry : moduleRootManager.getOrderEntries()) {
120         if (orderEntry instanceof LibraryOrSdkOrderEntry) {
121           final LibraryOrSdkOrderEntry entry = (LibraryOrSdkOrderEntry)orderEntry;
122           final VirtualFile[] sourceRoots = entry.getRootFiles(OrderRootType.SOURCES);
123           final VirtualFile[] classRoots = entry.getRootFiles(OrderRootType.CLASSES);
124
125           // Init library sources
126           for (final VirtualFile sourceRoot : sourceRoots) {
127             info.classAndSourceRoots.add(sourceRoot);
128             info.libraryOrSdkSources.add(sourceRoot);
129             info.packagePrefix.put(sourceRoot, "");
130           }
131
132           // init library classes
133           for (final VirtualFile classRoot : classRoots) {
134             info.classAndSourceRoots.add(classRoot);
135             info.libraryOrSdkClasses.add(classRoot);
136             info.packagePrefix.put(classRoot, "");
137           }
138
139           if (orderEntry instanceof LibraryOrderEntry) {
140             Library library = ((LibraryOrderEntry)orderEntry).getLibrary();
141             if (library != null) {
142               for (VirtualFile root : ((LibraryEx)library).getExcludedRoots()) {
143                 info.excludedFromLibraries.putValue(root, library);
144               }
145               for (VirtualFile root : sourceRoots) {
146                 info.sourceOfLibraries.putValue(root, library);
147               }
148               for (VirtualFile root : classRoots) {
149                 info.classOfLibraries.putValue(root, library);
150               }
151             }
152           }
153         }
154       }
155     }
156
157     for (DirectoryIndexExcludePolicy policy : Extensions.getExtensions(DirectoryIndexExcludePolicy.EP_NAME, project)) {
158       Collections.addAll(info.excludedFromProject, policy.getExcludeRootsForProject());
159     }
160     return info;
161   }
162
163   @NotNull
164   private synchronized OrderEntryGraph getOrderEntryGraph() {
165     if (myOrderEntryGraph == null) {
166       RootInfo rootInfo = buildRootInfo(myProject);
167       myOrderEntryGraph = new OrderEntryGraph(myProject, rootInfo);
168     }
169     return myOrderEntryGraph;
170   }
171
172   /**
173    * A reverse dependency graph of (library, jdk, module, module source) -> (module).
174    *
175    * <p>Each edge carries with it the associated OrderEntry that caused the dependency.
176    */
177   private static class OrderEntryGraph {
178
179     private static class Edge {
180       Module myKey;
181       ModuleOrderEntry myOrderEntry; // Order entry from myKey -> the node containing the edge
182       boolean myRecursive; // Whether this edge should be descended into during graph walk
183
184       public Edge(Module key, ModuleOrderEntry orderEntry, boolean recursive) {
185         myKey = key;
186         myOrderEntry = orderEntry;
187         myRecursive = recursive;
188       }
189
190       @Override
191       public String toString() {
192         return myOrderEntry.toString();
193       }
194     }
195
196     private static class Node {
197       Module myKey;
198       List<Edge> myEdges = new ArrayList<Edge>();
199
200       @Override
201       public String toString() {
202         return myKey.toString();
203       }
204     }
205
206     private static class Graph {
207       Map<Module, Node> myNodes = new HashMap<Module, Node>();
208     }
209
210     final Project myProject;
211     final RootInfo myRootInfo;
212     final Set<VirtualFile> myAllRoots;
213     Graph myGraph;
214     MultiMap<VirtualFile, Node> myRoots; // Map of roots to their root nodes, eg. library jar -> library node
215     final SynchronizedSLRUCache<VirtualFile, List<OrderEntry>> myCache;
216     private MultiMap<VirtualFile, OrderEntry> myLibClassRootEntries;
217     private MultiMap<VirtualFile, OrderEntry> myLibSourceRootEntries;
218
219     public OrderEntryGraph(Project project, RootInfo rootInfo) {
220       myProject = project;
221       myRootInfo = rootInfo;
222       myAllRoots = myRootInfo.getAllRoots();
223       int cacheSize = Math.max(25, (myAllRoots.size() / 100) * 2);
224       myCache = new SynchronizedSLRUCache<VirtualFile, List<OrderEntry>>(cacheSize, cacheSize) {
225         @NotNull
226         @Override
227         public List<OrderEntry> createValue(VirtualFile key) {
228           return collectOrderEntries(key);
229         }
230       };
231       initGraph();
232       initLibraryRoots();
233     }
234
235     private void initGraph() {
236       Graph graph = new Graph();
237
238       MultiMap<VirtualFile, Node> roots = MultiMap.createSmart();
239
240       for (final Module module : ModuleManager.getInstance(myProject).getModules()) {
241         final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
242         List<OrderEnumerationHandler> handlers = OrderEnumeratorBase.getCustomHandlers(module);
243         for (OrderEntry orderEntry : moduleRootManager.getOrderEntries()) {
244           if (orderEntry instanceof ModuleOrderEntry) {
245             ModuleOrderEntry moduleOrderEntry = (ModuleOrderEntry)orderEntry;
246             final Module depModule = moduleOrderEntry.getModule();
247             if (depModule != null) {
248               Node node = graph.myNodes.get(depModule);
249               OrderEnumerator en = OrderEnumerator.orderEntries(depModule).exportedOnly();
250               if (node == null) {
251                 node = new Node();
252                 node.myKey = depModule;
253                 graph.myNodes.put(depModule, node);
254
255                 VirtualFile[] importedClassRoots = en.classes().usingCache().getRoots();
256                 for (VirtualFile importedClassRoot : importedClassRoots) {
257                   roots.putValue(importedClassRoot, node);
258                 }
259
260                 VirtualFile[] importedSourceRoots = en.sources().usingCache().getRoots();
261                 for (VirtualFile sourceRoot : importedSourceRoots) {
262                   roots.putValue(sourceRoot, node);
263                 }
264               }
265               boolean shouldRecurse = en.recursively().shouldRecurse(moduleOrderEntry, handlers);
266               node.myEdges.add(new Edge(module, moduleOrderEntry, shouldRecurse));
267             }
268           }
269         }
270       }
271
272       myGraph = graph;
273       myRoots = roots;
274     }
275
276     private void initLibraryRoots() {
277       MultiMap<VirtualFile, OrderEntry> libClassRootEntries = MultiMap.createSmart();
278       MultiMap<VirtualFile, OrderEntry> libSourceRootEntries = MultiMap.createSmart();
279
280       for (final Module module : ModuleManager.getInstance(myProject).getModules()) {
281         final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
282         for (OrderEntry orderEntry : moduleRootManager.getOrderEntries()) {
283           if (orderEntry instanceof LibraryOrSdkOrderEntry) {
284             final LibraryOrSdkOrderEntry entry = (LibraryOrSdkOrderEntry)orderEntry;
285             for (final VirtualFile sourceRoot : entry.getRootFiles(OrderRootType.SOURCES)) {
286               libSourceRootEntries.putValue(sourceRoot, orderEntry);
287             }
288             for (final VirtualFile classRoot : entry.getRootFiles(OrderRootType.CLASSES)) {
289               libClassRootEntries.putValue(classRoot, orderEntry);
290             }
291           }
292         }
293       }
294
295       myLibClassRootEntries = libClassRootEntries;
296       myLibSourceRootEntries = libSourceRootEntries;
297     }
298
299     private List<OrderEntry> getOrderEntries(@NotNull VirtualFile file) {
300       return myCache.get(file);
301     }
302
303     /**
304      * Traverses the graph from the given file, collecting all encountered order entries.
305      */
306     private List<OrderEntry> collectOrderEntries(@NotNull VirtualFile file) {
307       List<VirtualFile> roots = getHierarchy(file, myAllRoots, myRootInfo);
308       if (roots == null) {
309         return Collections.emptyList();
310       }
311       List<OrderEntry> result = new ArrayList<OrderEntry>();
312       Stack<Node> stack = new Stack<Node>();
313       for (VirtualFile root : roots) {
314         Collection<Node> nodes = myRoots.get(root);
315         for (Node node : nodes) {
316           stack.push(node);
317         }
318       }
319
320       Set<Node> seen = new HashSet<Node>();
321       while (!stack.isEmpty()) {
322         Node node = stack.pop();
323         if (seen.contains(node)) {
324           continue;
325         }
326         seen.add(node);
327
328         for (Edge edge : node.myEdges) {
329           result.add(edge.myOrderEntry);
330
331           if (edge.myRecursive) {
332             Node targetNode = myGraph.myNodes.get(edge.myKey);
333             if (targetNode != null) {
334               stack.push(targetNode);
335             }
336           }
337         }
338       }
339
340       @Nullable VirtualFile libraryClassRoot = myRootInfo.findLibraryRootInfo(roots, false);
341       @Nullable VirtualFile librarySourceRoot = myRootInfo.findLibraryRootInfo(roots, true);
342       result.addAll(myRootInfo.getLibraryOrderEntries(roots, libraryClassRoot, librarySourceRoot, myLibClassRootEntries, myLibSourceRootEntries));
343
344       VirtualFile moduleContentRoot = myRootInfo.findModuleRootInfo(roots);
345       if (moduleContentRoot != null) {
346         ContainerUtil.addIfNotNull(result, myRootInfo.getModuleSourceEntry(roots, moduleContentRoot, myLibClassRootEntries));
347       }
348       Collections.sort(result, BY_OWNER_MODULE);
349       return result;
350     }
351   }
352
353   private int getRootTypeId(@NotNull JpsModuleSourceRootType<?> rootType) {
354     if (myRootTypeId.containsKey(rootType)) {
355       return myRootTypeId.get(rootType);
356     }
357
358     int id = myRootTypes.size();
359     if (id > DirectoryInfoImpl.MAX_ROOT_TYPE_ID) {
360       LOG.error("Too many different types of module source roots (" + id + ") registered: " + myRootTypes);
361     }
362     myRootTypes.add(rootType);
363     myRootTypeId.put(rootType, id);
364     return id;
365   }
366
367   @NotNull
368   public DirectoryInfo getInfoForFile(@NotNull VirtualFile file) {
369     if (!file.isValid()) {
370       return NonProjectDirectoryInfo.INVALID;
371     }
372     VirtualFile dir;
373     if (!file.isDirectory()) {
374       DirectoryInfo info = myInfoCache.getCachedInfo(file);
375       if (info != null) {
376         return info;
377       }
378       if (ourFileTypes.isFileIgnored(file)) {
379         return NonProjectDirectoryInfo.IGNORED;
380       }
381       dir = file.getParent();
382     }
383     else {
384       dir = file;
385     }
386
387     int count = 0;
388     for (VirtualFile root = dir; root != null; root = root.getParent()) {
389       if (++count > 1000) {
390         throw new IllegalStateException("Possible loop in tree, started at " + dir.getName());
391       }
392       DirectoryInfo info = myInfoCache.getCachedInfo(root);
393       if (info != null) {
394         if (!dir.equals(root)) {
395           cacheInfos(dir, root, info);
396         }
397         return info;
398       }
399
400       if (ourFileTypes.isFileIgnored(root)) {
401         return cacheInfos(dir, root, NonProjectDirectoryInfo.IGNORED);
402       }
403     }
404
405     return cacheInfos(dir, null, NonProjectDirectoryInfo.NOT_UNDER_PROJECT_ROOTS);
406   }
407
408   @NotNull
409   private DirectoryInfo cacheInfos(VirtualFile dir, @Nullable VirtualFile stopAt, @NotNull DirectoryInfo info) {
410     while (dir != null) {
411       myInfoCache.cacheInfo(dir, info);
412       if (dir.equals(stopAt)) {
413         break;
414       }
415       dir = dir.getParent();
416     }
417     return info;
418   }
419
420   @NotNull
421   public Query<VirtualFile> getDirectoriesByPackageName(@NotNull final String packageName, final boolean includeLibrarySources) {
422     // Note that this method is used in upsource as well, hence, don't reduce this method's visibility.
423     List<VirtualFile> result = myPackageDirectoryCache.getDirectoriesByPackageName(packageName);
424     if (!includeLibrarySources) {
425       result = ContainerUtil.filter(result, file -> {
426         DirectoryInfo info = getInfoForFile(file);
427         return info.isInProject() && (!info.isInLibrarySource() || info.isInModuleSource() || info.hasLibraryClassRoot());
428       });
429     }
430     return new CollectionQuery<VirtualFile>(result);
431   }
432
433   @Nullable
434   public String getPackageName(@NotNull final VirtualFile dir) {
435     if (dir.isDirectory()) {
436       if (ourFileTypes.isFileIgnored(dir)) {
437         return null;
438       }
439
440       if (myPackagePrefixByRoot.containsKey(dir)) {
441         return myPackagePrefixByRoot.get(dir);
442       }
443
444       final VirtualFile parent = dir.getParent();
445       if (parent != null) {
446         return getPackageNameForSubdir(getPackageName(parent), dir.getName());
447       }
448     }
449
450     return null;
451   }
452
453   @Nullable
454   protected static String getPackageNameForSubdir(@Nullable String parentPackageName, @NotNull String subdirName) {
455     if (parentPackageName == null) return null;
456     return parentPackageName.isEmpty() ? subdirName : parentPackageName + "." + subdirName;
457   }
458
459   @Nullable
460   public JpsModuleSourceRootType<?> getSourceRootType(@NotNull DirectoryInfo directoryInfo) {
461     return myRootTypes.get(directoryInfo.getSourceRootTypeId());
462   }
463
464   boolean resetOnEvents(@NotNull List<? extends VFileEvent> events) {
465     for (VFileEvent event : events) {
466       VirtualFile file = event.getFile();
467       if (file == null || file.isDirectory()) {
468         return true;
469       }
470     }
471     return false;
472   }
473
474   @Nullable("returns null only if dir is under ignored folder")
475   private static List<VirtualFile> getHierarchy(VirtualFile dir, @NotNull Set<VirtualFile> allRoots, @NotNull RootInfo info) {
476     List<VirtualFile> hierarchy = ContainerUtil.newArrayList();
477     boolean hasContentRoots = false;
478     while (dir != null) {
479       hasContentRoots |= info.contentRootOf.get(dir) != null;
480       if (!hasContentRoots && ourFileTypes.isFileIgnored(dir)) {
481         return null;
482       }
483       if (allRoots.contains(dir)) {
484         hierarchy.add(dir);
485       }
486       dir = dir.getParent();
487     }
488     return hierarchy;
489   }
490
491   private static class RootInfo {
492     // getDirectoriesByPackageName used to be in this order, some clients might rely on that
493     @NotNull final LinkedHashSet<VirtualFile> classAndSourceRoots = ContainerUtil.newLinkedHashSet();
494
495     @NotNull final Set<VirtualFile> libraryOrSdkSources = ContainerUtil.newHashSet();
496     @NotNull final Set<VirtualFile> libraryOrSdkClasses = ContainerUtil.newHashSet();
497     @NotNull final Map<VirtualFile, Module> contentRootOf = ContainerUtil.newHashMap();
498     @NotNull final MultiMap<VirtualFile, Module> sourceRootOf = MultiMap.createSet();
499     @NotNull final TObjectIntHashMap<VirtualFile> rootTypeId = new TObjectIntHashMap<VirtualFile>();
500     @NotNull final MultiMap<VirtualFile, Library> excludedFromLibraries = MultiMap.createSmart();
501     @NotNull final MultiMap<VirtualFile, Library> classOfLibraries = MultiMap.createSmart();
502     @NotNull final MultiMap<VirtualFile, Library> sourceOfLibraries = MultiMap.createSmart();
503     @NotNull final Set<VirtualFile> excludedFromProject = ContainerUtil.newHashSet();
504     @NotNull final Map<VirtualFile, Module> excludedFromModule = ContainerUtil.newHashMap();
505     @NotNull final Map<VirtualFile, String> packagePrefix = ContainerUtil.newHashMap();
506
507     @NotNull
508     Set<VirtualFile> getAllRoots() {
509       LinkedHashSet<VirtualFile> result = ContainerUtil.newLinkedHashSet();
510       result.addAll(classAndSourceRoots);
511       result.addAll(contentRootOf.keySet());
512       result.addAll(excludedFromLibraries.keySet());
513       result.addAll(excludedFromModule.keySet());
514       result.addAll(excludedFromProject);
515       return result;
516     }
517
518     @Nullable
519     private VirtualFile findModuleRootInfo(@NotNull List<VirtualFile> hierarchy) {
520       for (VirtualFile root : hierarchy) {
521         Module module = contentRootOf.get(root);
522         Module excludedFrom = excludedFromModule.get(root);
523         if (module != null && excludedFrom != module) {
524           return root;
525         }
526         if (excludedFrom != null || excludedFromProject.contains(root)) {
527           return null;
528         }
529       }
530       return null;
531     }
532
533     @Nullable
534     private VirtualFile findNearestContentRootForExcluded(@NotNull List<VirtualFile> hierarchy) {
535       for (VirtualFile root : hierarchy) {
536         if (contentRootOf.containsKey(root)) {
537           return root;
538         }
539       }
540       return null;
541     }
542
543     @Nullable
544     private VirtualFile findLibraryRootInfo(@NotNull List<VirtualFile> hierarchy, boolean source) {
545       Set<Library> librariesToIgnore = ContainerUtil.newHashSet();
546       for (VirtualFile root : hierarchy) {
547         librariesToIgnore.addAll(excludedFromLibraries.get(root));
548         if (source && libraryOrSdkSources.contains(root) &&
549             (!sourceOfLibraries.containsKey(root) || !librariesToIgnore.containsAll(sourceOfLibraries.get(root)))) {
550           return root;
551         }
552         else if (!source && libraryOrSdkClasses.contains(root) &&
553                  (!classOfLibraries.containsKey(root) || !librariesToIgnore.containsAll(classOfLibraries.get(root)))) {
554           return root;
555         }
556       }
557       return null;
558     }
559
560     private String calcPackagePrefix(@NotNull VirtualFile root,
561                                      @NotNull List<VirtualFile> hierarchy,
562                                      VirtualFile moduleContentRoot,
563                                      VirtualFile libraryClassRoot,
564                                      VirtualFile librarySourceRoot) {
565       VirtualFile packageRoot = findPackageRootInfo(hierarchy, moduleContentRoot, libraryClassRoot, librarySourceRoot);
566       String prefix = packagePrefix.get(packageRoot);
567       if (prefix != null && !root.equals(packageRoot)) {
568         assert packageRoot != null;
569         String relative = VfsUtilCore.getRelativePath(root, packageRoot, '.');
570         prefix = StringUtil.isEmpty(prefix) ? relative : prefix + '.' + relative;
571       }
572       return prefix;
573     }
574
575     @Nullable
576     private VirtualFile findPackageRootInfo(@NotNull List<VirtualFile> hierarchy,
577                                             VirtualFile moduleContentRoot,
578                                             VirtualFile libraryClassRoot,
579                                             VirtualFile librarySourceRoot) {
580       for (VirtualFile root : hierarchy) {
581         if (moduleContentRoot != null &&
582             sourceRootOf.get(root).contains(contentRootOf.get(moduleContentRoot)) &&
583             librarySourceRoot == null) {
584           return root;
585         }
586         if (root.equals(libraryClassRoot) || root.equals(librarySourceRoot)) {
587           return root;
588         }
589         if (root.equals(moduleContentRoot) && !sourceRootOf.containsKey(root) && librarySourceRoot == null && libraryClassRoot == null) {
590           return null;
591         }
592       }
593       return null;
594     }
595
596     @NotNull
597     private LinkedHashSet<OrderEntry> getLibraryOrderEntries(@NotNull List<VirtualFile> hierarchy,
598                                                              @Nullable VirtualFile libraryClassRoot,
599                                                              @Nullable VirtualFile librarySourceRoot,
600                                                              @NotNull MultiMap<VirtualFile, OrderEntry> libClassRootEntries,
601                                                              @NotNull MultiMap<VirtualFile, OrderEntry> libSourceRootEntries) {
602       LinkedHashSet<OrderEntry> orderEntries = ContainerUtil.newLinkedHashSet();
603       for (VirtualFile root : hierarchy) {
604         if (root.equals(libraryClassRoot) && !sourceRootOf.containsKey(root)) {
605           orderEntries.addAll(libClassRootEntries.get(root));
606         }
607         if (root.equals(librarySourceRoot) && libraryClassRoot == null) {
608           orderEntries.addAll(libSourceRootEntries.get(root));
609         }
610         if (libClassRootEntries.containsKey(root) || sourceRootOf.containsKey(root) && librarySourceRoot == null) {
611           break;
612         }
613       }
614       return orderEntries;
615     }
616
617
618     @Nullable
619     private ModuleSourceOrderEntry getModuleSourceEntry(@NotNull List<VirtualFile> hierarchy,
620                                                         @NotNull VirtualFile moduleContentRoot,
621                                                         @NotNull MultiMap<VirtualFile, OrderEntry> libClassRootEntries) {
622       Module module = contentRootOf.get(moduleContentRoot);
623       for (VirtualFile root : hierarchy) {
624         if (sourceRootOf.get(root).contains(module)) {
625           return ContainerUtil.findInstance(ModuleRootManager.getInstance(module).getOrderEntries(), ModuleSourceOrderEntry.class);
626         }
627         if (libClassRootEntries.containsKey(root)) {
628           return null;
629         }
630       }
631       return null;
632     }
633   }
634
635   @NotNull
636   private static Pair<DirectoryInfo, String> calcDirectoryInfo(@NotNull final VirtualFile root,
637                                                                @NotNull final List<VirtualFile> hierarchy,
638                                                                @NotNull RootInfo info) {
639     VirtualFile moduleContentRoot = info.findModuleRootInfo(hierarchy);
640     VirtualFile libraryClassRoot = info.findLibraryRootInfo(hierarchy, false);
641     VirtualFile librarySourceRoot = info.findLibraryRootInfo(hierarchy, true);
642     boolean inProject = moduleContentRoot != null || libraryClassRoot != null || librarySourceRoot != null;
643     VirtualFile nearestContentRoot;
644     if (inProject) {
645       nearestContentRoot = moduleContentRoot;
646     }
647     else {
648       nearestContentRoot = info.findNearestContentRootForExcluded(hierarchy);
649       if (nearestContentRoot == null) {
650         return new Pair<DirectoryInfo, String>(NonProjectDirectoryInfo.EXCLUDED, null);
651       }
652     }
653
654     VirtualFile sourceRoot = info.findPackageRootInfo(hierarchy, moduleContentRoot, null, librarySourceRoot);
655
656     VirtualFile moduleSourceRoot = info.findPackageRootInfo(hierarchy, moduleContentRoot, null, null);
657     boolean inModuleSources = moduleSourceRoot != null;
658     boolean inLibrarySource = librarySourceRoot != null;
659     int typeId = moduleSourceRoot != null ? info.rootTypeId.get(moduleSourceRoot) : 0;
660
661     Module module = info.contentRootOf.get(nearestContentRoot);
662     DirectoryInfo directoryInfo =
663       new DirectoryInfoImpl(root, module, nearestContentRoot, sourceRoot, libraryClassRoot, inModuleSources, inLibrarySource, !inProject, typeId);
664
665     String packagePrefix = info.calcPackagePrefix(root, hierarchy, moduleContentRoot, libraryClassRoot, librarySourceRoot);
666
667     return Pair.create(directoryInfo, packagePrefix);
668   }
669
670   @NotNull
671   public List<OrderEntry> getOrderEntries(@NotNull DirectoryInfo info) {
672     if (!(info instanceof DirectoryInfoImpl)) return Collections.emptyList();
673     return getOrderEntryGraph().getOrderEntries(((DirectoryInfoImpl)info).getRoot());
674   }
675
676   public interface InfoCache {
677     @Nullable
678     DirectoryInfo getCachedInfo(@NotNull VirtualFile dir);
679
680     void cacheInfo(@NotNull VirtualFile dir, @NotNull DirectoryInfo info);
681   }
682
683   /**
684    * An LRU cache with synchronization around the primary cache operations (get() and insertion
685    * of a newly created value). Other map operations are not synchronized.
686    */
687   abstract static class SynchronizedSLRUCache<K, V> extends SLRUMap<K,V> {
688     protected final Object myLock = new Object();
689
690     protected SynchronizedSLRUCache(final int protectedQueueSize, final int probationalQueueSize) {
691       super(protectedQueueSize, probationalQueueSize);
692     }
693
694     @NotNull
695     public abstract V createValue(K key);
696
697     @Override
698     @NotNull
699     public V get(K key) {
700       V value;
701       synchronized (myLock) {
702         value = super.get(key);
703         if (value != null) {
704           return value;
705         }
706       }
707       value = createValue(key);
708       synchronized (myLock) {
709         put(key, value);
710       }
711       return value;
712     }
713   }
714 }