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