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.JetClassOrObject;
038 import org.jetbrains.jet.lang.psi.JetEnumEntry;
039 import org.jetbrains.jet.lang.psi.JetFile;
040 import org.jetbrains.jet.lang.resolve.java.JavaPsiFacadeKotlinHacks;
041 import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
042 import org.jetbrains.jet.lang.resolve.kotlin.PackagePartClassUtils;
043 import org.jetbrains.jet.lang.resolve.name.FqName;
044 import org.jetbrains.jet.lang.resolve.name.NamePackage;
045
046 import java.util.Collection;
047 import java.util.List;
048 import java.util.Set;
049
050 public class JavaElementFinder extends PsiElementFinder implements JavaPsiFacadeKotlinHacks.KotlinFinderMarker {
051
052 @NotNull
053 public static JavaElementFinder getInstance(@NotNull Project project) {
054 PsiElementFinder[] extensions = Extensions.getArea(project).getExtensionPoint(PsiElementFinder.EP_NAME).getExtensions();
055 for (PsiElementFinder extension : extensions) {
056 if (extension instanceof JavaElementFinder) {
057 return (JavaElementFinder) extension;
058 }
059 }
060 throw new IllegalStateException(JavaElementFinder.class.getSimpleName() + " is not found for project " + project);
061 }
062
063 private final Project project;
064 private final PsiManager psiManager;
065 private final LightClassGenerationSupport lightClassGenerationSupport;
066
067 private final CachedValue<SLRUCache<FindClassesRequest, PsiClass[]>> findClassesCache;
068
069 public JavaElementFinder(
070 @NotNull Project project,
071 @NotNull LightClassGenerationSupport lightClassGenerationSupport
072 ) {
073 this.project = project;
074 this.psiManager = PsiManager.getInstance(project);
075 this.lightClassGenerationSupport = lightClassGenerationSupport;
076 this.findClassesCache = CachedValuesManager.getManager(project).createCachedValue(
077 new CachedValueProvider<SLRUCache<FindClassesRequest, PsiClass[]>>() {
078 @Nullable
079 @Override
080 public Result<SLRUCache<FindClassesRequest, PsiClass[]>> compute() {
081 return new Result<SLRUCache<FindClassesRequest, PsiClass[]>>(
082 new SLRUCache<FindClassesRequest, PsiClass[]>(30, 10) {
083 @NotNull
084 @Override
085 public PsiClass[] createValue(FindClassesRequest key) {
086 return doFindClasses(key.fqName, key.scope);
087 }
088 },
089 PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT
090 );
091 }
092 },
093 false
094 );
095 }
096
097 @Override
098 public PsiClass findClass(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) {
099 PsiClass[] allClasses = findClasses(qualifiedName, scope);
100 return allClasses.length > 0 ? allClasses[0] : null;
101 }
102
103 @NotNull
104 @Override
105 public PsiClass[] findClasses(@NotNull String qualifiedNameString, @NotNull GlobalSearchScope scope) {
106 SLRUCache<FindClassesRequest, PsiClass[]> value = findClassesCache.getValue();
107 synchronized (value) {
108 return value.get(new FindClassesRequest(qualifiedNameString, scope));
109 }
110 }
111
112 private PsiClass[] doFindClasses(String qualifiedNameString, GlobalSearchScope scope) {
113 if (!NamePackage.isValidJavaFqName(qualifiedNameString)) {
114 return PsiClass.EMPTY_ARRAY;
115 }
116
117 List<PsiClass> answer = new SmartList<PsiClass>();
118
119 FqName qualifiedName = new FqName(qualifiedNameString);
120
121 findClassesAndObjects(qualifiedName, scope, answer);
122
123 if (PackageClassUtils.isPackageClassFqName(qualifiedName)) {
124 findPackageClass(qualifiedName.parent(), scope, answer);
125 }
126
127 return answer.toArray(new PsiClass[answer.size()]);
128 }
129
130 // Finds explicitly declared classes and objects, not package classes
131 private void findClassesAndObjects(FqName qualifiedName, GlobalSearchScope scope, List<PsiClass> answer) {
132 Collection<JetClassOrObject> classOrObjectDeclarations =
133 lightClassGenerationSupport.findClassOrObjectDeclarations(qualifiedName, scope);
134
135 for (JetClassOrObject declaration : classOrObjectDeclarations) {
136 if (!(declaration instanceof JetEnumEntry)) {
137 PsiClass lightClass = LightClassUtil.getPsiClass(declaration);
138 if (lightClass != null) {
139 answer.add(lightClass);
140 }
141 }
142 }
143 }
144
145 private void findPackageClass(FqName qualifiedName, GlobalSearchScope scope, List<PsiClass> answer) {
146 List<LightClassGenerationSupport.KotlinLightPackageClassInfo>
147 packageClassesInfos = lightClassGenerationSupport.findPackageClassesInfos(qualifiedName, scope);
148 for (LightClassGenerationSupport.KotlinLightPackageClassInfo info : packageClassesInfos) {
149 Collection<JetFile> files = info.getFiles();
150 if (PackagePartClassUtils.getPackageFilesWithCallables(files).isEmpty()) continue;
151 KotlinLightClassForPackage lightClass =
152 KotlinLightClassForPackage.create(psiManager, qualifiedName, info.getScope(), files);
153 if (lightClass == null) continue;
154
155 answer.add(lightClass);
156
157 if (files.size() > 1) {
158 for (JetFile file : files) {
159 answer.add(new FakeLightClassForFileOfPackage(psiManager, lightClass, file));
160 }
161 }
162 }
163 }
164
165 @NotNull
166 @Override
167 public Set<String> getClassNames(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
168 FqName packageFQN = new FqName(psiPackage.getQualifiedName());
169
170 Collection<JetClassOrObject> declarations = lightClassGenerationSupport.findClassOrObjectDeclarationsInPackage(packageFQN, scope);
171
172 Set<String> answer = Sets.newHashSet();
173 answer.add(PackageClassUtils.getPackageClassName(packageFQN));
174
175 for (JetClassOrObject declaration : declarations) {
176 String name = declaration.getName();
177 if (name != null) {
178 answer.add(name);
179 }
180 }
181
182 return answer;
183 }
184
185 @Override
186 public PsiPackage findPackage(@NotNull String qualifiedNameString) {
187 if (!NamePackage.isValidJavaFqName(qualifiedNameString)) {
188 return null;
189 }
190
191 FqName fqName = new FqName(qualifiedNameString);
192
193 // allScope() because the contract says that the whole project
194 GlobalSearchScope allScope = GlobalSearchScope.allScope(project);
195 if (lightClassGenerationSupport.packageExists(fqName, allScope)) {
196 return new JetLightPackage(psiManager, fqName, allScope);
197 }
198
199 return null;
200 }
201
202 @NotNull
203 @Override
204 public PsiPackage[] getSubPackages(@NotNull PsiPackage psiPackage, @NotNull final GlobalSearchScope scope) {
205 FqName packageFQN = new FqName(psiPackage.getQualifiedName());
206
207 Collection<FqName> subpackages = lightClassGenerationSupport.getSubPackages(packageFQN, scope);
208
209 Collection<PsiPackage> answer = Collections2.transform(subpackages, new Function<FqName, PsiPackage>() {
210 @Override
211 public PsiPackage apply(@Nullable FqName input) {
212 return new JetLightPackage(psiManager, input, scope);
213 }
214 });
215
216 return answer.toArray(new PsiPackage[answer.size()]);
217 }
218
219 @NotNull
220 @Override
221 public PsiClass[] getClasses(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
222 List<PsiClass> answer = new SmartList<PsiClass>();
223 FqName packageFQN = new FqName(psiPackage.getQualifiedName());
224
225 findPackageClass(packageFQN, scope, answer);
226
227 Collection<JetClassOrObject> declarations = lightClassGenerationSupport.findClassOrObjectDeclarationsInPackage(packageFQN, scope);
228 for (JetClassOrObject declaration : declarations) {
229 PsiClass aClass = LightClassUtil.getPsiClass(declaration);
230 if (aClass != null) {
231 answer.add(aClass);
232 }
233 }
234
235 return answer.toArray(new PsiClass[answer.size()]);
236 }
237
238 private static class FindClassesRequest {
239 private final String fqName;
240 private final GlobalSearchScope scope;
241
242 private FindClassesRequest(@NotNull String fqName, @NotNull GlobalSearchScope scope) {
243 this.fqName = fqName;
244 this.scope = scope;
245 }
246
247 @Override
248 public boolean equals(Object o) {
249 if (this == o) return true;
250 if (o == null || getClass() != o.getClass()) return false;
251
252 FindClassesRequest request = (FindClassesRequest) o;
253
254 if (!fqName.equals(request.fqName)) return false;
255 if (!scope.equals(request.scope)) return false;
256
257 return true;
258 }
259
260 @Override
261 public int hashCode() {
262 int result = fqName.hashCode();
263 result = 31 * result + (scope.hashCode());
264 return result;
265 }
266 }
267 }
268