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