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.asJava;
018    
019    import com.google.common.base.Function;
020    import com.google.common.collect.Collections2;
021    import com.google.common.collect.Sets;
022    import com.intellij.openapi.extensions.Extensions;
023    import com.intellij.openapi.project.Project;
024    import com.intellij.openapi.util.Condition;
025    import com.intellij.openapi.vfs.VirtualFile;
026    import com.intellij.psi.*;
027    import com.intellij.psi.search.GlobalSearchScope;
028    import com.intellij.psi.util.*;
029    import com.intellij.util.SmartList;
030    import com.intellij.util.containers.ContainerUtil;
031    import com.intellij.util.containers.SLRUCache;
032    import org.jetbrains.annotations.NotNull;
033    import org.jetbrains.annotations.Nullable;
034    import org.jetbrains.kotlin.load.java.JvmAbi;
035    import org.jetbrains.kotlin.name.FqName;
036    import org.jetbrains.kotlin.name.FqNamesUtilKt;
037    import org.jetbrains.kotlin.psi.KtClass;
038    import org.jetbrains.kotlin.psi.KtClassOrObject;
039    import org.jetbrains.kotlin.psi.KtEnumEntry;
040    import org.jetbrains.kotlin.psi.KtFile;
041    import org.jetbrains.kotlin.resolve.jvm.KotlinFinderMarker;
042    
043    import java.util.Collection;
044    import java.util.Comparator;
045    import java.util.List;
046    import java.util.Set;
047    
048    public class JavaElementFinder extends PsiElementFinder implements KotlinFinderMarker {
049    
050        @NotNull
051        public static JavaElementFinder getInstance(@NotNull Project project) {
052            PsiElementFinder[] extensions = Extensions.getArea(project).getExtensionPoint(PsiElementFinder.EP_NAME).getExtensions();
053            for (PsiElementFinder extension : extensions) {
054                if (extension instanceof JavaElementFinder) {
055                    return (JavaElementFinder) extension;
056                }
057            }
058            throw new IllegalStateException(JavaElementFinder.class.getSimpleName() + " is not found for project " + project);
059        }
060    
061        private final Project project;
062        private final PsiManager psiManager;
063        private final LightClassGenerationSupport lightClassGenerationSupport;
064    
065        private final CachedValue<SLRUCache<FindClassesRequest, PsiClass[]>> findClassesCache;
066    
067        public JavaElementFinder(
068                @NotNull Project project,
069                @NotNull LightClassGenerationSupport lightClassGenerationSupport
070        ) {
071            this.project = project;
072            this.psiManager = PsiManager.getInstance(project);
073            this.lightClassGenerationSupport = lightClassGenerationSupport;
074            this.findClassesCache = CachedValuesManager.getManager(project).createCachedValue(
075                new CachedValueProvider<SLRUCache<FindClassesRequest, PsiClass[]>>() {
076                    @Nullable
077                    @Override
078                    public Result<SLRUCache<FindClassesRequest, PsiClass[]>> compute() {
079                        return new Result<SLRUCache<FindClassesRequest, PsiClass[]>>(
080                                new SLRUCache<FindClassesRequest, PsiClass[]>(30, 10) {
081                                    @NotNull
082                                    @Override
083                                    public PsiClass[] createValue(FindClassesRequest key) {
084                                        return doFindClasses(key.fqName, key.scope);
085                                    }
086                                },
087                                PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT
088                        );
089                    }
090                },
091                false
092            );
093        }
094    
095        @Override
096        public PsiClass findClass(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) {
097            PsiClass[] allClasses = findClasses(qualifiedName, scope);
098            return allClasses.length > 0 ? allClasses[0] : null;
099        }
100    
101        @NotNull
102        @Override
103        public PsiClass[] findClasses(@NotNull String qualifiedNameString, @NotNull GlobalSearchScope scope) {
104            SLRUCache<FindClassesRequest, PsiClass[]> value = findClassesCache.getValue();
105            synchronized (value) {
106                return value.get(new FindClassesRequest(qualifiedNameString, scope));
107            }
108        }
109    
110        private PsiClass[] doFindClasses(String qualifiedNameString, GlobalSearchScope scope) {
111            if (!FqNamesUtilKt.isValidJavaFqName(qualifiedNameString)) {
112                return PsiClass.EMPTY_ARRAY;
113            }
114    
115            List<PsiClass> answer = new SmartList<PsiClass>();
116    
117            FqName qualifiedName = new FqName(qualifiedNameString);
118    
119            findClassesAndObjects(qualifiedName, scope, answer);
120    
121            answer.addAll(lightClassGenerationSupport.getFacadeClasses(qualifiedName, scope));
122    
123            return sortByClasspath(answer, scope).toArray(new PsiClass[answer.size()]);
124        }
125    
126        // Finds explicitly declared classes and objects, not package classes
127        // Also DefaultImpls classes of interfaces
128        private void findClassesAndObjects(FqName qualifiedName, GlobalSearchScope scope, List<PsiClass> answer) {
129            findInterfaceDefaultImpls(qualifiedName, scope, answer);
130    
131            Collection<KtClassOrObject> classOrObjectDeclarations =
132                    lightClassGenerationSupport.findClassOrObjectDeclarations(qualifiedName, scope);
133    
134            for (KtClassOrObject declaration : classOrObjectDeclarations) {
135                if (!(declaration instanceof KtEnumEntry)) {
136                    PsiClass lightClass = LightClassUtil.INSTANCE$.getPsiClass(declaration);
137                    if (lightClass != null) {
138                        answer.add(lightClass);
139                    }
140                }
141            }
142        }
143    
144        private void findInterfaceDefaultImpls(FqName qualifiedName, GlobalSearchScope scope, List<PsiClass> answer) {
145            if (qualifiedName.isRoot()) return;
146    
147            if (!qualifiedName.shortName().asString().equals(JvmAbi.DEFAULT_IMPLS_CLASS_NAME)) return;
148    
149            for (KtClassOrObject classOrObject : lightClassGenerationSupport.findClassOrObjectDeclarations(qualifiedName.parent(), scope)) {
150                //NOTE: can't filter out more interfaces right away because decompiled declarations do not have member bodies
151                if (classOrObject instanceof KtClass && ((KtClass) classOrObject).isInterface()) {
152                    PsiClass interfaceClass = LightClassUtil.INSTANCE$.getPsiClass(classOrObject);
153                    if (interfaceClass != null) {
154                        PsiClass implsClass = interfaceClass.findInnerClassByName(JvmAbi.DEFAULT_IMPLS_CLASS_NAME, false);
155                        if (implsClass != null) {
156                            answer.add(implsClass);
157                        }
158                    }
159                }
160            }
161        }
162    
163        @NotNull
164        @Override
165        public Set<String> getClassNames(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
166            FqName packageFQN = new FqName(psiPackage.getQualifiedName());
167    
168            Collection<KtClassOrObject> declarations = lightClassGenerationSupport.findClassOrObjectDeclarationsInPackage(packageFQN, scope);
169    
170            Set<String> answer = Sets.newHashSet();
171            answer.addAll(lightClassGenerationSupport.getFacadeNames(packageFQN, scope));
172    
173            for (KtClassOrObject declaration : declarations) {
174                String name = declaration.getName();
175                if (name != null) {
176                    answer.add(name);
177                }
178            }
179    
180            return answer;
181        }
182    
183        @Override
184        public PsiPackage findPackage(@NotNull String qualifiedNameString) {
185            if (!FqNamesUtilKt.isValidJavaFqName(qualifiedNameString)) {
186                return null;
187            }
188    
189            FqName fqName = new FqName(qualifiedNameString);
190    
191            // allScope() because the contract says that the whole project
192            GlobalSearchScope allScope = GlobalSearchScope.allScope(project);
193            if (lightClassGenerationSupport.packageExists(fqName, allScope)) {
194                return new KtLightPackage(psiManager, fqName, allScope);
195            }
196    
197            return null;
198        }
199    
200        @NotNull
201        @Override
202        public PsiPackage[] getSubPackages(@NotNull PsiPackage psiPackage, @NotNull final GlobalSearchScope scope) {
203            FqName packageFQN = new FqName(psiPackage.getQualifiedName());
204    
205            Collection<FqName> subpackages = lightClassGenerationSupport.getSubPackages(packageFQN, scope);
206    
207            Collection<PsiPackage> answer = Collections2.transform(subpackages, new Function<FqName, PsiPackage>() {
208                @Override
209                public PsiPackage apply(@Nullable FqName input) {
210                    return new KtLightPackage(psiManager, input, scope);
211                }
212            });
213    
214            return answer.toArray(new PsiPackage[answer.size()]);
215        }
216    
217        @NotNull
218        @Override
219        public PsiClass[] getClasses(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
220            List<PsiClass> answer = new SmartList<PsiClass>();
221            FqName packageFQN = new FqName(psiPackage.getQualifiedName());
222    
223            answer.addAll(lightClassGenerationSupport.getFacadeClassesInPackage(packageFQN, scope));
224    
225            Collection<KtClassOrObject> declarations = lightClassGenerationSupport.findClassOrObjectDeclarationsInPackage(packageFQN, scope);
226            for (KtClassOrObject declaration : declarations) {
227                PsiClass aClass = LightClassUtil.INSTANCE$.getPsiClass(declaration);
228                if (aClass != null) {
229                    answer.add(aClass);
230                }
231            }
232    
233            return sortByClasspath(answer, scope).toArray(new PsiClass[answer.size()]);
234        }
235    
236        @Override
237        @NotNull
238        public PsiFile[] getPackageFiles(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
239            FqName packageFQN = new FqName(psiPackage.getQualifiedName());
240            Collection<KtFile> result = lightClassGenerationSupport.findFilesForPackage(packageFQN, scope);
241            return result.toArray(new PsiFile[result.size()]);
242        }
243    
244        @Override
245        @Nullable
246        public Condition<PsiFile> getPackageFilesFilter(@NotNull final PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
247            return new Condition<PsiFile>() {
248                @Override
249                public boolean value(PsiFile input) {
250                    if (!(input instanceof KtFile)) {
251                        return true;
252                    }
253                    return psiPackage.getQualifiedName().equals(((KtFile) input).getPackageFqName().asString());
254                }
255            };
256        }
257    
258        private static class FindClassesRequest {
259            private final String fqName;
260            private final GlobalSearchScope scope;
261    
262            private FindClassesRequest(@NotNull String fqName, @NotNull GlobalSearchScope scope) {
263                this.fqName = fqName;
264                this.scope = scope;
265            }
266    
267            @Override
268            public boolean equals(Object o) {
269                if (this == o) return true;
270                if (o == null || getClass() != o.getClass()) return false;
271    
272                FindClassesRequest request = (FindClassesRequest) o;
273    
274                if (!fqName.equals(request.fqName)) return false;
275                if (!scope.equals(request.scope)) return false;
276    
277                return true;
278            }
279    
280            @Override
281            public int hashCode() {
282                int result = fqName.hashCode();
283                result = 31 * result + (scope.hashCode());
284                return result;
285            }
286    
287            @Override
288            public String toString() {
289                return fqName + " in " + scope;
290            }
291        }
292    
293        @NotNull
294        public static Comparator<PsiElement> byClasspathComparator(@NotNull final GlobalSearchScope searchScope) {
295            return new Comparator<PsiElement>() {
296                @Override
297                public int compare(@NotNull PsiElement o1, @NotNull PsiElement o2) {
298                    VirtualFile f1 = PsiUtilCore.getVirtualFile(o1);
299                    VirtualFile f2 = PsiUtilCore.getVirtualFile(o2);
300                    if (f1 == f2) return 0;
301                    if (f1 == null) return -1;
302                    if (f2 == null) return 1;
303                    return searchScope.compare(f2, f1);
304                }
305            };
306        }
307    
308        private static Collection<PsiClass> sortByClasspath(@NotNull List<PsiClass> classes, @NotNull GlobalSearchScope searchScope) {
309            if (classes.size() > 1) {
310                ContainerUtil.quickSort(classes, byClasspathComparator(searchScope));
311            }
312    
313            return classes;
314        }
315    }