/* * Copyright 2010-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jetbrains.kotlin.resolve.jvm; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.PackageIndex; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.impl.PsiElementFinderImpl; import com.intellij.psi.impl.file.PsiPackageImpl; import com.intellij.psi.impl.file.impl.JavaFileManager; import com.intellij.psi.impl.light.LightModifierList; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiModificationTracker; import com.intellij.reference.SoftReference; import com.intellij.util.CommonProcessors; import com.intellij.util.ConcurrencyUtil; import com.intellij.util.Query; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.messages.MessageBus; import kotlin.collections.ArraysKt; import kotlin.collections.CollectionsKt; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.asJava.KtLightClassMarker; import org.jetbrains.kotlin.idea.KotlinLanguage; import org.jetbrains.kotlin.load.java.JavaClassFinderImpl; import org.jetbrains.kotlin.load.java.structure.JavaClass; import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl; import org.jetbrains.kotlin.name.ClassId; import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentMap; public class KotlinJavaPsiFacade { private volatile KotlinPsiElementFinderWrapper[] elementFinders; private static class PackageCache { final ConcurrentMap<Pair<String, GlobalSearchScope>, PsiPackage> packageInScopeCache = ContainerUtil.newConcurrentMap(); final ConcurrentMap<String, Boolean> hasPackageInAllScopeCache = ContainerUtil.newConcurrentMap(); } private volatile SoftReference<PackageCache> packageCache; private final Project project; private final LightModifierList emptyModifierList; public static KotlinJavaPsiFacade getInstance(Project project) { return ServiceManager.getService(project, KotlinJavaPsiFacade.class); } public KotlinJavaPsiFacade(@NotNull Project project) { this.project = project; emptyModifierList = new LightModifierList(PsiManager.getInstance(project), KotlinLanguage.INSTANCE); PsiModificationTracker modificationTracker = PsiManager.getInstance(project).getModificationTracker(); MessageBus bus = project.getMessageBus(); bus.connect().subscribe(PsiModificationTracker.TOPIC, new PsiModificationTracker.Listener() { private long lastTimeSeen = -1L; @Override public void modificationCountChanged() { long now = modificationTracker.getJavaStructureModificationCount(); if (lastTimeSeen != now) { lastTimeSeen = now; packageCache = null; } } }); } public LightModifierList getEmptyModifierList() { return emptyModifierList; } public JavaClass findClass(@NotNull ClassId classId, @NotNull GlobalSearchScope scope) { ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); // We hope this method is being called often enough to cancel daemon processes smoothly String qualifiedName = classId.asSingleFqName().asString(); if (shouldUseSlowResolve()) { PsiClass[] classes = findClassesInDumbMode(qualifiedName, scope); if (classes.length != 0) { return createJavaClass(classId, classes[0]); } return null; } for (KotlinPsiElementFinderWrapper finder : finders()) { if (finder instanceof CliFinder) { JavaClass aClass = ((CliFinder) finder).findClass(classId, scope); if (aClass != null) return aClass; } else { PsiClass aClass = finder.findClass(qualifiedName, scope); if (aClass != null) { if (scope instanceof JavaClassFinderImpl.FilterOutKotlinSourceFilesScope) { GlobalSearchScope baseScope = ((JavaClassFinderImpl.FilterOutKotlinSourceFilesScope) scope).getBase(); boolean isSourcesScope = baseScope instanceof GlobalSearchScopeWithModuleSources; if (!isSourcesScope) { Object originalFinder = (finder instanceof KotlinPsiElementFinderWrapperImpl) ? ((KotlinPsiElementFinderWrapperImpl) finder).getOriginal() : finder; // Temporary fix for #KT-12402 boolean isAndroidDataBindingClassWriter = originalFinder.getClass().getName() .equals("com.android.tools.idea.databinding.DataBindingClassFinder"); boolean isAndroidDataBindingComponentClassWriter = originalFinder.getClass().getName() .equals("com.android.tools.idea.databinding.DataBindingComponentClassFinder"); if (isAndroidDataBindingClassWriter || isAndroidDataBindingComponentClassWriter) { continue; } } } return createJavaClass(classId, aClass); } } } return null; } @NotNull private static JavaClass createJavaClass(@NotNull ClassId classId, @NotNull PsiClass psiClass) { JavaClassImpl javaClass = new JavaClassImpl(psiClass); FqName fqName = classId.asSingleFqName(); if (!fqName.equals(javaClass.getFqName())) { throw new IllegalStateException("Requested " + fqName + ", got " + javaClass.getFqName()); } if (psiClass instanceof KtLightClassMarker) { throw new IllegalStateException("Kotlin light classes should not be found by JavaPsiFacade, resolving: " + fqName); } return javaClass; } @Nullable public Set<String> knownClassNamesInPackage(@NotNull FqName packageFqName) { KotlinPsiElementFinderWrapper[] finders = finders(); if (finders.length == 1 && finders[0] instanceof CliFinder) { return ((CliFinder) finders[0]).knownClassNamesInPackage(packageFqName); } return null; } @NotNull private PsiClass[] findClassesInDumbMode(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { String packageName = StringUtil.getPackageName(qualifiedName); PsiPackage pkg = findPackage(packageName, scope); String className = StringUtil.getShortName(qualifiedName); if (pkg == null && packageName.length() < qualifiedName.length()) { PsiClass[] containingClasses = findClassesInDumbMode(packageName, scope); if (containingClasses.length == 1) { return PsiElementFinder.filterByName(className, containingClasses[0].getInnerClasses()); } return PsiClass.EMPTY_ARRAY; } if (pkg == null || !pkg.containsClassNamed(className)) { return PsiClass.EMPTY_ARRAY; } return pkg.findClassByShortName(className, scope); } private boolean shouldUseSlowResolve() { DumbService dumbService = DumbService.getInstance(getProject()); return dumbService.isDumb() && dumbService.isAlternativeResolveEnabled(); } @NotNull private KotlinPsiElementFinderWrapper[] finders() { KotlinPsiElementFinderWrapper[] answer = elementFinders; if (answer == null) { answer = calcFinders(); elementFinders = answer; } return answer; } @NotNull private KotlinPsiElementFinderWrapper[] calcFinders() { List<KotlinPsiElementFinderWrapper> elementFinders = new ArrayList<>(); JavaFileManager javaFileManager = findJavaFileManager(project); elementFinders.add( javaFileManager instanceof KotlinCliJavaFileManager ? new CliFinder((KotlinCliJavaFileManager) javaFileManager) : new NonCliFinder(project, javaFileManager) ); List<PsiElementFinder> nonKotlinFinders = ArraysKt.filter( getProject().getExtensions(PsiElementFinder.EP_NAME), finder -> (finder instanceof KotlinSafeClassFinder) || !(finder instanceof NonClasspathClassFinder || finder instanceof KotlinFinderMarker || finder instanceof PsiElementFinderImpl) ); elementFinders.addAll(CollectionsKt.map(nonKotlinFinders, KotlinJavaPsiFacade::wrap)); return elementFinders.toArray(new KotlinPsiElementFinderWrapper[elementFinders.size()]); } @NotNull private static JavaFileManager findJavaFileManager(@NotNull Project project) { JavaFileManager javaFileManager = ServiceManager.getService(project, JavaFileManager.class); if (javaFileManager == null) { throw new IllegalStateException("JavaFileManager component is not found in project"); } return javaFileManager; } public PsiPackage findPackage(@NotNull String qualifiedName, GlobalSearchScope searchScope) { PackageCache cache = SoftReference.dereference(packageCache); if (cache == null) { packageCache = new SoftReference<>(cache = new PackageCache()); } Pair<String, GlobalSearchScope> key = new Pair<>(qualifiedName, searchScope); PsiPackage aPackage = cache.packageInScopeCache.get(key); if (aPackage != null) { return aPackage; } KotlinPsiElementFinderWrapper[] finders = filteredFinders(); Boolean packageFoundInAllScope = cache.hasPackageInAllScopeCache.get(qualifiedName); if (packageFoundInAllScope != null) { if (!packageFoundInAllScope.booleanValue()) return null; // Package was found in AllScope with some of finders but is absent in packageCache for current scope. // We check only finders that depend on scope. for (KotlinPsiElementFinderWrapper finder : finders) { if (!finder.isSameResultForAnyScope()) { aPackage = finder.findPackage(qualifiedName, searchScope); if (aPackage != null) { return ConcurrencyUtil.cacheOrGet(cache.packageInScopeCache, key, aPackage); } } } } else { for (KotlinPsiElementFinderWrapper finder : finders) { aPackage = finder.findPackage(qualifiedName, searchScope); if (aPackage != null) { return ConcurrencyUtil.cacheOrGet(cache.packageInScopeCache, key, aPackage); } } boolean found = false; for (KotlinPsiElementFinderWrapper finder : finders) { if (!finder.isSameResultForAnyScope()) { aPackage = finder.findPackage(qualifiedName, GlobalSearchScope.allScope(project)); if (aPackage != null) { found = true; break; } } } cache.hasPackageInAllScopeCache.put(qualifiedName, found); } return null; } @NotNull private KotlinPsiElementFinderWrapper[] filteredFinders() { DumbService dumbService = DumbService.getInstance(getProject()); KotlinPsiElementFinderWrapper[] finders = finders(); if (dumbService.isDumb()) { List<KotlinPsiElementFinderWrapper> list = dumbService.filterByDumbAwareness(Arrays.asList(finders)); finders = list.toArray(new KotlinPsiElementFinderWrapper[list.size()]); } return finders; } @NotNull public Project getProject() { return project; } public static KotlinPsiElementFinderWrapper wrap(PsiElementFinder finder) { return finder instanceof DumbAware ? new KotlinPsiElementFinderWrapperImplDumbAware(finder) : new KotlinPsiElementFinderWrapperImpl(finder); } interface KotlinPsiElementFinderWrapper { PsiClass findClass(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope); PsiPackage findPackage(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope); boolean isSameResultForAnyScope(); } private static class KotlinPsiElementFinderWrapperImpl implements KotlinPsiElementFinderWrapper { private final PsiElementFinder finder; private KotlinPsiElementFinderWrapperImpl(@NotNull PsiElementFinder finder) { this.finder = finder; } public PsiElementFinder getOriginal() { return finder; } @Override public PsiClass findClass(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { return finder.findClass(qualifiedName, scope); } @Override public PsiPackage findPackage(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { // Original element finder can't search packages with scope return finder.findPackage(qualifiedName); } @Override public boolean isSameResultForAnyScope() { return true; } @Override public String toString() { return finder.toString(); } } private static class KotlinPsiElementFinderWrapperImplDumbAware extends KotlinPsiElementFinderWrapperImpl implements DumbAware { private KotlinPsiElementFinderWrapperImplDumbAware(PsiElementFinder finder) { super(finder); } } private static class CliFinder implements KotlinPsiElementFinderWrapper, DumbAware { private final KotlinCliJavaFileManager javaFileManager; public CliFinder(@NotNull KotlinCliJavaFileManager javaFileManager) { this.javaFileManager = javaFileManager; } @Override public PsiClass findClass(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { return javaFileManager.findClass(qualifiedName, scope); } public JavaClass findClass(@NotNull ClassId classId, @NotNull GlobalSearchScope scope) { return javaFileManager.findClass(classId, scope); } @Nullable public Set<String> knownClassNamesInPackage(@NotNull FqName packageFqName) { return javaFileManager.knownClassNamesInPackage(packageFqName); } @Override public PsiPackage findPackage(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { return javaFileManager.findPackage(qualifiedName); } @Override public boolean isSameResultForAnyScope() { return false; } } private static class NonCliFinder implements KotlinPsiElementFinderWrapper, DumbAware { private final JavaFileManager javaFileManager; private final PsiManager psiManager; private final PackageIndex packageIndex; public NonCliFinder(@NotNull Project project, @NotNull JavaFileManager javaFileManager) { this.javaFileManager = javaFileManager; this.packageIndex = PackageIndex.getInstance(project); this.psiManager = PsiManager.getInstance(project); } @Override public PsiClass findClass(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { return javaFileManager.findClass(qualifiedName, scope); } @Override public PsiPackage findPackage(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { Query<VirtualFile> dirs = packageIndex.getDirsByPackageName(qualifiedName, true); return hasDirectoriesInScope(dirs, scope) ? new PsiPackageImpl(psiManager, qualifiedName) : null; } @Override public boolean isSameResultForAnyScope() { return false; } private static boolean hasDirectoriesInScope(Query<VirtualFile> dirs, GlobalSearchScope scope) { CommonProcessors.FindProcessor<VirtualFile> findProcessor = new CommonProcessors.FindProcessor<VirtualFile>() { @Override protected boolean accept(VirtualFile file) { return scope.accept(file); } }; dirs.forEach(findProcessor); return findProcessor.isFound(); } } }