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 }