001    /*
002     * Copyright 2010-2016 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.finder;
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.PsiUtilCore;
029    import com.intellij.util.SmartList;
030    import com.intellij.util.containers.ContainerUtil;
031    import org.jetbrains.annotations.NotNull;
032    import org.jetbrains.annotations.Nullable;
033    import org.jetbrains.kotlin.asJava.LightClassGenerationSupport;
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    import static org.jetbrains.kotlin.asJava.LightClassUtilsKt.toLightClass;
049    
050    public class JavaElementFinder extends PsiElementFinder implements KotlinFinderMarker {
051    
052        @NotNull
053        public static JavaElementFinder getInstance(@NotNull Project project) {
054            PsiElementFinder[] extensions = Extensions.getArea(project).getExtensionPoint(PsiElementFinder.EP_NAME).getExtensions();
055            for (PsiElementFinder extension : extensions) {
056                if (extension instanceof JavaElementFinder) {
057                    return (JavaElementFinder) extension;
058                }
059            }
060            throw new IllegalStateException(JavaElementFinder.class.getSimpleName() + " is not found for project " + project);
061        }
062    
063        private final Project project;
064        private final PsiManager psiManager;
065        private final LightClassGenerationSupport lightClassGenerationSupport;
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        }
075    
076        @Override
077        public PsiClass findClass(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) {
078            PsiClass[] allClasses = findClasses(qualifiedName, scope);
079            return allClasses.length > 0 ? allClasses[0] : null;
080        }
081    
082        @NotNull
083        @Override
084        public PsiClass[] findClasses(@NotNull String qualifiedNameString, @NotNull GlobalSearchScope scope) {
085            if (!FqNamesUtilKt.isValidJavaFqName(qualifiedNameString)) {
086                return PsiClass.EMPTY_ARRAY;
087            }
088    
089            List<PsiClass> answer = new SmartList<PsiClass>();
090    
091            FqName qualifiedName = new FqName(qualifiedNameString);
092    
093            findClassesAndObjects(qualifiedName, scope, answer);
094    
095            answer.addAll(lightClassGenerationSupport.getFacadeClasses(qualifiedName, scope));
096            answer.addAll(lightClassGenerationSupport.getMultifilePartClasses(qualifiedName, scope));
097    
098            return sortByClasspath(answer, scope).toArray(new PsiClass[answer.size()]);
099        }
100    
101        // Finds explicitly declared classes and objects, not package classes
102        // Also DefaultImpls classes of interfaces
103        private void findClassesAndObjects(FqName qualifiedName, GlobalSearchScope scope, List<PsiClass> answer) {
104            findInterfaceDefaultImpls(qualifiedName, scope, answer);
105    
106            Collection<KtClassOrObject> classOrObjectDeclarations =
107                    lightClassGenerationSupport.findClassOrObjectDeclarations(qualifiedName, scope);
108    
109            for (KtClassOrObject declaration : classOrObjectDeclarations) {
110                if (!(declaration instanceof KtEnumEntry)) {
111                    PsiClass lightClass = toLightClass(declaration);
112                    if (lightClass != null) {
113                        answer.add(lightClass);
114                    }
115                }
116            }
117        }
118    
119        private void findInterfaceDefaultImpls(FqName qualifiedName, GlobalSearchScope scope, List<PsiClass> answer) {
120            if (qualifiedName.isRoot()) return;
121    
122            if (!qualifiedName.shortName().asString().equals(JvmAbi.DEFAULT_IMPLS_CLASS_NAME)) return;
123    
124            for (KtClassOrObject classOrObject : lightClassGenerationSupport.findClassOrObjectDeclarations(qualifiedName.parent(), scope)) {
125                //NOTE: can't filter out more interfaces right away because decompiled declarations do not have member bodies
126                if (classOrObject instanceof KtClass && ((KtClass) classOrObject).isInterface()) {
127                    PsiClass interfaceClass = toLightClass(classOrObject);
128                    if (interfaceClass != null) {
129                        PsiClass implsClass = interfaceClass.findInnerClassByName(JvmAbi.DEFAULT_IMPLS_CLASS_NAME, false);
130                        if (implsClass != null) {
131                            answer.add(implsClass);
132                        }
133                    }
134                }
135            }
136        }
137    
138        @NotNull
139        @Override
140        public Set<String> getClassNames(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
141            FqName packageFQN = new FqName(psiPackage.getQualifiedName());
142    
143            Collection<KtClassOrObject> declarations = lightClassGenerationSupport.findClassOrObjectDeclarationsInPackage(packageFQN, scope);
144    
145            Set<String> answer = Sets.newHashSet();
146            answer.addAll(lightClassGenerationSupport.getFacadeNames(packageFQN, scope));
147    
148            for (KtClassOrObject declaration : declarations) {
149                String name = declaration.getName();
150                if (name != null) {
151                    answer.add(name);
152                }
153            }
154    
155            return answer;
156        }
157    
158        @Override
159        public PsiPackage findPackage(@NotNull String qualifiedNameString) {
160            if (!FqNamesUtilKt.isValidJavaFqName(qualifiedNameString)) {
161                return null;
162            }
163    
164            FqName fqName = new FqName(qualifiedNameString);
165    
166            // allScope() because the contract says that the whole project
167            GlobalSearchScope allScope = GlobalSearchScope.allScope(project);
168            if (lightClassGenerationSupport.packageExists(fqName, allScope)) {
169                return new KtLightPackage(psiManager, fqName, allScope);
170            }
171    
172            return null;
173        }
174    
175        @NotNull
176        @Override
177        public PsiPackage[] getSubPackages(@NotNull PsiPackage psiPackage, @NotNull final GlobalSearchScope scope) {
178            FqName packageFQN = new FqName(psiPackage.getQualifiedName());
179    
180            Collection<FqName> subpackages = lightClassGenerationSupport.getSubPackages(packageFQN, scope);
181    
182            Collection<PsiPackage> answer = Collections2.transform(subpackages, new Function<FqName, PsiPackage>() {
183                @Override
184                public PsiPackage apply(@Nullable FqName input) {
185                    return new KtLightPackage(psiManager, input, scope);
186                }
187            });
188    
189            return answer.toArray(new PsiPackage[answer.size()]);
190        }
191    
192        @NotNull
193        @Override
194        public PsiClass[] getClasses(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
195            List<PsiClass> answer = new SmartList<PsiClass>();
196            FqName packageFQN = new FqName(psiPackage.getQualifiedName());
197    
198            answer.addAll(lightClassGenerationSupport.getFacadeClassesInPackage(packageFQN, scope));
199    
200            Collection<KtClassOrObject> declarations = lightClassGenerationSupport.findClassOrObjectDeclarationsInPackage(packageFQN, scope);
201            for (KtClassOrObject declaration : declarations) {
202                PsiClass aClass = toLightClass(declaration);
203                if (aClass != null) {
204                    answer.add(aClass);
205                }
206            }
207    
208            return sortByClasspath(answer, scope).toArray(new PsiClass[answer.size()]);
209        }
210    
211        @Override
212        @NotNull
213        public PsiFile[] getPackageFiles(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
214            FqName packageFQN = new FqName(psiPackage.getQualifiedName());
215            Collection<KtFile> result = lightClassGenerationSupport.findFilesForPackage(packageFQN, scope);
216            return result.toArray(new PsiFile[result.size()]);
217        }
218    
219        @Override
220        @Nullable
221        public Condition<PsiFile> getPackageFilesFilter(@NotNull final PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
222            return new Condition<PsiFile>() {
223                @Override
224                public boolean value(PsiFile input) {
225                    if (!(input instanceof KtFile)) {
226                        return true;
227                    }
228                    return psiPackage.getQualifiedName().equals(((KtFile) input).getPackageFqName().asString());
229                }
230            };
231        }
232    
233        @NotNull
234        public static Comparator<PsiElement> byClasspathComparator(@NotNull final GlobalSearchScope searchScope) {
235            return new Comparator<PsiElement>() {
236                @Override
237                public int compare(@NotNull PsiElement o1, @NotNull PsiElement o2) {
238                    VirtualFile f1 = PsiUtilCore.getVirtualFile(o1);
239                    VirtualFile f2 = PsiUtilCore.getVirtualFile(o2);
240                    if (f1 == f2) return 0;
241                    if (f1 == null) return -1;
242                    if (f2 == null) return 1;
243                    return searchScope.compare(f2, f1);
244                }
245            };
246        }
247    
248        private static Collection<PsiClass> sortByClasspath(@NotNull List<PsiClass> classes, @NotNull GlobalSearchScope searchScope) {
249            if (classes.size() > 1) {
250                ContainerUtil.quickSort(classes, byClasspathComparator(searchScope));
251            }
252    
253            return classes;
254        }
255    }