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.psi.*;
026    import com.intellij.psi.search.GlobalSearchScope;
027    import com.intellij.psi.util.CachedValue;
028    import com.intellij.psi.util.CachedValueProvider;
029    import com.intellij.psi.util.CachedValuesManager;
030    import com.intellij.psi.util.PsiModificationTracker;
031    import com.intellij.util.SmartList;
032    import com.intellij.util.containers.SLRUCache;
033    import org.jetbrains.annotations.NotNull;
034    import org.jetbrains.annotations.Nullable;
035    import org.jetbrains.kotlin.load.kotlin.PackageClassUtils;
036    import org.jetbrains.kotlin.name.FqName;
037    import org.jetbrains.kotlin.name.NamePackage;
038    import org.jetbrains.kotlin.psi.JetClassOrObject;
039    import org.jetbrains.kotlin.psi.JetEnumEntry;
040    import org.jetbrains.kotlin.psi.JetFile;
041    import org.jetbrains.kotlin.resolve.jvm.KotlinFinderMarker;
042    
043    import java.util.Collection;
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            if (PackageClassUtils.isPackageClassFqName(qualifiedName)) {
121                answer.addAll(lightClassGenerationSupport.getPackageClasses(qualifiedName.parent(), scope));
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        @NotNull
143        @Override
144        public Set<String> getClassNames(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
145            FqName packageFQN = new FqName(psiPackage.getQualifiedName());
146    
147            Collection<JetClassOrObject> declarations = lightClassGenerationSupport.findClassOrObjectDeclarationsInPackage(packageFQN, scope);
148    
149            Set<String> answer = Sets.newHashSet();
150            answer.add(PackageClassUtils.getPackageClassName(packageFQN));
151    
152            for (JetClassOrObject declaration : declarations) {
153                String name = declaration.getName();
154                if (name != null) {
155                    answer.add(name);
156                }
157            }
158    
159            return answer;
160        }
161    
162        @Override
163        public PsiPackage findPackage(@NotNull String qualifiedNameString) {
164            if (!NamePackage.isValidJavaFqName(qualifiedNameString)) {
165                return null;
166            }
167    
168            FqName fqName = new FqName(qualifiedNameString);
169    
170            // allScope() because the contract says that the whole project
171            GlobalSearchScope allScope = GlobalSearchScope.allScope(project);
172            if (lightClassGenerationSupport.packageExists(fqName, allScope)) {
173                return new JetLightPackage(psiManager, fqName, allScope);
174            }
175    
176            return null;
177        }
178    
179        @NotNull
180        @Override
181        public PsiPackage[] getSubPackages(@NotNull PsiPackage psiPackage, @NotNull final GlobalSearchScope scope) {
182            FqName packageFQN = new FqName(psiPackage.getQualifiedName());
183    
184            Collection<FqName> subpackages = lightClassGenerationSupport.getSubPackages(packageFQN, scope);
185    
186            Collection<PsiPackage> answer = Collections2.transform(subpackages, new Function<FqName, PsiPackage>() {
187                @Override
188                public PsiPackage apply(@Nullable FqName input) {
189                    return new JetLightPackage(psiManager, input, scope);
190                }
191            });
192    
193            return answer.toArray(new PsiPackage[answer.size()]);
194        }
195    
196        @NotNull
197        @Override
198        public PsiClass[] getClasses(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
199            List<PsiClass> answer = new SmartList<PsiClass>();
200            FqName packageFQN = new FqName(psiPackage.getQualifiedName());
201    
202            answer.addAll(lightClassGenerationSupport.getPackageClasses(packageFQN, scope));
203    
204            Collection<JetClassOrObject> declarations = lightClassGenerationSupport.findClassOrObjectDeclarationsInPackage(packageFQN, scope);
205            for (JetClassOrObject declaration : declarations) {
206                PsiClass aClass = LightClassUtil.getPsiClass(declaration);
207                if (aClass != null) {
208                    answer.add(aClass);
209                }
210            }
211    
212            return answer.toArray(new PsiClass[answer.size()]);
213        }
214    
215        // implements a method added in 14.1
216        @NotNull
217        public PsiFile[] getPackageFiles(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
218            FqName packageFQN = new FqName(psiPackage.getQualifiedName());
219            Collection<JetFile> result = lightClassGenerationSupport.findFilesForPackage(packageFQN, scope);
220            return result.toArray(new PsiFile[result.size()]);
221        }
222    
223        // implements a method added in IDEA 14.1
224        @SuppressWarnings({"UnusedDeclaration", "MethodMayBeStatic"})
225        @Nullable
226        public Condition<PsiFile> getPackageFilesFilter(@NotNull final PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
227            return new Condition<PsiFile>() {
228                @Override
229                public boolean value(@Nullable PsiFile input) {
230                    if (!(input instanceof JetFile)) {
231                        return true;
232                    }
233                    return psiPackage.getQualifiedName().equals(((JetFile) input).getPackageFqName().asString());
234                }
235            };
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