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