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