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