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