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.progress.ProgressIndicatorProvider;
021    import com.intellij.openapi.project.DumbAware;
022    import com.intellij.openapi.project.DumbService;
023    import com.intellij.openapi.project.Project;
024    import com.intellij.openapi.roots.PackageIndex;
025    import com.intellij.openapi.util.Pair;
026    import com.intellij.openapi.util.text.StringUtil;
027    import com.intellij.openapi.vfs.VirtualFile;
028    import com.intellij.psi.*;
029    import com.intellij.psi.impl.PsiElementFinderImpl;
030    import com.intellij.psi.impl.file.PsiPackageImpl;
031    import com.intellij.psi.impl.file.impl.JavaFileManager;
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.KotlinPackage;
041    import kotlin.jvm.functions.Function1;
042    import org.jetbrains.annotations.NotNull;
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            ProgressIndicatorProvider.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    }