001 /* 002 * Copyright 2010-2014 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.jet.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.psi.PsiClass; 025 import com.intellij.psi.PsiElementFinder; 026 import com.intellij.psi.PsiManager; 027 import com.intellij.psi.PsiPackage; 028 import com.intellij.psi.search.GlobalSearchScope; 029 import com.intellij.psi.util.CachedValue; 030 import com.intellij.psi.util.CachedValueProvider; 031 import com.intellij.psi.util.CachedValuesManager; 032 import com.intellij.psi.util.PsiModificationTracker; 033 import com.intellij.util.SmartList; 034 import com.intellij.util.containers.SLRUCache; 035 import org.jetbrains.annotations.NotNull; 036 import org.jetbrains.annotations.Nullable; 037 import org.jetbrains.jet.lang.psi.*; 038 import org.jetbrains.jet.lang.resolve.java.JavaPsiFacadeKotlinHacks; 039 import org.jetbrains.jet.lang.resolve.java.PackageClassUtils; 040 import org.jetbrains.jet.lang.resolve.name.FqName; 041 import org.jetbrains.jet.lang.resolve.name.NamePackage; 042 043 import java.util.Collection; 044 import java.util.List; 045 import java.util.Set; 046 047 public class JavaElementFinder extends PsiElementFinder implements JavaPsiFacadeKotlinHacks.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 (!NamePackage.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 if (PackageClassUtils.isPackageClassFqName(qualifiedName)) { 121 findPackageClass(qualifiedName.parent(), scope, answer); 122 } 123 124 return answer.toArray(new PsiClass[answer.size()]); 125 } 126 127 // Finds explicitly declared classes and objects, not package classes 128 private void findClassesAndObjects(FqName qualifiedName, GlobalSearchScope scope, List<PsiClass> answer) { 129 Collection<JetClassOrObject> classOrObjectDeclarations = 130 lightClassGenerationSupport.findClassOrObjectDeclarations(qualifiedName, scope); 131 132 for (JetClassOrObject declaration : classOrObjectDeclarations) { 133 if (!(declaration instanceof JetEnumEntry)) { 134 PsiClass lightClass = LightClassUtil.getPsiClass(declaration); 135 if (lightClass != null) { 136 answer.add(lightClass); 137 } 138 } 139 } 140 } 141 142 private void findPackageClass(FqName qualifiedName, GlobalSearchScope scope, List<PsiClass> answer) { 143 Collection<JetFile> filesForPackage = lightClassGenerationSupport.findFilesForPackage(qualifiedName, scope); 144 if (!shouldGeneratePackageClass(filesForPackage)) return; 145 146 KotlinLightClassForPackage lightClass = KotlinLightClassForPackage.create(psiManager, qualifiedName, scope, filesForPackage); 147 if (lightClass == null) return; 148 149 answer.add(lightClass); 150 151 if (filesForPackage.size() > 1) { 152 for (JetFile file : filesForPackage) { 153 answer.add(new FakeLightClassForFileOfPackage(psiManager, lightClass, file)); 154 } 155 } 156 } 157 158 private static boolean shouldGeneratePackageClass(@NotNull Collection<JetFile> packageFiles) { 159 for (JetFile file : packageFiles) { 160 for (JetDeclaration declaration : file.getDeclarations()) { 161 if (declaration instanceof JetProperty || declaration instanceof JetNamedFunction) { 162 return true; 163 } 164 } 165 } 166 167 return false; 168 } 169 170 @NotNull 171 @Override 172 public Set<String> getClassNames(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) { 173 FqName packageFQN = new FqName(psiPackage.getQualifiedName()); 174 175 Collection<JetClassOrObject> declarations = lightClassGenerationSupport.findClassOrObjectDeclarationsInPackage(packageFQN, scope); 176 177 Set<String> answer = Sets.newHashSet(); 178 answer.add(PackageClassUtils.getPackageClassName(packageFQN)); 179 180 for (JetClassOrObject declaration : declarations) { 181 String name = declaration.getName(); 182 if (name != null) { 183 answer.add(name); 184 } 185 } 186 187 return answer; 188 } 189 190 @Override 191 public PsiPackage findPackage(@NotNull String qualifiedNameString) { 192 if (!NamePackage.isValidJavaFqName(qualifiedNameString)) { 193 return null; 194 } 195 196 FqName fqName = new FqName(qualifiedNameString); 197 198 // allScope() because the contract says that the whole project 199 GlobalSearchScope allScope = GlobalSearchScope.allScope(project); 200 if (lightClassGenerationSupport.packageExists(fqName, allScope)) { 201 return new JetLightPackage(psiManager, fqName, allScope); 202 } 203 204 return null; 205 } 206 207 @NotNull 208 @Override 209 public PsiPackage[] getSubPackages(@NotNull PsiPackage psiPackage, @NotNull final GlobalSearchScope scope) { 210 FqName packageFQN = new FqName(psiPackage.getQualifiedName()); 211 212 Collection<FqName> subpackages = lightClassGenerationSupport.getSubPackages(packageFQN, scope); 213 214 Collection<PsiPackage> answer = Collections2.transform(subpackages, new Function<FqName, PsiPackage>() { 215 @Override 216 public PsiPackage apply(@Nullable FqName input) { 217 return new JetLightPackage(psiManager, input, scope); 218 } 219 }); 220 221 return answer.toArray(new PsiPackage[answer.size()]); 222 } 223 224 @NotNull 225 @Override 226 public PsiClass[] getClasses(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) { 227 List<PsiClass> answer = new SmartList<PsiClass>(); 228 FqName packageFQN = new FqName(psiPackage.getQualifiedName()); 229 230 findPackageClass(packageFQN, scope, answer); 231 232 Collection<JetClassOrObject> declarations = lightClassGenerationSupport.findClassOrObjectDeclarationsInPackage(packageFQN, scope); 233 for (JetClassOrObject declaration : declarations) { 234 PsiClass aClass = LightClassUtil.getPsiClass(declaration); 235 if (aClass != null) { 236 answer.add(aClass); 237 } 238 } 239 240 return answer.toArray(new PsiClass[answer.size()]); 241 } 242 243 private static class FindClassesRequest { 244 private final String fqName; 245 private final GlobalSearchScope scope; 246 247 private FindClassesRequest(@NotNull String fqName, @NotNull GlobalSearchScope scope) { 248 this.fqName = fqName; 249 this.scope = scope; 250 } 251 252 @Override 253 public boolean equals(Object o) { 254 if (this == o) return true; 255 if (o == null || getClass() != o.getClass()) return false; 256 257 FindClassesRequest request = (FindClassesRequest) o; 258 259 if (!fqName.equals(request.fqName)) return false; 260 if (!scope.equals(request.scope)) return false; 261 262 return true; 263 } 264 265 @Override 266 public int hashCode() { 267 int result = fqName.hashCode(); 268 result = 31 * result + (scope.hashCode()); 269 return result; 270 } 271 } 272 } 273