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 }