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