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