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.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    }