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