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