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.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 public class JavaElementFinder extends PsiElementFinder implements KotlinFinderMarker { 049 050 @NotNull 051 public static JavaElementFinder getInstance(@NotNull Project project) { 052 PsiElementFinder[] extensions = Extensions.getArea(project).getExtensionPoint(PsiElementFinder.EP_NAME).getExtensions(); 053 for (PsiElementFinder extension : extensions) { 054 if (extension instanceof JavaElementFinder) { 055 return (JavaElementFinder) extension; 056 } 057 } 058 throw new IllegalStateException(JavaElementFinder.class.getSimpleName() + " is not found for project " + project); 059 } 060 061 private final Project project; 062 private final PsiManager psiManager; 063 private final LightClassGenerationSupport lightClassGenerationSupport; 064 065 private final CachedValue<SLRUCache<FindClassesRequest, PsiClass[]>> findClassesCache; 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 this.findClassesCache = CachedValuesManager.getManager(project).createCachedValue( 075 new CachedValueProvider<SLRUCache<FindClassesRequest, PsiClass[]>>() { 076 @Nullable 077 @Override 078 public Result<SLRUCache<FindClassesRequest, PsiClass[]>> compute() { 079 return new Result<SLRUCache<FindClassesRequest, PsiClass[]>>( 080 new SLRUCache<FindClassesRequest, PsiClass[]>(30, 10) { 081 @NotNull 082 @Override 083 public PsiClass[] createValue(FindClassesRequest key) { 084 return doFindClasses(key.fqName, key.scope); 085 } 086 }, 087 PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT 088 ); 089 } 090 }, 091 false 092 ); 093 } 094 095 @Override 096 public PsiClass findClass(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { 097 PsiClass[] allClasses = findClasses(qualifiedName, scope); 098 return allClasses.length > 0 ? allClasses[0] : null; 099 } 100 101 @NotNull 102 @Override 103 public PsiClass[] findClasses(@NotNull String qualifiedNameString, @NotNull GlobalSearchScope scope) { 104 SLRUCache<FindClassesRequest, PsiClass[]> value = findClassesCache.getValue(); 105 synchronized (value) { 106 return value.get(new FindClassesRequest(qualifiedNameString, scope)); 107 } 108 } 109 110 private PsiClass[] doFindClasses(String qualifiedNameString, GlobalSearchScope scope) { 111 if (!FqNamesUtilKt.isValidJavaFqName(qualifiedNameString)) { 112 return PsiClass.EMPTY_ARRAY; 113 } 114 115 List<PsiClass> answer = new SmartList<PsiClass>(); 116 117 FqName qualifiedName = new FqName(qualifiedNameString); 118 119 findClassesAndObjects(qualifiedName, scope, answer); 120 121 answer.addAll(lightClassGenerationSupport.getFacadeClasses(qualifiedName, scope)); 122 123 return sortByClasspath(answer, scope).toArray(new PsiClass[answer.size()]); 124 } 125 126 // Finds explicitly declared classes and objects, not package classes 127 // Also DefaultImpls classes of interfaces 128 private void findClassesAndObjects(FqName qualifiedName, GlobalSearchScope scope, List<PsiClass> answer) { 129 findInterfaceDefaultImpls(qualifiedName, scope, answer); 130 131 Collection<KtClassOrObject> classOrObjectDeclarations = 132 lightClassGenerationSupport.findClassOrObjectDeclarations(qualifiedName, scope); 133 134 for (KtClassOrObject declaration : classOrObjectDeclarations) { 135 if (!(declaration instanceof KtEnumEntry)) { 136 PsiClass lightClass = LightClassUtil.INSTANCE$.getPsiClass(declaration); 137 if (lightClass != null) { 138 answer.add(lightClass); 139 } 140 } 141 } 142 } 143 144 private void findInterfaceDefaultImpls(FqName qualifiedName, GlobalSearchScope scope, List<PsiClass> answer) { 145 if (qualifiedName.isRoot()) return; 146 147 if (!qualifiedName.shortName().asString().equals(JvmAbi.DEFAULT_IMPLS_CLASS_NAME)) return; 148 149 for (KtClassOrObject classOrObject : lightClassGenerationSupport.findClassOrObjectDeclarations(qualifiedName.parent(), scope)) { 150 //NOTE: can't filter out more interfaces right away because decompiled declarations do not have member bodies 151 if (classOrObject instanceof KtClass && ((KtClass) classOrObject).isInterface()) { 152 PsiClass interfaceClass = LightClassUtil.INSTANCE$.getPsiClass(classOrObject); 153 if (interfaceClass != null) { 154 PsiClass implsClass = interfaceClass.findInnerClassByName(JvmAbi.DEFAULT_IMPLS_CLASS_NAME, false); 155 if (implsClass != null) { 156 answer.add(implsClass); 157 } 158 } 159 } 160 } 161 } 162 163 @NotNull 164 @Override 165 public Set<String> getClassNames(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) { 166 FqName packageFQN = new FqName(psiPackage.getQualifiedName()); 167 168 Collection<KtClassOrObject> declarations = lightClassGenerationSupport.findClassOrObjectDeclarationsInPackage(packageFQN, scope); 169 170 Set<String> answer = Sets.newHashSet(); 171 answer.addAll(lightClassGenerationSupport.getFacadeNames(packageFQN, scope)); 172 173 for (KtClassOrObject declaration : declarations) { 174 String name = declaration.getName(); 175 if (name != null) { 176 answer.add(name); 177 } 178 } 179 180 return answer; 181 } 182 183 @Override 184 public PsiPackage findPackage(@NotNull String qualifiedNameString) { 185 if (!FqNamesUtilKt.isValidJavaFqName(qualifiedNameString)) { 186 return null; 187 } 188 189 FqName fqName = new FqName(qualifiedNameString); 190 191 // allScope() because the contract says that the whole project 192 GlobalSearchScope allScope = GlobalSearchScope.allScope(project); 193 if (lightClassGenerationSupport.packageExists(fqName, allScope)) { 194 return new KtLightPackage(psiManager, fqName, allScope); 195 } 196 197 return null; 198 } 199 200 @NotNull 201 @Override 202 public PsiPackage[] getSubPackages(@NotNull PsiPackage psiPackage, @NotNull final GlobalSearchScope scope) { 203 FqName packageFQN = new FqName(psiPackage.getQualifiedName()); 204 205 Collection<FqName> subpackages = lightClassGenerationSupport.getSubPackages(packageFQN, scope); 206 207 Collection<PsiPackage> answer = Collections2.transform(subpackages, new Function<FqName, PsiPackage>() { 208 @Override 209 public PsiPackage apply(@Nullable FqName input) { 210 return new KtLightPackage(psiManager, input, scope); 211 } 212 }); 213 214 return answer.toArray(new PsiPackage[answer.size()]); 215 } 216 217 @NotNull 218 @Override 219 public PsiClass[] getClasses(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) { 220 List<PsiClass> answer = new SmartList<PsiClass>(); 221 FqName packageFQN = new FqName(psiPackage.getQualifiedName()); 222 223 answer.addAll(lightClassGenerationSupport.getFacadeClassesInPackage(packageFQN, scope)); 224 225 Collection<KtClassOrObject> declarations = lightClassGenerationSupport.findClassOrObjectDeclarationsInPackage(packageFQN, scope); 226 for (KtClassOrObject declaration : declarations) { 227 PsiClass aClass = LightClassUtil.INSTANCE$.getPsiClass(declaration); 228 if (aClass != null) { 229 answer.add(aClass); 230 } 231 } 232 233 return sortByClasspath(answer, scope).toArray(new PsiClass[answer.size()]); 234 } 235 236 @Override 237 @NotNull 238 public PsiFile[] getPackageFiles(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) { 239 FqName packageFQN = new FqName(psiPackage.getQualifiedName()); 240 Collection<KtFile> result = lightClassGenerationSupport.findFilesForPackage(packageFQN, scope); 241 return result.toArray(new PsiFile[result.size()]); 242 } 243 244 @Override 245 @Nullable 246 public Condition<PsiFile> getPackageFilesFilter(@NotNull final PsiPackage psiPackage, @NotNull GlobalSearchScope scope) { 247 return new Condition<PsiFile>() { 248 @Override 249 public boolean value(PsiFile input) { 250 if (!(input instanceof KtFile)) { 251 return true; 252 } 253 return psiPackage.getQualifiedName().equals(((KtFile) input).getPackageFqName().asString()); 254 } 255 }; 256 } 257 258 private static class FindClassesRequest { 259 private final String fqName; 260 private final GlobalSearchScope scope; 261 262 private FindClassesRequest(@NotNull String fqName, @NotNull GlobalSearchScope scope) { 263 this.fqName = fqName; 264 this.scope = scope; 265 } 266 267 @Override 268 public boolean equals(Object o) { 269 if (this == o) return true; 270 if (o == null || getClass() != o.getClass()) return false; 271 272 FindClassesRequest request = (FindClassesRequest) o; 273 274 if (!fqName.equals(request.fqName)) return false; 275 if (!scope.equals(request.scope)) return false; 276 277 return true; 278 } 279 280 @Override 281 public int hashCode() { 282 int result = fqName.hashCode(); 283 result = 31 * result + (scope.hashCode()); 284 return result; 285 } 286 287 @Override 288 public String toString() { 289 return fqName + " in " + scope; 290 } 291 } 292 293 @NotNull 294 public static Comparator<PsiElement> byClasspathComparator(@NotNull final GlobalSearchScope searchScope) { 295 return new Comparator<PsiElement>() { 296 @Override 297 public int compare(@NotNull PsiElement o1, @NotNull PsiElement o2) { 298 VirtualFile f1 = PsiUtilCore.getVirtualFile(o1); 299 VirtualFile f2 = PsiUtilCore.getVirtualFile(o2); 300 if (f1 == f2) return 0; 301 if (f1 == null) return -1; 302 if (f2 == null) return 1; 303 return searchScope.compare(f2, f1); 304 } 305 }; 306 } 307 308 private static Collection<PsiClass> sortByClasspath(@NotNull List<PsiClass> classes, @NotNull GlobalSearchScope searchScope) { 309 if (classes.size() > 1) { 310 ContainerUtil.quickSort(classes, byClasspathComparator(searchScope)); 311 } 312 313 return classes; 314 } 315 }