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