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 }