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