fix "IDEA-221944 Deadlock on opening second project" and support preloading for proje...
[idea/community.git] / java / java-indexing-impl / src / com / intellij / psi / impl / file / impl / JavaFileManagerImpl.java
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.psi.impl.file.impl;
3
4 import com.intellij.ProjectTopics;
5 import com.intellij.ide.highlighter.JavaClassFileType;
6 import com.intellij.openapi.Disposable;
7 import com.intellij.openapi.diagnostic.Logger;
8 import com.intellij.openapi.fileTypes.FileTypeRegistry;
9 import com.intellij.openapi.module.Module;
10 import com.intellij.openapi.module.impl.scopes.ModuleWithDependenciesScope;
11 import com.intellij.openapi.project.Project;
12 import com.intellij.openapi.roots.*;
13 import com.intellij.openapi.util.Comparing;
14 import com.intellij.openapi.util.Pair;
15 import com.intellij.openapi.vfs.VirtualFile;
16 import com.intellij.openapi.vfs.VirtualFileWithId;
17 import com.intellij.psi.*;
18 import com.intellij.psi.impl.PsiImplUtil;
19 import com.intellij.psi.impl.PsiManagerEx;
20 import com.intellij.psi.impl.file.PsiPackageImpl;
21 import com.intellij.psi.impl.java.stubs.index.JavaAutoModuleNameIndex;
22 import com.intellij.psi.impl.java.stubs.index.JavaFullClassNameIndex;
23 import com.intellij.psi.impl.java.stubs.index.JavaModuleNameIndex;
24 import com.intellij.psi.impl.light.LightJavaModule;
25 import com.intellij.psi.search.DelegatingGlobalSearchScope;
26 import com.intellij.psi.search.GlobalSearchScope;
27 import com.intellij.util.Query;
28 import com.intellij.util.containers.ContainerUtil;
29 import org.jetbrains.annotations.NotNull;
30 import org.jetbrains.annotations.Nullable;
31 import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes;
32
33 import java.util.*;
34 import java.util.stream.Stream;
35
36 import static java.util.Objects.requireNonNull;
37
38 /**
39  * @author dmitry lomov
40  */
41 public final class JavaFileManagerImpl implements JavaFileManager, Disposable {
42   private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.file.impl.JavaFileManagerImpl");
43
44   private final PsiManagerEx myManager;
45   private volatile Set<String> myNontrivialPackagePrefixes;
46   private boolean myDisposed;
47
48   public JavaFileManagerImpl(Project project) {
49     myManager = PsiManagerEx.getInstanceEx(project);
50     project.getMessageBus().connect().subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() {
51       @Override
52       public void rootsChanged(@NotNull final ModuleRootEvent event) {
53         myNontrivialPackagePrefixes = null;
54       }
55     });
56   }
57
58   @Override
59   public void dispose() {
60     myDisposed = true;
61   }
62
63   @Override
64   @Nullable
65   public PsiPackage findPackage(@NotNull String packageName) {
66     Query<VirtualFile> dirs = PackageIndex.getInstance(myManager.getProject()).getDirsByPackageName(packageName, true);
67     if (dirs.findFirst() == null) return null;
68     return new PsiPackageImpl(myManager, packageName);
69   }
70
71   @NotNull
72   @Override
73   public PsiClass[] findClasses(@NotNull String qName, @NotNull final GlobalSearchScope scope) {
74     List<Pair<PsiClass, VirtualFile>> result = doFindClasses(qName, scope);
75
76     int count = result.size();
77     if (count == 0) return PsiClass.EMPTY_ARRAY;
78     if (count == 1) return new PsiClass[] {result.get(0).getFirst()};
79
80     ContainerUtil.quickSort(result, (o1, o2) -> scope.compare(o2.getSecond(), o1.getSecond()));
81
82     return result.stream().map(p -> p.getFirst()).toArray(PsiClass[]::new);
83   }
84
85   @NotNull
86   private List<Pair<PsiClass, VirtualFile>> doFindClasses(@NotNull String qName, @NotNull final GlobalSearchScope scope) {
87     final Collection<PsiClass> classes = JavaFullClassNameIndex.getInstance().get(qName.hashCode(), myManager.getProject(), scope);
88     if (classes.isEmpty()) return Collections.emptyList();
89     List<Pair<PsiClass, VirtualFile>> result = new ArrayList<>(classes.size());
90     for (PsiClass aClass : classes) {
91       final String qualifiedName = aClass.getQualifiedName();
92       if (qualifiedName == null || !qualifiedName.equals(qName)) continue;
93
94       PsiFile file = aClass.getContainingFile();
95       if (file == null) {
96         throw new AssertionError("No file for class: " + aClass + " of " + aClass.getClass());
97       }
98       final boolean valid = file.isValid();
99       VirtualFile vFile = file.getVirtualFile();
100       if (!valid) {
101         LOG.error("Invalid file " +
102                   file + "; virtualFile:" + vFile +
103                   (vFile != null && !vFile.isValid() ? " (invalid)" : "") +
104                   "; id=" + (vFile == null ? 0 : ((VirtualFileWithId)vFile).getId()),
105                   new PsiInvalidElementAccessException(aClass));
106         continue;
107       }
108       if (!hasAcceptablePackage(vFile)) continue;
109
110       result.add(Pair.create(aClass, vFile));
111     }
112
113     return result;
114   }
115
116   @Override
117   @Nullable
118   public PsiClass findClass(@NotNull String qName, @NotNull GlobalSearchScope scope) {
119     LOG.assertTrue(!myDisposed);
120     VirtualFile bestFile = null;
121     PsiClass bestClass = null;
122     List<Pair<PsiClass, VirtualFile>> result = doFindClasses(qName, scope);
123
124     //noinspection ForLoopReplaceableByForEach
125     for (int i = 0; i < result.size(); i++) {
126       Pair<PsiClass, VirtualFile> pair = result.get(i);
127       VirtualFile vFile = pair.getSecond();
128       if (bestFile == null || scope.compare(vFile, bestFile) > 0) {
129         bestFile = vFile;
130         bestClass = pair.getFirst();
131       }
132     }
133
134     return bestClass;
135   }
136
137   private boolean hasAcceptablePackage(@NotNull VirtualFile vFile) {
138     if (FileTypeRegistry.getInstance().isFileOfType(vFile, JavaClassFileType.INSTANCE)) {
139       // See IDEADEV-5626
140       final VirtualFile root = ProjectRootManager.getInstance(myManager.getProject()).getFileIndex().getClassRootForFile(vFile);
141       VirtualFile parent = vFile.getParent();
142       final PsiNameHelper nameHelper = PsiNameHelper.getInstance(myManager.getProject());
143       while (parent != null && !Comparing.equal(parent, root)) {
144         if (!nameHelper.isIdentifier(parent.getName())) return false;
145         parent = parent.getParent();
146       }
147     }
148
149     return true;
150   }
151
152   @NotNull
153   @Override
154   public Collection<String> getNonTrivialPackagePrefixes() {
155     Set<String> names = myNontrivialPackagePrefixes;
156     if (names == null) {
157       names = new HashSet<>();
158       final ProjectRootManager rootManager = ProjectRootManager.getInstance(myManager.getProject());
159       final List<VirtualFile> sourceRoots = rootManager.getModuleSourceRoots(JavaModuleSourceRootTypes.SOURCES);
160       final ProjectFileIndex fileIndex = rootManager.getFileIndex();
161       for (final VirtualFile sourceRoot : sourceRoots) {
162         if (sourceRoot.isDirectory()) {
163           final String packageName = fileIndex.getPackageNameByDirectory(sourceRoot);
164           if (packageName != null && !packageName.isEmpty()) {
165             names.add(packageName);
166           }
167         }
168       }
169       myNontrivialPackagePrefixes = names;
170     }
171     return names;
172   }
173
174   @NotNull
175   @Override
176   public Collection<PsiJavaModule> findModules(@NotNull String moduleName, @NotNull GlobalSearchScope scope) {
177     GlobalSearchScope excludingScope = new LibSrcExcludingScope(scope);
178
179     List<PsiJavaModule> results = new ArrayList<>(JavaModuleNameIndex.getInstance().get(moduleName, myManager.getProject(), excludingScope));
180
181     Collection<VirtualFile> jars = JavaAutoModuleNameIndex.getFilesByKey(moduleName, excludingScope);
182     if (!jars.isEmpty()) {
183       jars.stream().map(f -> LightJavaModule.getModule(myManager, f)).forEach(results::add);
184     }
185
186     return upgradeModules(sortModules(results, scope), moduleName, scope);
187   }
188
189   private static class LibSrcExcludingScope extends DelegatingGlobalSearchScope {
190     private final ProjectFileIndex myIndex;
191
192     private LibSrcExcludingScope(@NotNull GlobalSearchScope baseScope) {
193       super(baseScope);
194       myIndex = ProjectFileIndex.getInstance(requireNonNull(baseScope.getProject()));
195     }
196
197     @Override
198     public boolean contains(@NotNull VirtualFile file) {
199       return super.contains(file) && (!myIndex.isInLibrarySource(file) || myIndex.isInLibraryClasses(file));
200     }
201   }
202
203   private static Collection<PsiJavaModule> sortModules(Collection<PsiJavaModule> modules, GlobalSearchScope scope) {
204     if (modules.size() > 1) {
205       List<PsiJavaModule> list = new ArrayList<>(modules);
206       list.sort((m1, m2) -> scope.compare(PsiImplUtil.getModuleVirtualFile(m2), PsiImplUtil.getModuleVirtualFile(m1)));
207       modules = list;
208     }
209     return modules;
210   }
211
212   private static Collection<PsiJavaModule> upgradeModules(Collection<PsiJavaModule> modules, String moduleName, GlobalSearchScope scope) {
213     if (modules.size() > 1 && PsiJavaModule.UPGRADEABLE.contains(moduleName) && scope instanceof ModuleWithDependenciesScope) {
214       Module module = ((ModuleWithDependenciesScope)scope).getModule();
215       boolean isModular = Stream.of(ModuleRootManager.getInstance(module).getSourceRoots(true))
216         .filter(scope::contains)
217         .anyMatch(root -> root.findChild(PsiJavaModule.MODULE_INFO_FILE) != null);
218       if (isModular) {
219         List<PsiJavaModule> list = new ArrayList<>(modules);
220
221         ModuleFileIndex index = ModuleRootManager.getInstance(module).getFileIndex();
222         for (ListIterator<PsiJavaModule> i = list.listIterator(); i.hasNext(); ) {
223           PsiJavaModule candidate = i.next();
224           if (index.getOrderEntryForFile(PsiImplUtil.getModuleVirtualFile(candidate)) instanceof JdkOrderEntry) {
225             if (i.previousIndex() > 0) {
226               i.remove();  // not at the top -> is upgraded
227             }
228             else {
229               list = Collections.singletonList(candidate);  // shadows subsequent modules
230               break;
231             }
232           }
233         }
234
235         if (list.size() != modules.size()) {
236           modules = list;
237         }
238       }
239     }
240
241     return modules;
242   }
243 }