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.resolve.java.JavaPsiFacadeKotlinHacks;
040    import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
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                answer.addAll(lightClassGenerationSupport.getPackageClasses(qualifiedName.parent(), scope));
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        @NotNull
144        @Override
145        public Set<String> getClassNames(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
146            FqName packageFQN = new FqName(psiPackage.getQualifiedName());
147    
148            Collection<JetClassOrObject> declarations = lightClassGenerationSupport.findClassOrObjectDeclarationsInPackage(packageFQN, scope);
149    
150            Set<String> answer = Sets.newHashSet();
151            answer.add(PackageClassUtils.getPackageClassName(packageFQN));
152    
153            for (JetClassOrObject declaration : declarations) {
154                String name = declaration.getName();
155                if (name != null) {
156                    answer.add(name);
157                }
158            }
159    
160            return answer;
161        }
162    
163        @Override
164        public PsiPackage findPackage(@NotNull String qualifiedNameString) {
165            if (!NamePackage.isValidJavaFqName(qualifiedNameString)) {
166                return null;
167            }
168    
169            FqName fqName = new FqName(qualifiedNameString);
170    
171            // allScope() because the contract says that the whole project
172            GlobalSearchScope allScope = GlobalSearchScope.allScope(project);
173            if (lightClassGenerationSupport.packageExists(fqName, allScope)) {
174                return new JetLightPackage(psiManager, fqName, allScope);
175            }
176    
177            return null;
178        }
179    
180        @NotNull
181        @Override
182        public PsiPackage[] getSubPackages(@NotNull PsiPackage psiPackage, @NotNull final GlobalSearchScope scope) {
183            FqName packageFQN = new FqName(psiPackage.getQualifiedName());
184    
185            Collection<FqName> subpackages = lightClassGenerationSupport.getSubPackages(packageFQN, scope);
186    
187            Collection<PsiPackage> answer = Collections2.transform(subpackages, new Function<FqName, PsiPackage>() {
188                @Override
189                public PsiPackage apply(@Nullable FqName input) {
190                    return new JetLightPackage(psiManager, input, scope);
191                }
192            });
193    
194            return answer.toArray(new PsiPackage[answer.size()]);
195        }
196    
197        @NotNull
198        @Override
199        public PsiClass[] getClasses(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
200            List<PsiClass> answer = new SmartList<PsiClass>();
201            FqName packageFQN = new FqName(psiPackage.getQualifiedName());
202    
203            answer.addAll(lightClassGenerationSupport.getPackageClasses(packageFQN, scope));
204    
205            Collection<JetClassOrObject> declarations = lightClassGenerationSupport.findClassOrObjectDeclarationsInPackage(packageFQN, scope);
206            for (JetClassOrObject declaration : declarations) {
207                PsiClass aClass = LightClassUtil.getPsiClass(declaration);
208                if (aClass != null) {
209                    answer.add(aClass);
210                }
211            }
212    
213            return answer.toArray(new PsiClass[answer.size()]);
214        }
215    
216        private static class FindClassesRequest {
217            private final String fqName;
218            private final GlobalSearchScope scope;
219    
220            private FindClassesRequest(@NotNull String fqName, @NotNull GlobalSearchScope scope) {
221                this.fqName = fqName;
222                this.scope = scope;
223            }
224    
225            @Override
226            public boolean equals(Object o) {
227                if (this == o) return true;
228                if (o == null || getClass() != o.getClass()) return false;
229    
230                FindClassesRequest request = (FindClassesRequest) o;
231    
232                if (!fqName.equals(request.fqName)) return false;
233                if (!scope.equals(request.scope)) return false;
234    
235                return true;
236            }
237    
238            @Override
239            public int hashCode() {
240                int result = fqName.hashCode();
241                result = 31 * result + (scope.hashCode());
242                return result;
243            }
244        }
245    }
246