e401087d5a86c57d55ea20ac016870eb8506992e
[idea/community.git] / java / java-indexing-impl / src / com / intellij / psi / impl / search / JavaFunctionalExpressionSearcher.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.psi.impl.search;
17
18 import com.intellij.compiler.CompilerReferenceService;
19 import com.intellij.compiler.CompilerDirectHierarchyInfo;
20 import com.intellij.ide.highlighter.JavaFileType;
21 import com.intellij.lang.injection.InjectedLanguageManager;
22 import com.intellij.openapi.application.QueryExecutorBase;
23 import com.intellij.openapi.application.ReadAction;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.module.Module;
26 import com.intellij.openapi.module.ModuleManager;
27 import com.intellij.openapi.progress.ProgressManager;
28 import com.intellij.openapi.project.Project;
29 import com.intellij.openapi.roots.LanguageLevelModuleExtension;
30 import com.intellij.openapi.roots.ModuleRootManager;
31 import com.intellij.openapi.vfs.VirtualFile;
32 import com.intellij.pom.java.LanguageLevel;
33 import com.intellij.psi.*;
34 import com.intellij.psi.impl.java.FunExprOccurrence;
35 import com.intellij.psi.impl.java.JavaFunctionalExpressionIndex;
36 import com.intellij.psi.impl.java.stubs.FunctionalExpressionKey;
37 import com.intellij.psi.impl.java.stubs.index.JavaMethodParameterTypesIndex;
38 import com.intellij.psi.search.*;
39 import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
40 import com.intellij.psi.search.searches.FunctionalExpressionSearch.SearchParameters;
41 import com.intellij.psi.stubs.StubIndex;
42 import com.intellij.psi.util.InheritanceUtil;
43 import com.intellij.psi.util.PsiTreeUtil;
44 import com.intellij.psi.util.PsiUtil;
45 import com.intellij.psi.util.PsiUtilCore;
46 import com.intellij.util.PairProcessor;
47 import com.intellij.util.Processor;
48 import com.intellij.util.Processors;
49 import com.intellij.util.containers.ContainerUtil;
50 import com.intellij.util.containers.HashSet;
51 import com.intellij.util.containers.JBIterable;
52 import com.intellij.util.containers.MultiMap;
53 import com.intellij.util.indexing.FileBasedIndex;
54 import org.jetbrains.annotations.NotNull;
55 import org.jetbrains.annotations.Nullable;
56 import org.jetbrains.annotations.TestOnly;
57
58 import java.util.*;
59 import java.util.concurrent.atomic.AtomicInteger;
60
61 import static com.intellij.util.ObjectUtils.assertNotNull;
62
63 public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunctionalExpression, SearchParameters> {
64   private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.search.JavaFunctionalExpressionSearcher");
65   public static final int SMART_SEARCH_THRESHOLD = 5;
66
67   @Override
68   public void processQuery(@NotNull SearchParameters p, @NotNull Processor<PsiFunctionalExpression> consumer) {
69     List<SamDescriptor> descriptors = calcDescriptors(p);
70     Project project = PsiUtilCore.getProjectInReadAction(p.getElementToSearch());
71     if (project == null) return;
72
73     SearchScope searchScope = ReadAction.compute(() -> p.getEffectiveSearchScope());
74     if (searchScope instanceof GlobalSearchScope && !performSearchUsingCompilerIndices(descriptors,
75                                                                                        (GlobalSearchScope)searchScope,
76                                                                                        project,
77                                                                                        consumer)) {
78       return;
79     }
80
81     AtomicInteger exprCount = new AtomicInteger();
82     AtomicInteger fileCount = new AtomicInteger();
83
84     PsiManager manager = ReadAction.compute(() -> p.getElementToSearch().getManager());
85     manager.startBatchFilesProcessingMode();
86     try {
87       processOffsets(descriptors, (file, offsets) -> {
88         fileCount.incrementAndGet();
89         exprCount.addAndGet(offsets.size());
90         return processFile(consumer, descriptors, file, offsets);
91       });
92     }
93     finally {
94       manager.finishBatchFilesProcessingMode();
95     }
96     if (exprCount.get() > 0) {
97       LOG.debug("Loaded " + exprCount.get() + " fun-expressions in " + fileCount.get() + " files");
98     }
99   }
100
101   @TestOnly
102   public static Set<VirtualFile> getFilesToSearchInPsi(PsiClass samClass) {
103     Set<VirtualFile> result = new HashSet<>();
104     processOffsets(calcDescriptors(new SearchParameters(samClass, samClass.getUseScope())), (file, offsets) -> result.add(file));
105     return result;
106   }
107
108   @NotNull
109   private static List<SamDescriptor> calcDescriptors(@NotNull SearchParameters queryParameters) {
110     List<SamDescriptor> descriptors = new ArrayList<>();
111
112     ReadAction.run(() -> {
113       PsiClass aClass = queryParameters.getElementToSearch();
114       if (!aClass.isValid() || !aClass.isInterface()) {
115         return;
116       }
117       Project project = aClass.getProject();
118       if (InjectedLanguageManager.getInstance(project).isInjectedFragment(aClass.getContainingFile()) || !hasJava8Modules(project)) {
119         return;
120       }
121
122       for (PsiClass samClass : processSubInterfaces(aClass)) {
123         if (LambdaUtil.isFunctionalClass(samClass)) {
124           PsiMethod saMethod = assertNotNull(LambdaUtil.getFunctionalInterfaceMethod(samClass));
125           PsiType samType = saMethod.getReturnType();
126           if (samType == null) continue;
127
128           SearchScope scope = samClass.getUseScope().intersectWith(queryParameters.getEffectiveSearchScope());
129           descriptors.add(new SamDescriptor(samClass, saMethod, samType, GlobalSearchScopeUtil.toGlobalSearchScope(scope, project)));
130         }
131       }
132     });
133     return descriptors;
134   }
135
136   @NotNull
137   private static Set<VirtualFile> getLikelyFiles(List<SamDescriptor> descriptors) {
138     return JBIterable.from(descriptors).flatMap(SamDescriptor::getMostLikelyFiles).toSet();
139   }
140
141   @NotNull
142   private static MultiMap<VirtualFile, FunExprOccurrence> getAllOccurrences(List<SamDescriptor> descriptors) {
143     MultiMap<VirtualFile, FunExprOccurrence> result = MultiMap.createLinkedSet();
144     for (SamDescriptor descriptor : descriptors) {
145       ReadAction.run(() -> {
146         for (FunctionalExpressionKey key : descriptor.generateKeys()) {
147           FileBasedIndex.getInstance().processValues(JavaFunctionalExpressionIndex.INDEX_ID, key, null, (file, infos) -> {
148             ProgressManager.checkCanceled();
149             result.putValues(file, infos);
150             return true;
151           }, new JavaSourceFilterScope(descriptor.effectiveUseScope));
152         }
153       });
154     }
155     LOG.debug("Found " + result.values().size() + " fun-expressions in " + result.keySet().size() + " files");
156     return result;
157   }
158
159   private static void processOffsets(List<SamDescriptor> descriptors, PairProcessor<VirtualFile, List<Integer>> processor) {
160     if (descriptors.isEmpty()) return;
161
162     List<PsiClass> samClasses = ContainerUtil.map(descriptors, d -> d.samClass);
163     MultiMap<VirtualFile, FunExprOccurrence> allCandidates = getAllOccurrences(descriptors);
164     for (VirtualFile vFile : putLikelyFilesFirst(descriptors, allCandidates.keySet())) {
165       List<FunExprOccurrence> toLoad = filterInapplicable(samClasses, vFile, allCandidates.get(vFile));
166       if (!toLoad.isEmpty()) {
167         LOG.trace("To load " + vFile.getPath() + " with values: " + toLoad);
168         if (!processor.process(vFile, ContainerUtil.map(toLoad, it -> it.funExprOffset))) {
169           return;
170         }
171       }
172     }
173   }
174
175   @NotNull
176   private static Set<VirtualFile> putLikelyFilesFirst(List<SamDescriptor> descriptors, Set<VirtualFile> allFiles) {
177     Set<VirtualFile> orderedFiles = new LinkedHashSet<>(allFiles);
178     orderedFiles.retainAll(getLikelyFiles(descriptors));
179     orderedFiles.addAll(allFiles);
180     return orderedFiles;
181   }
182
183   @NotNull
184   private static List<FunExprOccurrence> filterInapplicable(List<PsiClass> samClasses,
185                                                             VirtualFile vFile,
186                                                             Collection<FunExprOccurrence> occurrences) {
187     return ReadAction.compute(() -> ContainerUtil.filter(occurrences, it -> it.canHaveType(samClasses, vFile)));
188   }
189
190   private static boolean processFile(@NotNull Processor<PsiFunctionalExpression> consumer,
191                                      List<SamDescriptor> descriptors,
192                                      VirtualFile vFile, Collection<Integer> offsets) {
193     return ReadAction.compute(() -> {
194       PsiFile file = descriptors.get(0).samClass.getManager().findFile(vFile);
195       if (!(file instanceof PsiJavaFile)) {
196         LOG.error("Non-java file " + file + "; " + vFile);
197         return true;
198       }
199
200       for (Integer offset : offsets) {
201         PsiFunctionalExpression expression = PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiFunctionalExpression.class, false);
202         if (expression == null || expression.getTextRange().getStartOffset() != offset) {
203           LOG.error("Fun expression not found in " + file + " at " + offset);
204           continue;
205         }
206
207         if (hasType(descriptors, expression) && !consumer.process(expression)) {
208           return false;
209         }
210       }
211
212       return true;
213     });
214   }
215
216   private static boolean hasType(List<SamDescriptor> descriptors, PsiFunctionalExpression expression) {
217     if (!canHaveType(expression, ContainerUtil.map(descriptors, d -> d.samClass))) return false;
218
219     PsiClass actualClass = PsiUtil.resolveClassInType(expression.getFunctionalInterfaceType());
220     return ContainerUtil.exists(descriptors, d -> InheritanceUtil.isInheritorOrSelf(actualClass, d.samClass, true));
221   }
222
223   private static boolean canHaveType(PsiFunctionalExpression expression, List<PsiClass> samClasses) {
224     PsiElement parent = expression.getParent();
225     if (parent instanceof PsiExpressionList && parent.getParent() instanceof PsiMethodCallExpression) {
226       PsiExpression[] args = ((PsiExpressionList)parent).getExpressions();
227       int argIndex = Arrays.asList(args).indexOf(expression);
228       PsiReferenceExpression methodExpression = ((PsiMethodCallExpression)parent.getParent()).getMethodExpression();
229       PsiExpression qualifier = methodExpression.getQualifierExpression();
230       String methodName = methodExpression.getReferenceName();
231       if (qualifier != null && methodName != null && argIndex >= 0) {
232         Set<PsiClass> approximateTypes = ApproximateResolver.getPossibleTypes(qualifier, 10);
233         List<PsiMethod> methods = approximateTypes == null ? null :
234                                   ApproximateResolver.getPossibleMethods(approximateTypes, methodName, args.length);
235         return methods == null ||
236                ContainerUtil.exists(methods, m -> FunExprOccurrence.hasCompatibleParameter(m, argIndex, samClasses));
237       }
238     }
239     return true;
240   }
241
242   private static boolean hasJava8Modules(Project project) {
243     final boolean projectLevelIsHigh = PsiUtil.getLanguageLevel(project).isAtLeast(LanguageLevel.JDK_1_8);
244
245     for (Module module : ModuleManager.getInstance(project).getModules()) {
246       final LanguageLevelModuleExtension extension = ModuleRootManager.getInstance(module).getModuleExtension(LanguageLevelModuleExtension.class);
247       if (extension != null) {
248         final LanguageLevel level = extension.getLanguageLevel();
249         if (level == null && projectLevelIsHigh || level != null && level.isAtLeast(LanguageLevel.JDK_1_8)) {
250           return true;
251         }
252       }
253     }
254     return false;
255   }
256
257   private static Set<PsiClass> processSubInterfaces(PsiClass base) {
258     Set<PsiClass> result = new HashSet<>();
259     new Object() {
260       void visit(PsiClass c) {
261         if (!result.add(c)) return;
262
263         DirectClassInheritorsSearch.search(c).forEach(candidate -> {
264           if (candidate.isInterface()) {
265             visit(candidate);
266           }
267           return true;
268         });
269       }
270     }.visit(base);
271     return result;
272   }
273
274   private static class SamDescriptor {
275     final PsiClass samClass;
276     final int samParamCount;
277     final boolean booleanCompatible;
278     final boolean isVoid;
279     GlobalSearchScope effectiveUseScope;
280
281     SamDescriptor(PsiClass samClass, PsiMethod samMethod, PsiType samType, GlobalSearchScope useScope) {
282       this.samClass = samClass;
283       this.effectiveUseScope = useScope;
284       this.samParamCount = samMethod.getParameterList().getParametersCount();
285       this.booleanCompatible = FunctionalExpressionKey.isBooleanCompatible(samType);
286       this.isVoid = PsiType.VOID.equals(samType);
287     }
288
289     List<FunctionalExpressionKey> generateKeys() {
290       List<FunctionalExpressionKey> result = new ArrayList<>();
291       for (String lambdaType : new String[]{assertNotNull(samClass.getName()), ""}) {
292         for (int lambdaParamCount : new int[]{FunctionalExpressionKey.UNKNOWN_PARAM_COUNT, samParamCount}) {
293           result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.UNKNOWN, lambdaType));
294           if (isVoid) {
295             result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.VOID, lambdaType));
296           } else {
297             if (booleanCompatible) {
298               result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.BOOLEAN, lambdaType));
299             }
300             result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.NON_VOID, lambdaType));
301           }
302         }
303       }
304
305       return result;
306     }
307
308     @NotNull
309     private Set<VirtualFile> getMostLikelyFiles() {
310       Set<VirtualFile> files = ContainerUtil.newLinkedHashSet();
311       ReadAction.run(() -> {
312         if (!samClass.isValid()) return;
313
314         String className = samClass.getName();
315         Project project = samClass.getProject();
316         if (className == null) return;
317
318         Set<String> likelyNames = ContainerUtil.newLinkedHashSet(className);
319         StubIndex.getInstance().processElements(JavaMethodParameterTypesIndex.getInstance().getKey(), className,
320                                                 project, effectiveUseScope, PsiMethod.class, method -> {
321             ProgressManager.checkCanceled();
322             likelyNames.add(method.getName());
323             return true;
324           });
325
326         PsiSearchHelperImpl helper = (PsiSearchHelperImpl)PsiSearchHelper.SERVICE.getInstance(project);
327         Processor<VirtualFile> processor = Processors.cancelableCollectProcessor(files);
328         for (String word : likelyNames) {
329           helper.processFilesWithText(effectiveUseScope, UsageSearchContext.IN_CODE, true, word, processor);
330         }
331       });
332       return files;
333     }
334   }
335
336   private static boolean performSearchUsingCompilerIndices(@NotNull List<SamDescriptor> descriptors,
337                                                            @NotNull GlobalSearchScope searchScope,
338                                                            @NotNull Project project,
339                                                            @NotNull Processor<PsiFunctionalExpression> consumer) {
340     CompilerReferenceService compilerReferenceService = CompilerReferenceService.getInstance(project);
341     for (SamDescriptor descriptor : descriptors) {
342       if (!processFunctionalExpressions(performSearchUsingCompilerIndices(descriptor,
343                                                                           searchScope,
344                                                                           compilerReferenceService), descriptor, consumer)) {
345         return false;
346       }
347     }
348     return true;
349   }
350
351   private static CompilerDirectHierarchyInfo<PsiFunctionalExpression> performSearchUsingCompilerIndices(@NotNull SamDescriptor descriptor,
352                                                                                                         @NotNull GlobalSearchScope searchScope,
353                                                                                                         @NotNull CompilerReferenceService service) {
354     return service.getFunExpressions(descriptor.samClass, descriptor.effectiveUseScope, searchScope, JavaFileType.INSTANCE);
355   }
356
357
358   private static boolean processFunctionalExpressions(@Nullable CompilerDirectHierarchyInfo<PsiFunctionalExpression> funExprInfo,
359                                                       @NotNull SamDescriptor descriptor,
360                                                       @NotNull Processor<PsiFunctionalExpression> consumer) {
361     if (funExprInfo != null) {
362       Iterator<PsiFunctionalExpression> it = funExprInfo.getHierarchyChildren().iterator();
363       while (it.hasNext()) {
364         PsiFunctionalExpression next = it.next();
365         if (!consumer.process(next)) {
366           return false;
367         }
368       }
369       GlobalSearchScope dirtyScope = funExprInfo.getDirtyScope();
370       descriptor.effectiveUseScope = descriptor.effectiveUseScope.intersectWith(dirtyScope);
371     }
372     return true;
373   }
374
375 }