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