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