add DumbService.filterByDumbAwareness(T[])
[idea/community.git] / java / java-psi-impl / src / com / intellij / psi / impl / JavaPsiFacadeImpl.java
1 /*
2  * Copyright 2000-2015 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.psi.impl;
17
18 import com.intellij.openapi.Disposable;
19 import com.intellij.openapi.progress.ProgressIndicatorProvider;
20 import com.intellij.openapi.project.DumbService;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.util.Comparing;
23 import com.intellij.openapi.util.Condition;
24 import com.intellij.openapi.util.Conditions;
25 import com.intellij.openapi.util.text.StringUtil;
26 import com.intellij.openapi.vfs.VirtualFileFilter;
27 import com.intellij.psi.*;
28 import com.intellij.psi.impl.file.impl.JavaFileManager;
29 import com.intellij.psi.impl.source.DummyHolderFactory;
30 import com.intellij.psi.impl.source.JavaDummyHolder;
31 import com.intellij.psi.impl.source.JavaDummyHolderFactory;
32 import com.intellij.psi.impl.source.resolve.FileContextUtil;
33 import com.intellij.psi.search.GlobalSearchScope;
34 import com.intellij.psi.util.PsiModificationTracker;
35 import com.intellij.reference.SoftReference;
36 import com.intellij.util.ConcurrencyUtil;
37 import com.intellij.util.Processor;
38 import com.intellij.util.SmartList;
39 import com.intellij.util.containers.ContainerUtil;
40 import com.intellij.util.messages.MessageBus;
41 import gnu.trove.THashSet;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.TestOnly;
44
45 import java.util.*;
46 import java.util.concurrent.ConcurrentMap;
47
48 /**
49  * @author max
50  */
51 public class JavaPsiFacadeImpl extends JavaPsiFacadeEx {
52   private volatile PsiElementFinder[] myElementFinders;
53   private final PsiConstantEvaluationHelper myConstantEvaluationHelper;
54   private volatile SoftReference<ConcurrentMap<String, PsiPackage>> myPackageCache;
55   private final Project myProject;
56   private final JavaFileManager myFileManager;
57
58   public JavaPsiFacadeImpl(Project project,
59                            PsiManager psiManager,
60                            JavaFileManager javaFileManager,
61                            MessageBus bus) {
62     myProject = project;
63     myFileManager = javaFileManager;
64     myConstantEvaluationHelper = new PsiConstantEvaluationHelperImpl();
65
66     final PsiModificationTracker modificationTracker = psiManager.getModificationTracker();
67
68     if (bus != null) {
69       bus.connect().subscribe(PsiModificationTracker.TOPIC, new PsiModificationTracker.Listener() {
70         private long lastTimeSeen = -1L;
71
72         @Override
73         public void modificationCountChanged() {
74           final long now = modificationTracker.getJavaStructureModificationCount();
75           if (lastTimeSeen != now) {
76             lastTimeSeen = now;
77             myPackageCache = null;
78           }
79         }
80       });
81     }
82
83     DummyHolderFactory.setFactory(new JavaDummyHolderFactory());
84   }
85
86   @Override
87   public PsiClass findClass(@NotNull final String qualifiedName, @NotNull GlobalSearchScope scope) {
88     ProgressIndicatorProvider.checkCanceled(); // We hope this method is being called often enough to cancel daemon processes smoothly
89
90     if (shouldUseSlowResolve()) {
91       PsiClass[] classes = findClassesInDumbMode(qualifiedName, scope);
92       if (classes.length != 0) {
93         return classes[0];
94       }
95       return null;
96     }
97
98     for (PsiElementFinder finder : finders()) {
99       PsiClass aClass = finder.findClass(qualifiedName, scope);
100       if (aClass != null) return aClass;
101     }
102
103     return null;
104   }
105
106   @NotNull
107   private PsiClass[] findClassesInDumbMode(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) {
108     final String packageName = StringUtil.getPackageName(qualifiedName);
109     final PsiPackage pkg = findPackage(packageName);
110     final String className = StringUtil.getShortName(qualifiedName);
111     if (pkg == null && packageName.length() < qualifiedName.length()) {
112       PsiClass[] containingClasses = findClassesInDumbMode(packageName, scope);
113       if (containingClasses.length == 1) {
114         return PsiElementFinder.filterByName(className, containingClasses[0].getInnerClasses());
115       }
116
117       return PsiClass.EMPTY_ARRAY;
118     }
119
120     if (pkg == null || !pkg.containsClassNamed(className)) {
121       return PsiClass.EMPTY_ARRAY;
122     }
123
124     return pkg.findClassByShortName(className, scope);
125   }
126
127   @Override
128   @NotNull
129   public PsiClass[] findClasses(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) {
130     if (shouldUseSlowResolve()) {
131       return findClassesInDumbMode(qualifiedName, scope);
132     }
133
134     List<PsiClass> classes = new SmartList<PsiClass>();
135     for (PsiElementFinder finder : finders()) {
136       PsiClass[] finderClasses = finder.findClasses(qualifiedName, scope);
137       ContainerUtil.addAll(classes, finderClasses);
138     }
139
140     return classes.toArray(new PsiClass[classes.size()]);
141   }
142
143   private boolean shouldUseSlowResolve() {
144     DumbService dumbService = DumbService.getInstance(getProject());
145     return dumbService.isDumb() && dumbService.isAlternativeResolveEnabled();
146   }
147
148   @NotNull
149   private PsiElementFinder[] finders() {
150     PsiElementFinder[] answer = myElementFinders;
151     if (answer == null) {
152       answer = calcFinders();
153       myElementFinders = answer;
154     }
155
156     return answer;
157   }
158
159   @NotNull
160   protected PsiElementFinder[] calcFinders() {
161     List<PsiElementFinder> elementFinders = new ArrayList<PsiElementFinder>();
162     ContainerUtil.addAll(elementFinders, myProject.getExtensions(PsiElementFinder.EP_NAME));
163     return elementFinders.toArray(new PsiElementFinder[elementFinders.size()]);
164   }
165
166   @Override
167   @NotNull
168   public PsiConstantEvaluationHelper getConstantEvaluationHelper() {
169     return myConstantEvaluationHelper;
170   }
171
172   @Override
173   public PsiPackage findPackage(@NotNull String qualifiedName) {
174     ConcurrentMap<String, PsiPackage> cache = SoftReference.dereference(myPackageCache);
175     if (cache == null) {
176       myPackageCache = new SoftReference<ConcurrentMap<String, PsiPackage>>(cache = ContainerUtil.newConcurrentMap());
177     }
178
179     PsiPackage aPackage = cache.get(qualifiedName);
180     if (aPackage != null) {
181       return aPackage;
182     }
183
184     for (PsiElementFinder finder : filteredFinders()) {
185       aPackage = finder.findPackage(qualifiedName);
186       if (aPackage != null) {
187         return ConcurrencyUtil.cacheOrGet(cache, qualifiedName, aPackage);
188       }
189     }
190
191     return null;
192   }
193
194   @NotNull
195   private PsiElementFinder[] filteredFinders() {
196     DumbService dumbService = DumbService.getInstance(getProject());
197     PsiElementFinder[] finders = finders();
198     if (dumbService.isDumb()) {
199       List<PsiElementFinder> list = dumbService.filterByDumbAwareness(finders);
200       finders = list.toArray(new PsiElementFinder[list.size()]);
201     }
202     return finders;
203   }
204
205   @Override
206   @NotNull
207   public PsiJavaParserFacade getParserFacade() {
208     return getElementFactory(); // TODO: lighter implementation which doesn't mark all the elements as generated.
209   }
210
211   @Override
212   @NotNull
213   public PsiResolveHelper getResolveHelper() {
214     return PsiResolveHelper.SERVICE.getInstance(myProject);
215   }
216
217   @Override
218   @NotNull
219   public PsiNameHelper getNameHelper() {
220     return PsiNameHelper.getInstance(myProject);
221   }
222
223   @NotNull
224   public Set<String> getClassNames(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
225     Set<String> result = new THashSet<String>();
226     for (PsiElementFinder finder : filteredFinders()) {
227       result.addAll(finder.getClassNames(psiPackage, scope));
228     }
229     return result;
230   }
231
232   @NotNull
233   public PsiClass[] getClasses(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
234     List<PsiClass> result = null;
235     for (PsiElementFinder finder : filteredFinders()) {
236       PsiClass[] classes = finder.getClasses(psiPackage, scope);
237       if (classes.length == 0) continue;
238       if (result == null) result = new ArrayList<PsiClass>();
239       ContainerUtil.addAll(result, classes);
240     }
241
242     return result == null ? PsiClass.EMPTY_ARRAY : result.toArray(new PsiClass[result.size()]);
243   }
244
245   @NotNull
246   public PsiFile[] getPackageFiles(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
247     Condition<PsiFile> filter = null;
248
249     for (PsiElementFinder finder : filteredFinders()) {
250       Condition<PsiFile> finderFilter = finder.getPackageFilesFilter(psiPackage, scope);
251       if (finderFilter != null) {
252         if (filter == null) {
253           filter = finderFilter;
254         }
255         else {
256           filter = Conditions.and(filter, finderFilter);
257         }
258       }
259     }
260
261     Set<PsiFile> result = new LinkedHashSet<PsiFile>();
262     PsiDirectory[] directories = psiPackage.getDirectories(scope);
263     for (PsiDirectory directory : directories) {
264       for (PsiFile file : directory.getFiles()) {
265         if (filter == null || filter.value(file)) {
266           result.add(file);
267         }
268       }
269     }
270
271     for (PsiElementFinder finder : filteredFinders()) {
272       Collections.addAll(result, finder.getPackageFiles(psiPackage, scope));
273     }
274     return result.toArray(new PsiFile[result.size()]);
275   }
276
277   public boolean processPackageDirectories(@NotNull PsiPackage psiPackage,
278                                            @NotNull GlobalSearchScope scope,
279                                            @NotNull Processor<PsiDirectory> consumer,
280                                            boolean includeLibrarySources) {
281     for (PsiElementFinder finder : filteredFinders()) {
282       if (!finder.processPackageDirectories(psiPackage, scope, consumer, includeLibrarySources)) {
283         return false;
284       }
285     }
286     return true;
287   }
288
289   @NotNull
290   public PsiPackage[] getSubPackages(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
291     LinkedHashMap<String, PsiPackage> result = new LinkedHashMap<String, PsiPackage>();
292     for (PsiElementFinder finder : filteredFinders()) {
293       // Ensure uniqueness of names in the returned list of subpackages. If a plugin PsiElementFinder
294       // returns the same package from its getSubPackages() implementation that Java already knows about
295       // (the Kotlin plugin can do that), the Java package takes precedence.
296       PsiPackage[] packages = finder.getSubPackages(psiPackage, scope);
297       for (PsiPackage aPackage : packages) {
298         if (result.get(aPackage.getName()) == null) {
299           result.put(aPackage.getName(), aPackage);
300         }
301       }
302     }
303     return result.values().toArray(new PsiPackage[result.size()]);
304   }
305
306   @Override
307   public boolean isPartOfPackagePrefix(@NotNull String packageName) {
308     final Collection<String> packagePrefixes = myFileManager.getNonTrivialPackagePrefixes();
309     for (final String subpackageName : packagePrefixes) {
310       if (PsiNameHelper.isSubpackageOf(subpackageName, packageName)) return true;
311     }
312     return false;
313   }
314
315   @Override
316   public boolean isInPackage(@NotNull PsiElement element, @NotNull PsiPackage aPackage) {
317     final PsiFile file = FileContextUtil.getContextFile(element);
318     if (file instanceof JavaDummyHolder) {
319       return ((JavaDummyHolder) file).isInPackage(aPackage);
320     }
321     if (file instanceof PsiJavaFile) {
322       final String packageName = ((PsiJavaFile) file).getPackageName();
323       return packageName.equals(aPackage.getQualifiedName());
324     }
325     return false;
326   }
327
328   @Override
329   public boolean arePackagesTheSame(@NotNull PsiElement element1, @NotNull PsiElement element2) {
330     PsiFile file1 = FileContextUtil.getContextFile(element1);
331     PsiFile file2 = FileContextUtil.getContextFile(element2);
332     if (Comparing.equal(file1, file2)) return true;
333     if (file1 instanceof JavaDummyHolder && file2 instanceof JavaDummyHolder) return true;
334     if (file1 instanceof JavaDummyHolder || file2 instanceof JavaDummyHolder) {
335       JavaDummyHolder dummyHolder = (JavaDummyHolder) (file1 instanceof JavaDummyHolder ? file1 : file2);
336       PsiElement other = file1 instanceof JavaDummyHolder ? file2 : file1;
337       return dummyHolder.isSamePackage(other);
338     }
339     if (!(file1 instanceof PsiClassOwner)) return false;
340     if (!(file2 instanceof PsiClassOwner)) return false;
341     String package1 = ((PsiClassOwner) file1).getPackageName();
342     String package2 = ((PsiClassOwner) file2).getPackageName();
343     return Comparing.equal(package1, package2);
344   }
345
346   @Override
347   @NotNull
348   public Project getProject() {
349     return myProject;
350   }
351
352   @Override
353   @NotNull
354   public PsiElementFactory getElementFactory() {
355     return PsiElementFactory.SERVICE.getInstance(myProject);
356   }
357
358   @TestOnly
359   @Override
360   public void setAssertOnFileLoadingFilter(@NotNull final VirtualFileFilter filter, Disposable parentDisposable) {
361     ((PsiManagerImpl)PsiManager.getInstance(myProject)).setAssertOnFileLoadingFilter(filter, parentDisposable);
362   }
363 }