10a9e5d97e164c98f89ec50be440bcde533b334e
[idea/community.git] / java / java-indexing-impl / src / com / intellij / psi / impl / file / impl / JavaFileManagerImpl.java
1 // Copyright 2000-2018 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 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   private final PackageIndex myPackageIndex;
48
49   public JavaFileManagerImpl(Project project) {
50     myManager = PsiManagerEx.getInstanceEx(project);
51     myPackageIndex = PackageIndex.getInstance(myManager.getProject());
52     project.getMessageBus().connect().subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() {
53       @Override
54       public void rootsChanged(@NotNull final ModuleRootEvent event) {
55         myNontrivialPackagePrefixes = null;
56       }
57     });
58   }
59
60   @Override
61   public void dispose() {
62     myDisposed = true;
63   }
64
65   @Override
66   @Nullable
67   public PsiPackage findPackage(@NotNull String packageName) {
68     Query<VirtualFile> dirs = myPackageIndex.getDirsByPackageName(packageName, true);
69     if (dirs.findFirst() == null) return null;
70     return new PsiPackageImpl(myManager, packageName);
71   }
72
73   @NotNull
74   @Override
75   public PsiClass[] findClasses(@NotNull String qName, @NotNull final GlobalSearchScope scope) {
76     List<Pair<PsiClass, VirtualFile>> result = doFindClasses(qName, scope);
77
78     int count = result.size();
79     if (count == 0) return PsiClass.EMPTY_ARRAY;
80     if (count == 1) return new PsiClass[] {result.get(0).getFirst()};
81
82     ContainerUtil.quickSort(result, (o1, o2) -> scope.compare(o2.getSecond(), o1.getSecond()));
83
84     return result.stream().map(p -> p.getFirst()).toArray(PsiClass[]::new);
85   }
86
87   @NotNull
88   private List<Pair<PsiClass, VirtualFile>> doFindClasses(@NotNull String qName, @NotNull final GlobalSearchScope scope) {
89     final Collection<PsiClass> classes = JavaFullClassNameIndex.getInstance().get(qName.hashCode(), myManager.getProject(), scope);
90     if (classes.isEmpty()) return Collections.emptyList();
91     List<Pair<PsiClass, VirtualFile>> result = new ArrayList<>(classes.size());
92     for (PsiClass aClass : classes) {
93       final String qualifiedName = aClass.getQualifiedName();
94       if (qualifiedName == null || !qualifiedName.equals(qName)) continue;
95
96       PsiFile file = aClass.getContainingFile();
97       if (file == null) {
98         throw new AssertionError("No file for class: " + aClass + " of " + aClass.getClass());
99       }
100       final boolean valid = file.isValid();
101       VirtualFile vFile = file.getVirtualFile();
102       if (!valid) {
103         LOG.error("Invalid file " +
104                   file + "; virtualFile:" + vFile +
105                   (vFile != null && !vFile.isValid() ? " (invalid)" : "") +
106                   "; id=" + (vFile == null ? 0 : ((VirtualFileWithId)vFile).getId()),
107                   new PsiInvalidElementAccessException(aClass));
108         continue;
109       }
110       if (!hasAcceptablePackage(vFile)) continue;
111
112       result.add(Pair.create(aClass, vFile));
113     }
114
115     return result;
116   }
117
118   @Override
119   @Nullable
120   public PsiClass findClass(@NotNull String qName, @NotNull GlobalSearchScope scope) {
121     LOG.assertTrue(!myDisposed);
122     VirtualFile bestFile = null;
123     PsiClass bestClass = null;
124     List<Pair<PsiClass, VirtualFile>> result = doFindClasses(qName, scope);
125
126     //noinspection ForLoopReplaceableByForEach
127     for (int i = 0; i < result.size(); i++) {
128       Pair<PsiClass, VirtualFile> pair = result.get(i);
129       VirtualFile vFile = pair.getSecond();
130       if (bestFile == null || scope.compare(vFile, bestFile) > 0) {
131         bestFile = vFile;
132         bestClass = pair.getFirst();
133       }
134     }
135
136     return bestClass;
137   }
138
139   private boolean hasAcceptablePackage(@NotNull VirtualFile vFile) {
140     if (FileTypeRegistry.getInstance().isFileOfType(vFile, JavaClassFileType.INSTANCE)) {
141       // See IDEADEV-5626
142       final VirtualFile root = ProjectRootManager.getInstance(myManager.getProject()).getFileIndex().getClassRootForFile(vFile);
143       VirtualFile parent = vFile.getParent();
144       final PsiNameHelper nameHelper = PsiNameHelper.getInstance(myManager.getProject());
145       while (parent != null && !Comparing.equal(parent, root)) {
146         if (!nameHelper.isIdentifier(parent.getName())) return false;
147         parent = parent.getParent();
148       }
149     }
150
151     return true;
152   }
153
154   @NotNull
155   @Override
156   public Collection<String> getNonTrivialPackagePrefixes() {
157     Set<String> names = myNontrivialPackagePrefixes;
158     if (names == null) {
159       names = new HashSet<>();
160       final ProjectRootManager rootManager = ProjectRootManager.getInstance(myManager.getProject());
161       final List<VirtualFile> sourceRoots = rootManager.getModuleSourceRoots(JavaModuleSourceRootTypes.SOURCES);
162       final ProjectFileIndex fileIndex = rootManager.getFileIndex();
163       for (final VirtualFile sourceRoot : sourceRoots) {
164         if (sourceRoot.isDirectory()) {
165           final String packageName = fileIndex.getPackageNameByDirectory(sourceRoot);
166           if (packageName != null && !packageName.isEmpty()) {
167             names.add(packageName);
168           }
169         }
170       }
171       myNontrivialPackagePrefixes = names;
172     }
173     return names;
174   }
175
176   @NotNull
177   @Override
178   public Collection<PsiJavaModule> findModules(@NotNull String moduleName, @NotNull GlobalSearchScope scope) {
179     GlobalSearchScope excludingScope = new LibSrcExcludingScope(scope);
180
181     List<PsiJavaModule> results = new ArrayList<>(JavaModuleNameIndex.getInstance().get(moduleName, myManager.getProject(), excludingScope));
182
183     Collection<VirtualFile> jars = JavaAutoModuleNameIndex.getFilesByKey(moduleName, excludingScope);
184     if (!jars.isEmpty()) {
185       jars.stream().map(f -> LightJavaModule.getModule(myManager, f)).forEach(results::add);
186     }
187
188     return upgradeModules(sortModules(results, scope), moduleName, scope);
189   }
190
191   private static class LibSrcExcludingScope extends DelegatingGlobalSearchScope {
192     private final ProjectFileIndex myIndex;
193
194     private LibSrcExcludingScope(@NotNull GlobalSearchScope baseScope) {
195       super(baseScope);
196       myIndex = ProjectFileIndex.getInstance(requireNonNull(baseScope.getProject()));
197     }
198
199     @Override
200     public boolean contains(@NotNull VirtualFile file) {
201       return super.contains(file) && (!myIndex.isInLibrarySource(file) || myIndex.isInLibraryClasses(file));
202     }
203   }
204
205   private static Collection<PsiJavaModule> sortModules(Collection<PsiJavaModule> modules, GlobalSearchScope scope) {
206     if (modules.size() > 1) {
207       List<PsiJavaModule> list = new ArrayList<>(modules);
208       list.sort((m1, m2) -> scope.compare(PsiImplUtil.getModuleVirtualFile(m2), PsiImplUtil.getModuleVirtualFile(m1)));
209       modules = list;
210     }
211     return modules;
212   }
213
214   private static Collection<PsiJavaModule> upgradeModules(Collection<PsiJavaModule> modules, String moduleName, GlobalSearchScope scope) {
215     if (modules.size() > 1 && PsiJavaModule.UPGRADEABLE.contains(moduleName) && scope instanceof ModuleWithDependenciesScope) {
216       Module module = ((ModuleWithDependenciesScope)scope).getModule();
217       boolean isModular = Stream.of(ModuleRootManager.getInstance(module).getSourceRoots(true))
218         .filter(scope::contains)
219         .anyMatch(root -> root.findChild(PsiJavaModule.MODULE_INFO_FILE) != null);
220       if (isModular) {
221         List<PsiJavaModule> list = new ArrayList<>(modules);
222
223         ModuleFileIndex index = ModuleRootManager.getInstance(module).getFileIndex();
224         for (ListIterator<PsiJavaModule> i = list.listIterator(); i.hasNext(); ) {
225           PsiJavaModule candidate = i.next();
226           if (index.getOrderEntryForFile(PsiImplUtil.getModuleVirtualFile(candidate)) instanceof JdkOrderEntry) {
227             if (i.previousIndex() > 0) {
228               i.remove();  // not at the top -> is upgraded
229             }
230             else {
231               list = Collections.singletonList(candidate);  // shadows subsequent modules
232               break;
233             }
234           }
235         }
236
237         if (list.size() != modules.size()) {
238           modules = list;
239         }
240       }
241     }
242
243     return modules;
244   }
245 }