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