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