2 * Copyright 2000-2015 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.
16 package com.intellij.psi.impl;
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;
46 import java.util.concurrent.ConcurrentMap;
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;
58 public JavaPsiFacadeImpl(Project project,
59 PsiManager psiManager,
60 JavaFileManager javaFileManager,
63 myFileManager = javaFileManager;
64 myConstantEvaluationHelper = new PsiConstantEvaluationHelperImpl();
66 final PsiModificationTracker modificationTracker = psiManager.getModificationTracker();
69 bus.connect().subscribe(PsiModificationTracker.TOPIC, new PsiModificationTracker.Listener() {
70 private long lastTimeSeen = -1L;
73 public void modificationCountChanged() {
74 final long now = modificationTracker.getJavaStructureModificationCount();
75 if (lastTimeSeen != now) {
77 myPackageCache = null;
83 DummyHolderFactory.setFactory(new JavaDummyHolderFactory());
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
90 if (shouldUseSlowResolve()) {
91 PsiClass[] classes = findClassesInDumbMode(qualifiedName, scope);
92 if (classes.length != 0) {
98 for (PsiElementFinder finder : finders()) {
99 PsiClass aClass = finder.findClass(qualifiedName, scope);
100 if (aClass != null) return aClass;
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());
117 return PsiClass.EMPTY_ARRAY;
120 if (pkg == null || !pkg.containsClassNamed(className)) {
121 return PsiClass.EMPTY_ARRAY;
124 return pkg.findClassByShortName(className, scope);
129 public PsiClass[] findClasses(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) {
130 if (shouldUseSlowResolve()) {
131 return findClassesInDumbMode(qualifiedName, scope);
134 List<PsiClass> classes = new SmartList<PsiClass>();
135 for (PsiElementFinder finder : finders()) {
136 PsiClass[] finderClasses = finder.findClasses(qualifiedName, scope);
137 ContainerUtil.addAll(classes, finderClasses);
140 return classes.toArray(new PsiClass[classes.size()]);
143 private boolean shouldUseSlowResolve() {
144 DumbService dumbService = DumbService.getInstance(getProject());
145 return dumbService.isDumb() && dumbService.isAlternativeResolveEnabled();
149 private PsiElementFinder[] finders() {
150 PsiElementFinder[] answer = myElementFinders;
151 if (answer == null) {
152 answer = calcFinders();
153 myElementFinders = answer;
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()]);
168 public PsiConstantEvaluationHelper getConstantEvaluationHelper() {
169 return myConstantEvaluationHelper;
173 public PsiPackage findPackage(@NotNull String qualifiedName) {
174 ConcurrentMap<String, PsiPackage> cache = SoftReference.dereference(myPackageCache);
176 myPackageCache = new SoftReference<ConcurrentMap<String, PsiPackage>>(cache = ContainerUtil.newConcurrentMap());
179 PsiPackage aPackage = cache.get(qualifiedName);
180 if (aPackage != null) {
184 for (PsiElementFinder finder : filteredFinders()) {
185 aPackage = finder.findPackage(qualifiedName);
186 if (aPackage != null) {
187 return ConcurrencyUtil.cacheOrGet(cache, qualifiedName, aPackage);
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()]);
207 public PsiJavaParserFacade getParserFacade() {
208 return getElementFactory(); // TODO: lighter implementation which doesn't mark all the elements as generated.
213 public PsiResolveHelper getResolveHelper() {
214 return PsiResolveHelper.SERVICE.getInstance(myProject);
219 public PsiNameHelper getNameHelper() {
220 return PsiNameHelper.getInstance(myProject);
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));
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);
242 return result == null ? PsiClass.EMPTY_ARRAY : result.toArray(new PsiClass[result.size()]);
246 public PsiFile[] getPackageFiles(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
247 Condition<PsiFile> filter = null;
249 for (PsiElementFinder finder : filteredFinders()) {
250 Condition<PsiFile> finderFilter = finder.getPackageFilesFilter(psiPackage, scope);
251 if (finderFilter != null) {
252 if (filter == null) {
253 filter = finderFilter;
256 filter = Conditions.and(filter, finderFilter);
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)) {
271 for (PsiElementFinder finder : filteredFinders()) {
272 Collections.addAll(result, finder.getPackageFiles(psiPackage, scope));
274 return result.toArray(new PsiFile[result.size()]);
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)) {
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);
303 return result.values().toArray(new PsiPackage[result.size()]);
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;
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);
321 if (file instanceof PsiJavaFile) {
322 final String packageName = ((PsiJavaFile) file).getPackageName();
323 return packageName.equals(aPackage.getQualifiedName());
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);
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);
348 public Project getProject() {
354 public PsiElementFactory getElementFactory() {
355 return PsiElementFactory.SERVICE.getInstance(myProject);
360 public void setAssertOnFileLoadingFilter(@NotNull final VirtualFileFilter filter, Disposable parentDisposable) {
361 ((PsiManagerImpl)PsiManager.getInstance(myProject)).setAssertOnFileLoadingFilter(filter, parentDisposable);