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.kotlin.PackageClassUtils;
035    import org.jetbrains.kotlin.name.FqName;
036    import org.jetbrains.kotlin.name.NamePackage;
037    import org.jetbrains.kotlin.psi.JetClassOrObject;
038    import org.jetbrains.kotlin.psi.JetEnumEntry;
039    import org.jetbrains.kotlin.psi.JetFile;
040    import org.jetbrains.kotlin.resolve.jvm.KotlinFinderMarker;
041    
042    import java.util.Collection;
043    import java.util.Comparator;
044    import java.util.List;
045    import java.util.Set;
046    
047    public class JavaElementFinder extends PsiElementFinder implements 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            answer.addAll(lightClassGenerationSupport.getFacadeClasses(qualifiedName, scope));
121    
122            if (PackageClassUtils.isPackageClassFqName(qualifiedName)) {
123                answer.addAll(lightClassGenerationSupport.getPackageClasses(qualifiedName.parent(), scope));
124            }
125    
126            return sortByClasspath(answer, scope).toArray(new PsiClass[answer.size()]);
127        }
128    
129        // Finds explicitly declared classes and objects, not package classes
130        private void findClassesAndObjects(FqName qualifiedName, GlobalSearchScope scope, List<PsiClass> answer) {
131            Collection<JetClassOrObject> classOrObjectDeclarations =
132                    lightClassGenerationSupport.findClassOrObjectDeclarations(qualifiedName, scope);
133    
134            for (JetClassOrObject declaration : classOrObjectDeclarations) {
135                if (!(declaration instanceof JetEnumEntry)) {
136                    PsiClass lightClass = LightClassUtil.INSTANCE$.getPsiClass(declaration);
137                    if (lightClass != null) {
138                        answer.add(lightClass);
139                    }
140                }
141            }
142        }
143    
144        @NotNull
145        @Override
146        public Set<String> getClassNames(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
147            FqName packageFQN = new FqName(psiPackage.getQualifiedName());
148    
149            Collection<JetClassOrObject> declarations = lightClassGenerationSupport.findClassOrObjectDeclarationsInPackage(packageFQN, scope);
150    
151            Set<String> answer = Sets.newHashSet();
152            answer.add(PackageClassUtils.getPackageClassName(packageFQN));
153            answer.addAll(lightClassGenerationSupport.getFacadeNames(packageFQN, scope));
154    
155            for (JetClassOrObject declaration : declarations) {
156                String name = declaration.getName();
157                if (name != null) {
158                    answer.add(name);
159                }
160            }
161    
162            return answer;
163        }
164    
165        @Override
166        public PsiPackage findPackage(@NotNull String qualifiedNameString) {
167            if (!NamePackage.isValidJavaFqName(qualifiedNameString)) {
168                return null;
169            }
170    
171            FqName fqName = new FqName(qualifiedNameString);
172    
173            // allScope() because the contract says that the whole project
174            GlobalSearchScope allScope = GlobalSearchScope.allScope(project);
175            if (lightClassGenerationSupport.packageExists(fqName, allScope)) {
176                return new JetLightPackage(psiManager, fqName, allScope);
177            }
178    
179            return null;
180        }
181    
182        @NotNull
183        @Override
184        public PsiPackage[] getSubPackages(@NotNull PsiPackage psiPackage, @NotNull final GlobalSearchScope scope) {
185            FqName packageFQN = new FqName(psiPackage.getQualifiedName());
186    
187            Collection<FqName> subpackages = lightClassGenerationSupport.getSubPackages(packageFQN, scope);
188    
189            Collection<PsiPackage> answer = Collections2.transform(subpackages, new Function<FqName, PsiPackage>() {
190                @Override
191                public PsiPackage apply(@Nullable FqName input) {
192                    return new JetLightPackage(psiManager, input, scope);
193                }
194            });
195    
196            return answer.toArray(new PsiPackage[answer.size()]);
197        }
198    
199        @NotNull
200        @Override
201        public PsiClass[] getClasses(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
202            List<PsiClass> answer = new SmartList<PsiClass>();
203            FqName packageFQN = new FqName(psiPackage.getQualifiedName());
204    
205            answer.addAll(lightClassGenerationSupport.getPackageClasses(packageFQN, scope));
206            answer.addAll(lightClassGenerationSupport.getFacadeClassesInPackage(packageFQN, scope));
207    
208            Collection<JetClassOrObject> declarations = lightClassGenerationSupport.findClassOrObjectDeclarationsInPackage(packageFQN, scope);
209            for (JetClassOrObject declaration : declarations) {
210                PsiClass aClass = LightClassUtil.INSTANCE$.getPsiClass(declaration);
211                if (aClass != null) {
212                    answer.add(aClass);
213                }
214            }
215    
216            return sortByClasspath(answer, scope).toArray(new PsiClass[answer.size()]);
217        }
218    
219        @Override
220        @NotNull
221        public PsiFile[] getPackageFiles(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
222            FqName packageFQN = new FqName(psiPackage.getQualifiedName());
223            Collection<JetFile> result = lightClassGenerationSupport.findFilesForPackage(packageFQN, scope);
224            return result.toArray(new PsiFile[result.size()]);
225        }
226    
227        @Override
228        @Nullable
229        public Condition<PsiFile> getPackageFilesFilter(@NotNull final PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
230            return new Condition<PsiFile>() {
231                @Override
232                public boolean value(PsiFile input) {
233                    if (!(input instanceof JetFile)) {
234                        return true;
235                    }
236                    return psiPackage.getQualifiedName().equals(((JetFile) input).getPackageFqName().asString());
237                }
238            };
239        }
240    
241        private static class FindClassesRequest {
242            private final String fqName;
243            private final GlobalSearchScope scope;
244    
245            private FindClassesRequest(@NotNull String fqName, @NotNull GlobalSearchScope scope) {
246                this.fqName = fqName;
247                this.scope = scope;
248            }
249    
250            @Override
251            public boolean equals(Object o) {
252                if (this == o) return true;
253                if (o == null || getClass() != o.getClass()) return false;
254    
255                FindClassesRequest request = (FindClassesRequest) o;
256    
257                if (!fqName.equals(request.fqName)) return false;
258                if (!scope.equals(request.scope)) return false;
259    
260                return true;
261            }
262    
263            @Override
264            public int hashCode() {
265                int result = fqName.hashCode();
266                result = 31 * result + (scope.hashCode());
267                return result;
268            }
269    
270            @Override
271            public String toString() {
272                return fqName + " in " + scope;
273            }
274        }
275    
276        @NotNull
277        public static Comparator<PsiElement> byClasspathComparator(@NotNull final GlobalSearchScope searchScope) {
278            return new Comparator<PsiElement>() {
279                @Override
280                public int compare(@NotNull PsiElement o1, @NotNull PsiElement o2) {
281                    VirtualFile f1 = PsiUtilCore.getVirtualFile(o1);
282                    VirtualFile f2 = PsiUtilCore.getVirtualFile(o2);
283                    if (f1 == f2) return 0;
284                    if (f1 == null) return -1;
285                    if (f2 == null) return 1;
286                    return searchScope.compare(f2, f1);
287                }
288            };
289        }
290    
291        private static Collection<PsiClass> sortByClasspath(@NotNull List<PsiClass> classes, @NotNull GlobalSearchScope searchScope) {
292            if (classes.size() > 1) {
293                ContainerUtil.quickSort(classes, byClasspathComparator(searchScope));
294            }
295    
296            return classes;
297        }
298    }