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