001 /* 002 * Copyright 2010-2015 JetBrains s.r.o. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017 package org.jetbrains.kotlin.resolve.jvm; 018 019 import com.intellij.openapi.components.ServiceManager; 020 import com.intellij.openapi.project.DumbAware; 021 import com.intellij.openapi.project.DumbService; 022 import com.intellij.openapi.project.Project; 023 import com.intellij.openapi.roots.PackageIndex; 024 import com.intellij.openapi.util.Pair; 025 import com.intellij.openapi.util.text.StringUtil; 026 import com.intellij.openapi.vfs.VirtualFile; 027 import com.intellij.psi.*; 028 import com.intellij.psi.impl.PsiElementFinderImpl; 029 import com.intellij.psi.impl.file.PsiPackageImpl; 030 import com.intellij.psi.impl.file.impl.JavaFileManager; 031 import com.intellij.psi.search.GlobalSearchScope; 032 import com.intellij.psi.util.PsiModificationTracker; 033 import com.intellij.reference.SoftReference; 034 import com.intellij.util.CommonProcessors; 035 import com.intellij.util.ConcurrencyUtil; 036 import com.intellij.util.Query; 037 import com.intellij.util.containers.ContainerUtil; 038 import com.intellij.util.messages.MessageBus; 039 import kotlin.collections.ArraysKt; 040 import kotlin.collections.CollectionsKt; 041 import kotlin.jvm.functions.Function1; 042 import org.jetbrains.annotations.NotNull; 043 import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus; 044 import org.jetbrains.kotlin.name.ClassId; 045 046 import java.util.ArrayList; 047 import java.util.Arrays; 048 import java.util.List; 049 import java.util.concurrent.ConcurrentMap; 050 051 public class KotlinJavaPsiFacade { 052 private volatile KotlinPsiElementFinderWrapper[] elementFinders; 053 054 private static class PackageCache { 055 final ConcurrentMap<Pair<String, GlobalSearchScope>, PsiPackage> packageInScopeCache = ContainerUtil.newConcurrentMap(); 056 final ConcurrentMap<String, Boolean> hasPackageInAllScopeCache = ContainerUtil.newConcurrentMap(); 057 } 058 059 private volatile SoftReference<PackageCache> packageCache; 060 061 private final Project project; 062 063 public static KotlinJavaPsiFacade getInstance(Project project) { 064 return ServiceManager.getService(project, KotlinJavaPsiFacade.class); 065 } 066 067 public KotlinJavaPsiFacade(@NotNull Project project) { 068 this.project = project; 069 070 final PsiModificationTracker modificationTracker = PsiManager.getInstance(project).getModificationTracker(); 071 MessageBus bus = project.getMessageBus(); 072 073 bus.connect().subscribe(PsiModificationTracker.TOPIC, new PsiModificationTracker.Listener() { 074 private long lastTimeSeen = -1L; 075 076 @Override 077 public void modificationCountChanged() { 078 long now = modificationTracker.getJavaStructureModificationCount(); 079 if (lastTimeSeen != now) { 080 lastTimeSeen = now; 081 082 packageCache = null; 083 } 084 } 085 }); 086 } 087 088 public PsiClass findClass(@NotNull ClassId classId, @NotNull GlobalSearchScope scope) { 089 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); // We hope this method is being called often enough to cancel daemon processes smoothly 090 091 String qualifiedName = classId.asSingleFqName().asString(); 092 093 if (shouldUseSlowResolve()) { 094 PsiClass[] classes = findClassesInDumbMode(qualifiedName, scope); 095 if (classes.length != 0) { 096 return classes[0]; 097 } 098 return null; 099 } 100 101 for (KotlinPsiElementFinderWrapper finder : finders()) { 102 if (finder instanceof KotlinPsiElementFinderImpl) { 103 PsiClass aClass = ((KotlinPsiElementFinderImpl) finder).findClass(classId, scope); 104 if (aClass != null) return aClass; 105 } 106 else { 107 PsiClass aClass = finder.findClass(qualifiedName, scope); 108 if (aClass != null) return aClass; 109 } 110 } 111 112 return null; 113 } 114 115 @NotNull 116 private PsiClass[] findClassesInDumbMode(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { 117 String packageName = StringUtil.getPackageName(qualifiedName); 118 PsiPackage pkg = findPackage(packageName, scope); 119 String className = StringUtil.getShortName(qualifiedName); 120 if (pkg == null && packageName.length() < qualifiedName.length()) { 121 PsiClass[] containingClasses = findClassesInDumbMode(packageName, scope); 122 if (containingClasses.length == 1) { 123 return PsiElementFinder.filterByName(className, containingClasses[0].getInnerClasses()); 124 } 125 126 return PsiClass.EMPTY_ARRAY; 127 } 128 129 if (pkg == null || !pkg.containsClassNamed(className)) { 130 return PsiClass.EMPTY_ARRAY; 131 } 132 133 return pkg.findClassByShortName(className, scope); 134 } 135 136 private boolean shouldUseSlowResolve() { 137 DumbService dumbService = DumbService.getInstance(getProject()); 138 return dumbService.isDumb() && dumbService.isAlternativeResolveEnabled(); 139 } 140 141 @NotNull 142 private KotlinPsiElementFinderWrapper[] finders() { 143 KotlinPsiElementFinderWrapper[] answer = elementFinders; 144 if (answer == null) { 145 answer = calcFinders(); 146 elementFinders = answer; 147 } 148 149 return answer; 150 } 151 152 @NotNull 153 protected KotlinPsiElementFinderWrapper[] calcFinders() { 154 List<KotlinPsiElementFinderWrapper> elementFinders = new ArrayList<KotlinPsiElementFinderWrapper>(); 155 elementFinders.add(new KotlinPsiElementFinderImpl(getProject())); 156 157 List<PsiElementFinder> nonKotlinFinders = ArraysKt.filter( 158 getProject().getExtensions(PsiElementFinder.EP_NAME), new Function1<PsiElementFinder, Boolean>() { 159 @Override 160 public Boolean invoke(PsiElementFinder finder) { 161 return !(finder instanceof NonClasspathClassFinder || finder instanceof KotlinFinderMarker || finder instanceof PsiElementFinderImpl); 162 } 163 }); 164 165 elementFinders.addAll(CollectionsKt.map(nonKotlinFinders, new Function1<PsiElementFinder, KotlinPsiElementFinderWrapper>() { 166 @Override 167 public KotlinPsiElementFinderWrapper invoke(PsiElementFinder finder) { 168 return wrap(finder); 169 } 170 })); 171 172 return elementFinders.toArray(new KotlinPsiElementFinderWrapper[elementFinders.size()]); 173 } 174 175 public PsiPackage findPackage(@NotNull String qualifiedName, GlobalSearchScope searchScope) { 176 PackageCache cache = SoftReference.dereference(packageCache); 177 if (cache == null) { 178 packageCache = new SoftReference<PackageCache>(cache = new PackageCache()); 179 } 180 181 Pair<String, GlobalSearchScope> key = new Pair<String, GlobalSearchScope>(qualifiedName, searchScope); 182 PsiPackage aPackage = cache.packageInScopeCache.get(key); 183 if (aPackage != null) { 184 return aPackage; 185 } 186 187 KotlinPsiElementFinderWrapper[] finders = filteredFinders(); 188 189 Boolean packageFoundInAllScope = cache.hasPackageInAllScopeCache.get(qualifiedName); 190 if (packageFoundInAllScope != null) { 191 if (!packageFoundInAllScope.booleanValue()) return null; 192 193 // Package was found in AllScope with some of finders but is absent in packageCache for current scope. 194 // We check only finders that depend on scope. 195 for (KotlinPsiElementFinderWrapper finder : finders) { 196 if (!finder.isSameResultForAnyScope()) { 197 aPackage = finder.findPackage(qualifiedName, searchScope); 198 if (aPackage != null) { 199 return ConcurrencyUtil.cacheOrGet(cache.packageInScopeCache, key, aPackage); 200 } 201 } 202 } 203 } 204 else { 205 for (KotlinPsiElementFinderWrapper finder : finders) { 206 aPackage = finder.findPackage(qualifiedName, searchScope); 207 208 if (aPackage != null) { 209 return ConcurrencyUtil.cacheOrGet(cache.packageInScopeCache, key, aPackage); 210 } 211 } 212 213 boolean found = false; 214 for (KotlinPsiElementFinderWrapper finder : finders) { 215 if (!finder.isSameResultForAnyScope()) { 216 aPackage = finder.findPackage(qualifiedName, GlobalSearchScope.allScope(project)); 217 if (aPackage != null) { 218 found = true; 219 break; 220 } 221 } 222 } 223 224 cache.hasPackageInAllScopeCache.put(qualifiedName, found); 225 } 226 227 return null; 228 } 229 230 @NotNull 231 private KotlinPsiElementFinderWrapper[] filteredFinders() { 232 DumbService dumbService = DumbService.getInstance(getProject()); 233 KotlinPsiElementFinderWrapper[] finders = finders(); 234 if (dumbService.isDumb()) { 235 List<KotlinPsiElementFinderWrapper> list = dumbService.filterByDumbAwareness(Arrays.asList(finders)); 236 finders = list.toArray(new KotlinPsiElementFinderWrapper[list.size()]); 237 } 238 return finders; 239 } 240 241 @NotNull 242 public Project getProject() { 243 return project; 244 } 245 246 public static KotlinPsiElementFinderWrapper wrap(PsiElementFinder finder) { 247 return finder instanceof DumbAware 248 ? new KotlinPsiElementFinderWrapperImplDumbAware(finder) 249 : new KotlinPsiElementFinderWrapperImpl(finder); 250 } 251 252 interface KotlinPsiElementFinderWrapper { 253 PsiClass findClass(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope); 254 PsiPackage findPackage(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope); 255 boolean isSameResultForAnyScope(); 256 } 257 258 private static class KotlinPsiElementFinderWrapperImpl implements KotlinPsiElementFinderWrapper { 259 private final PsiElementFinder finder; 260 261 private KotlinPsiElementFinderWrapperImpl(@NotNull PsiElementFinder finder) { 262 this.finder = finder; 263 } 264 265 @Override 266 public PsiClass findClass(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { 267 return finder.findClass(qualifiedName, scope); 268 } 269 270 @Override 271 public PsiPackage findPackage(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { 272 // Original element finder can't search packages with scope 273 return finder.findPackage(qualifiedName); 274 } 275 276 @Override 277 public boolean isSameResultForAnyScope() { 278 return true; 279 } 280 281 @Override 282 public String toString() { 283 return finder.toString(); 284 } 285 } 286 287 private static class KotlinPsiElementFinderWrapperImplDumbAware extends KotlinPsiElementFinderWrapperImpl implements DumbAware { 288 private KotlinPsiElementFinderWrapperImplDumbAware(PsiElementFinder finder) { 289 super(finder); 290 } 291 } 292 293 static class KotlinPsiElementFinderImpl implements KotlinPsiElementFinderWrapper, DumbAware { 294 private final JavaFileManager javaFileManager; 295 private final boolean isCliFileManager; 296 297 private final PsiManager psiManager; 298 private final PackageIndex packageIndex; 299 300 public KotlinPsiElementFinderImpl(Project project) { 301 this.javaFileManager = findJavaFileManager(project); 302 this.isCliFileManager = javaFileManager instanceof KotlinCliJavaFileManager; 303 304 this.packageIndex = PackageIndex.getInstance(project); 305 this.psiManager = PsiManager.getInstance(project); 306 } 307 308 @NotNull 309 private static JavaFileManager findJavaFileManager(@NotNull Project project) { 310 JavaFileManager javaFileManager = ServiceManager.getService(project, JavaFileManager.class); 311 if (javaFileManager == null) { 312 throw new IllegalStateException("JavaFileManager component is not found in project"); 313 } 314 315 return javaFileManager; 316 } 317 318 319 @Override 320 public PsiClass findClass(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { 321 return javaFileManager.findClass(qualifiedName, scope); 322 } 323 324 public PsiClass findClass(@NotNull ClassId classId, @NotNull GlobalSearchScope scope) { 325 if (isCliFileManager) { 326 return ((KotlinCliJavaFileManager) javaFileManager).findClass(classId, scope); 327 } 328 return findClass(classId.asSingleFqName().asString(), scope); 329 } 330 331 @Override 332 public PsiPackage findPackage(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { 333 if (isCliFileManager) { 334 return javaFileManager.findPackage(qualifiedName); 335 } 336 337 Query<VirtualFile> dirs = packageIndex.getDirsByPackageName(qualifiedName, true); 338 return hasDirectoriesInScope(dirs, scope) ? new PsiPackageImpl(psiManager, qualifiedName) : null; 339 } 340 341 @Override 342 public boolean isSameResultForAnyScope() { 343 return false; 344 } 345 346 private static boolean hasDirectoriesInScope(Query<VirtualFile> dirs, final GlobalSearchScope scope) { 347 CommonProcessors.FindProcessor<VirtualFile> findProcessor = new CommonProcessors.FindProcessor<VirtualFile>() { 348 @Override 349 protected boolean accept(VirtualFile file) { 350 return scope.accept(file); 351 } 352 }; 353 354 dirs.forEach(findProcessor); 355 return findProcessor.isFound(); 356 } 357 } 358 }