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