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.annotations.Nullable; 044 import org.jetbrains.kotlin.name.FqName; 045 import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus; 046 import org.jetbrains.kotlin.name.ClassId; 047 048 import java.util.ArrayList; 049 import java.util.Arrays; 050 import java.util.List; 051 import java.util.Set; 052 import java.util.concurrent.ConcurrentMap; 053 054 public class KotlinJavaPsiFacade { 055 private volatile KotlinPsiElementFinderWrapper[] elementFinders; 056 057 private static class PackageCache { 058 final ConcurrentMap<Pair<String, GlobalSearchScope>, PsiPackage> packageInScopeCache = ContainerUtil.newConcurrentMap(); 059 final ConcurrentMap<String, Boolean> hasPackageInAllScopeCache = ContainerUtil.newConcurrentMap(); 060 } 061 062 private volatile SoftReference<PackageCache> packageCache; 063 064 private final Project project; 065 066 public static KotlinJavaPsiFacade getInstance(Project project) { 067 return ServiceManager.getService(project, KotlinJavaPsiFacade.class); 068 } 069 070 public KotlinJavaPsiFacade(@NotNull Project project) { 071 this.project = project; 072 073 final PsiModificationTracker modificationTracker = PsiManager.getInstance(project).getModificationTracker(); 074 MessageBus bus = project.getMessageBus(); 075 076 bus.connect().subscribe(PsiModificationTracker.TOPIC, new PsiModificationTracker.Listener() { 077 private long lastTimeSeen = -1L; 078 079 @Override 080 public void modificationCountChanged() { 081 long now = modificationTracker.getJavaStructureModificationCount(); 082 if (lastTimeSeen != now) { 083 lastTimeSeen = now; 084 085 packageCache = null; 086 } 087 } 088 }); 089 } 090 091 public PsiClass findClass(@NotNull ClassId classId, @NotNull GlobalSearchScope scope) { 092 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); // We hope this method is being called often enough to cancel daemon processes smoothly 093 094 String qualifiedName = classId.asSingleFqName().asString(); 095 096 if (shouldUseSlowResolve()) { 097 PsiClass[] classes = findClassesInDumbMode(qualifiedName, scope); 098 if (classes.length != 0) { 099 return classes[0]; 100 } 101 return null; 102 } 103 104 for (KotlinPsiElementFinderWrapper finder : finders()) { 105 if (finder instanceof KotlinPsiElementFinderImpl) { 106 PsiClass aClass = ((KotlinPsiElementFinderImpl) finder).findClass(classId, scope); 107 if (aClass != null) return aClass; 108 } 109 else { 110 PsiClass aClass = finder.findClass(qualifiedName, scope); 111 if (aClass != null) return aClass; 112 } 113 } 114 115 return null; 116 } 117 118 @Nullable 119 public Set<String> knownClassNamesInPackage(@NotNull FqName packageFqName) { 120 KotlinPsiElementFinderWrapper[] finders = finders(); 121 122 if (finders.length == 1) { 123 return ((KotlinPsiElementFinderImpl) finders[0]).knownClassNamesInPackage(packageFqName); 124 } 125 126 return null; 127 } 128 129 @NotNull 130 private PsiClass[] findClassesInDumbMode(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { 131 String packageName = StringUtil.getPackageName(qualifiedName); 132 PsiPackage pkg = findPackage(packageName, scope); 133 String className = StringUtil.getShortName(qualifiedName); 134 if (pkg == null && packageName.length() < qualifiedName.length()) { 135 PsiClass[] containingClasses = findClassesInDumbMode(packageName, scope); 136 if (containingClasses.length == 1) { 137 return PsiElementFinder.filterByName(className, containingClasses[0].getInnerClasses()); 138 } 139 140 return PsiClass.EMPTY_ARRAY; 141 } 142 143 if (pkg == null || !pkg.containsClassNamed(className)) { 144 return PsiClass.EMPTY_ARRAY; 145 } 146 147 return pkg.findClassByShortName(className, scope); 148 } 149 150 private boolean shouldUseSlowResolve() { 151 DumbService dumbService = DumbService.getInstance(getProject()); 152 return dumbService.isDumb() && dumbService.isAlternativeResolveEnabled(); 153 } 154 155 @NotNull 156 private KotlinPsiElementFinderWrapper[] finders() { 157 KotlinPsiElementFinderWrapper[] answer = elementFinders; 158 if (answer == null) { 159 answer = calcFinders(); 160 elementFinders = answer; 161 } 162 163 return answer; 164 } 165 166 @NotNull 167 protected KotlinPsiElementFinderWrapper[] calcFinders() { 168 List<KotlinPsiElementFinderWrapper> elementFinders = new ArrayList<KotlinPsiElementFinderWrapper>(); 169 elementFinders.add(new KotlinPsiElementFinderImpl(getProject())); 170 171 List<PsiElementFinder> nonKotlinFinders = ArraysKt.filter( 172 getProject().getExtensions(PsiElementFinder.EP_NAME), new Function1<PsiElementFinder, Boolean>() { 173 @Override 174 public Boolean invoke(PsiElementFinder finder) { 175 return !(finder instanceof NonClasspathClassFinder || finder instanceof KotlinFinderMarker || finder instanceof PsiElementFinderImpl); 176 } 177 }); 178 179 elementFinders.addAll(CollectionsKt.map(nonKotlinFinders, new Function1<PsiElementFinder, KotlinPsiElementFinderWrapper>() { 180 @Override 181 public KotlinPsiElementFinderWrapper invoke(PsiElementFinder finder) { 182 return wrap(finder); 183 } 184 })); 185 186 return elementFinders.toArray(new KotlinPsiElementFinderWrapper[elementFinders.size()]); 187 } 188 189 public PsiPackage findPackage(@NotNull String qualifiedName, GlobalSearchScope searchScope) { 190 PackageCache cache = SoftReference.dereference(packageCache); 191 if (cache == null) { 192 packageCache = new SoftReference<PackageCache>(cache = new PackageCache()); 193 } 194 195 Pair<String, GlobalSearchScope> key = new Pair<String, GlobalSearchScope>(qualifiedName, searchScope); 196 PsiPackage aPackage = cache.packageInScopeCache.get(key); 197 if (aPackage != null) { 198 return aPackage; 199 } 200 201 KotlinPsiElementFinderWrapper[] finders = filteredFinders(); 202 203 Boolean packageFoundInAllScope = cache.hasPackageInAllScopeCache.get(qualifiedName); 204 if (packageFoundInAllScope != null) { 205 if (!packageFoundInAllScope.booleanValue()) return null; 206 207 // Package was found in AllScope with some of finders but is absent in packageCache for current scope. 208 // We check only finders that depend on scope. 209 for (KotlinPsiElementFinderWrapper finder : finders) { 210 if (!finder.isSameResultForAnyScope()) { 211 aPackage = finder.findPackage(qualifiedName, searchScope); 212 if (aPackage != null) { 213 return ConcurrencyUtil.cacheOrGet(cache.packageInScopeCache, key, aPackage); 214 } 215 } 216 } 217 } 218 else { 219 for (KotlinPsiElementFinderWrapper finder : finders) { 220 aPackage = finder.findPackage(qualifiedName, searchScope); 221 222 if (aPackage != null) { 223 return ConcurrencyUtil.cacheOrGet(cache.packageInScopeCache, key, aPackage); 224 } 225 } 226 227 boolean found = false; 228 for (KotlinPsiElementFinderWrapper finder : finders) { 229 if (!finder.isSameResultForAnyScope()) { 230 aPackage = finder.findPackage(qualifiedName, GlobalSearchScope.allScope(project)); 231 if (aPackage != null) { 232 found = true; 233 break; 234 } 235 } 236 } 237 238 cache.hasPackageInAllScopeCache.put(qualifiedName, found); 239 } 240 241 return null; 242 } 243 244 @NotNull 245 private KotlinPsiElementFinderWrapper[] filteredFinders() { 246 DumbService dumbService = DumbService.getInstance(getProject()); 247 KotlinPsiElementFinderWrapper[] finders = finders(); 248 if (dumbService.isDumb()) { 249 List<KotlinPsiElementFinderWrapper> list = dumbService.filterByDumbAwareness(Arrays.asList(finders)); 250 finders = list.toArray(new KotlinPsiElementFinderWrapper[list.size()]); 251 } 252 return finders; 253 } 254 255 @NotNull 256 public Project getProject() { 257 return project; 258 } 259 260 public static KotlinPsiElementFinderWrapper wrap(PsiElementFinder finder) { 261 return finder instanceof DumbAware 262 ? new KotlinPsiElementFinderWrapperImplDumbAware(finder) 263 : new KotlinPsiElementFinderWrapperImpl(finder); 264 } 265 266 interface KotlinPsiElementFinderWrapper { 267 PsiClass findClass(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope); 268 PsiPackage findPackage(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope); 269 boolean isSameResultForAnyScope(); 270 } 271 272 private static class KotlinPsiElementFinderWrapperImpl implements KotlinPsiElementFinderWrapper { 273 private final PsiElementFinder finder; 274 275 private KotlinPsiElementFinderWrapperImpl(@NotNull PsiElementFinder finder) { 276 this.finder = finder; 277 } 278 279 @Override 280 public PsiClass findClass(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { 281 return finder.findClass(qualifiedName, scope); 282 } 283 284 @Override 285 public PsiPackage findPackage(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { 286 // Original element finder can't search packages with scope 287 return finder.findPackage(qualifiedName); 288 } 289 290 @Override 291 public boolean isSameResultForAnyScope() { 292 return true; 293 } 294 295 @Override 296 public String toString() { 297 return finder.toString(); 298 } 299 } 300 301 private static class KotlinPsiElementFinderWrapperImplDumbAware extends KotlinPsiElementFinderWrapperImpl implements DumbAware { 302 private KotlinPsiElementFinderWrapperImplDumbAware(PsiElementFinder finder) { 303 super(finder); 304 } 305 } 306 307 static class KotlinPsiElementFinderImpl implements KotlinPsiElementFinderWrapper, DumbAware { 308 private final JavaFileManager javaFileManager; 309 private final boolean isCliFileManager; 310 311 private final PsiManager psiManager; 312 private final PackageIndex packageIndex; 313 314 public KotlinPsiElementFinderImpl(Project project) { 315 this.javaFileManager = findJavaFileManager(project); 316 this.isCliFileManager = javaFileManager instanceof KotlinCliJavaFileManager; 317 318 this.packageIndex = PackageIndex.getInstance(project); 319 this.psiManager = PsiManager.getInstance(project); 320 } 321 322 @NotNull 323 private static JavaFileManager findJavaFileManager(@NotNull Project project) { 324 JavaFileManager javaFileManager = ServiceManager.getService(project, JavaFileManager.class); 325 if (javaFileManager == null) { 326 throw new IllegalStateException("JavaFileManager component is not found in project"); 327 } 328 329 return javaFileManager; 330 } 331 332 333 @Override 334 public PsiClass findClass(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { 335 return javaFileManager.findClass(qualifiedName, scope); 336 } 337 338 public PsiClass findClass(@NotNull ClassId classId, @NotNull GlobalSearchScope scope) { 339 if (isCliFileManager) { 340 return ((KotlinCliJavaFileManager) javaFileManager).findClass(classId, scope); 341 } 342 return findClass(classId.asSingleFqName().asString(), scope); 343 } 344 345 @Nullable 346 public Set<String> knownClassNamesInPackage(@NotNull FqName packageFqName) { 347 if (isCliFileManager) { 348 return ((KotlinCliJavaFileManager) javaFileManager).knownClassNamesInPackage(packageFqName); 349 } 350 351 return null; 352 } 353 354 @Override 355 public PsiPackage findPackage(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { 356 if (isCliFileManager) { 357 return javaFileManager.findPackage(qualifiedName); 358 } 359 360 Query<VirtualFile> dirs = packageIndex.getDirsByPackageName(qualifiedName, true); 361 return hasDirectoriesInScope(dirs, scope) ? new PsiPackageImpl(psiManager, qualifiedName) : null; 362 } 363 364 @Override 365 public boolean isSameResultForAnyScope() { 366 return false; 367 } 368 369 private static boolean hasDirectoriesInScope(Query<VirtualFile> dirs, final GlobalSearchScope scope) { 370 CommonProcessors.FindProcessor<VirtualFile> findProcessor = new CommonProcessors.FindProcessor<VirtualFile>() { 371 @Override 372 protected boolean accept(VirtualFile file) { 373 return scope.accept(file); 374 } 375 }; 376 377 dirs.forEach(findProcessor); 378 return findProcessor.isFound(); 379 } 380 } 381 }