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