CompilerReferenceService rename getScopeWithoutReferences -> getScopeWithoutCodeRefer...
[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.JavaFunctionalExpressionCompilerSearchAdapter;
20 import com.intellij.lang.injection.InjectedLanguageManager;
21 import com.intellij.openapi.application.QueryExecutorBase;
22 import com.intellij.openapi.application.ReadAction;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.module.Module;
25 import com.intellij.openapi.module.ModuleManager;
26 import com.intellij.openapi.progress.ProgressManager;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.roots.LanguageLevelModuleExtension;
29 import com.intellij.openapi.roots.ModuleRootManager;
30 import com.intellij.openapi.vfs.VirtualFile;
31 import com.intellij.pom.java.LanguageLevel;
32 import com.intellij.psi.*;
33 import com.intellij.psi.impl.java.FunExprOccurrence;
34 import com.intellij.psi.impl.java.JavaFunctionalExpressionIndex;
35 import com.intellij.psi.impl.java.stubs.FunctionalExpressionKey;
36 import com.intellij.psi.impl.java.stubs.index.JavaMethodParameterTypesIndex;
37 import com.intellij.psi.search.*;
38 import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
39 import com.intellij.psi.search.searches.FunctionalExpressionSearch.SearchParameters;
40 import com.intellij.psi.stubs.StubIndex;
41 import com.intellij.psi.util.InheritanceUtil;
42 import com.intellij.psi.util.PsiTreeUtil;
43 import com.intellij.psi.util.PsiUtil;
44 import com.intellij.util.PairProcessor;
45 import com.intellij.util.Processor;
46 import com.intellij.util.Processors;
47 import com.intellij.util.containers.ContainerUtil;
48 import com.intellij.util.containers.HashSet;
49 import com.intellij.util.containers.JBIterable;
50 import com.intellij.util.containers.MultiMap;
51 import com.intellij.util.indexing.FileBasedIndex;
52 import org.jetbrains.annotations.NotNull;
53 import org.jetbrains.annotations.TestOnly;
54
55 import java.util.*;
56 import java.util.concurrent.atomic.AtomicInteger;
57
58 import static com.intellij.util.ObjectUtils.assertNotNull;
59
60 public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunctionalExpression, SearchParameters> {
61   private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.search.JavaFunctionalExpressionSearcher");
62   public static final int SMART_SEARCH_THRESHOLD = 5;
63
64   @Override
65   public void processQuery(@NotNull SearchParameters p, @NotNull Processor<PsiFunctionalExpression> consumer) {
66     List<SamDescriptor> descriptors = calcDescriptors(p);
67     AtomicInteger exprCount = new AtomicInteger();
68     AtomicInteger fileCount = new AtomicInteger();
69
70     PsiManager manager = ReadAction.compute(() -> p.getElementToSearch().getManager());
71     manager.startBatchFilesProcessingMode();
72     try {
73       processOffsets(descriptors, (file, offsets) -> {
74         fileCount.incrementAndGet();
75         exprCount.addAndGet(offsets.size());
76         return processFile(consumer, descriptors, file, offsets);
77       });
78     }
79     finally {
80       manager.finishBatchFilesProcessingMode();
81     }
82     if (exprCount.get() > 0) {
83       LOG.debug("Loaded " + exprCount.get() + " fun-expressions in " + fileCount.get() + " files");
84     }
85   }
86
87   @TestOnly
88   public static Set<VirtualFile> getFilesToSearchInPsi(PsiClass samClass) {
89     Set<VirtualFile> result = new HashSet<>();
90     processOffsets(calcDescriptors(new SearchParameters(samClass, samClass.getUseScope())), (file, offsets) -> result.add(file));
91     return result;
92   }
93
94   @NotNull
95   private static List<SamDescriptor> calcDescriptors(@NotNull SearchParameters queryParameters) {
96     List<SamDescriptor> descriptors = new ArrayList<>();
97
98     ReadAction.run(() -> {
99       PsiClass aClass = queryParameters.getElementToSearch();
100       if (!aClass.isValid() || !aClass.isInterface()) {
101         return;
102       }
103       Project project = aClass.getProject();
104       if (InjectedLanguageManager.getInstance(project).isInjectedFragment(aClass.getContainingFile()) || !hasJava8Modules(project)) {
105         return;
106       }
107
108       for (PsiClass samClass : processSubInterfaces(aClass)) {
109         if (LambdaUtil.isFunctionalClass(samClass)) {
110           PsiMethod saMethod = assertNotNull(LambdaUtil.getFunctionalInterfaceMethod(samClass));
111           PsiType samType = saMethod.getReturnType();
112           if (samType == null) continue;
113
114           SearchScope scope = samClass.getUseScope().intersectWith(queryParameters.getEffectiveSearchScope());
115
116           final GlobalSearchScope excludedScope =
117             CompilerReferenceService.getInstance(project).getScopeWithoutCodeReferences(samClass, JavaFunctionalExpressionCompilerSearchAdapter.INSTANCE);
118           if (excludedScope != null) {
119             scope = scope.intersectWith(GlobalSearchScope.notScope(excludedScope));
120           }
121
122           descriptors.add(new SamDescriptor(samClass, saMethod, samType, GlobalSearchScopeUtil.toGlobalSearchScope(scope, project)));
123         }
124       }
125     });
126     return descriptors;
127   }
128
129   @NotNull
130   private static Set<VirtualFile> getLikelyFiles(List<SamDescriptor> descriptors) {
131     return JBIterable.from(descriptors).flatMap(SamDescriptor::getMostLikelyFiles).toSet();
132   }
133
134   @NotNull
135   private static MultiMap<VirtualFile, FunExprOccurrence> getAllOccurrences(List<SamDescriptor> descriptors) {
136     MultiMap<VirtualFile, FunExprOccurrence> result = MultiMap.createLinkedSet();
137     for (SamDescriptor descriptor : descriptors) {
138       ReadAction.run(() -> {
139         for (FunctionalExpressionKey key : descriptor.generateKeys()) {
140           FileBasedIndex.getInstance().processValues(JavaFunctionalExpressionIndex.INDEX_ID, key, null, (file, infos) -> {
141             ProgressManager.checkCanceled();
142             result.putValues(file, infos);
143             return true;
144           }, new JavaSourceFilterScope(descriptor.useScope));
145         }
146       });
147     }
148     LOG.debug("Found " + result.values().size() + " fun-expressions in " + result.keySet().size() + " files");
149     return result;
150   }
151
152   private static void processOffsets(List<SamDescriptor> descriptors, PairProcessor<VirtualFile, List<Integer>> processor) {
153     if (descriptors.isEmpty()) return;
154
155     List<PsiClass> samClasses = ContainerUtil.map(descriptors, d -> d.samClass);
156     MultiMap<VirtualFile, FunExprOccurrence> allCandidates = getAllOccurrences(descriptors);
157     for (VirtualFile vFile : putLikelyFilesFirst(descriptors, allCandidates.keySet())) {
158       List<FunExprOccurrence> toLoad = filterInapplicable(samClasses, vFile, allCandidates.get(vFile));
159       if (!toLoad.isEmpty()) {
160         LOG.trace("To load " + vFile.getPath() + " with values: " + toLoad);
161         if (!processor.process(vFile, ContainerUtil.map(toLoad, it -> it.funExprOffset))) {
162           return;
163         }
164       }
165     }
166   }
167
168   @NotNull
169   private static Set<VirtualFile> putLikelyFilesFirst(List<SamDescriptor> descriptors, Set<VirtualFile> allFiles) {
170     Set<VirtualFile> orderedFiles = new LinkedHashSet<>(allFiles);
171     orderedFiles.retainAll(getLikelyFiles(descriptors));
172     orderedFiles.addAll(allFiles);
173     return orderedFiles;
174   }
175
176   @NotNull
177   private static List<FunExprOccurrence> filterInapplicable(List<PsiClass> samClasses,
178                                                             VirtualFile vFile,
179                                                             Collection<FunExprOccurrence> occurrences) {
180     return ReadAction.compute(() -> ContainerUtil.filter(occurrences, it -> it.canHaveType(samClasses, vFile)));
181   }
182
183   private static boolean processFile(@NotNull Processor<PsiFunctionalExpression> consumer,
184                                      List<SamDescriptor> descriptors,
185                                      VirtualFile vFile, Collection<Integer> offsets) {
186     return ReadAction.compute(() -> {
187       PsiFile file = descriptors.get(0).samClass.getManager().findFile(vFile);
188       if (!(file instanceof PsiJavaFile)) {
189         LOG.error("Non-java file " + file + "; " + vFile);
190         return true;
191       }
192
193       for (Integer offset : offsets) {
194         PsiFunctionalExpression expression = PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiFunctionalExpression.class, false);
195         if (expression == null || expression.getTextRange().getStartOffset() != offset) {
196           LOG.error("Fun expression not found in " + file + " at " + offset);
197           continue;
198         }
199
200         if (hasType(descriptors, expression) && !consumer.process(expression)) {
201           return false;
202         }
203       }
204
205       return true;
206     });
207   }
208
209   private static boolean hasType(List<SamDescriptor> descriptors, PsiFunctionalExpression expression) {
210     if (!canHaveType(expression, ContainerUtil.map(descriptors, d -> d.samClass))) return false;
211
212     PsiClass actualClass = PsiUtil.resolveClassInType(expression.getFunctionalInterfaceType());
213     return ContainerUtil.exists(descriptors, d -> InheritanceUtil.isInheritorOrSelf(actualClass, d.samClass, true));
214   }
215
216   private static boolean canHaveType(PsiFunctionalExpression expression, List<PsiClass> samClasses) {
217     PsiElement parent = expression.getParent();
218     if (parent instanceof PsiExpressionList && parent.getParent() instanceof PsiMethodCallExpression) {
219       PsiExpression[] args = ((PsiExpressionList)parent).getExpressions();
220       int argIndex = Arrays.asList(args).indexOf(expression);
221       PsiReferenceExpression methodExpression = ((PsiMethodCallExpression)parent.getParent()).getMethodExpression();
222       PsiExpression qualifier = methodExpression.getQualifierExpression();
223       String methodName = methodExpression.getReferenceName();
224       if (qualifier != null && methodName != null && argIndex >= 0) {
225         Set<PsiClass> approximateTypes = ApproximateResolver.getPossibleTypes(qualifier, 10);
226         List<PsiMethod> methods = approximateTypes == null ? null :
227                                   ApproximateResolver.getPossibleMethods(approximateTypes, methodName, args.length);
228         return methods == null ||
229                ContainerUtil.exists(methods, m -> FunExprOccurrence.hasCompatibleParameter(m, argIndex, samClasses));
230       }
231     }
232     return true;
233   }
234
235   private static boolean hasJava8Modules(Project project) {
236     final boolean projectLevelIsHigh = PsiUtil.getLanguageLevel(project).isAtLeast(LanguageLevel.JDK_1_8);
237
238     for (Module module : ModuleManager.getInstance(project).getModules()) {
239       final LanguageLevelModuleExtension extension = ModuleRootManager.getInstance(module).getModuleExtension(LanguageLevelModuleExtension.class);
240       if (extension != null) {
241         final LanguageLevel level = extension.getLanguageLevel();
242         if (level == null && projectLevelIsHigh || level != null && level.isAtLeast(LanguageLevel.JDK_1_8)) {
243           return true;
244         }
245       }
246     }
247     return false;
248   }
249
250   private static Set<PsiClass> processSubInterfaces(PsiClass base) {
251     Set<PsiClass> result = new HashSet<>();
252     new Object() {
253       void visit(PsiClass c) {
254         if (!result.add(c)) return;
255
256         DirectClassInheritorsSearch.search(c).forEach(candidate -> {
257           if (candidate.isInterface()) {
258             visit(candidate);
259           }
260           return true;
261         });
262       }
263     }.visit(base);
264     return result;
265   }
266
267   private static class SamDescriptor {
268     final PsiClass samClass;
269     final GlobalSearchScope useScope;
270     final int samParamCount;
271     final boolean booleanCompatible;
272     final boolean isVoid;
273
274     SamDescriptor(PsiClass samClass, PsiMethod samMethod, PsiType samType, GlobalSearchScope useScope) {
275       this.samClass = samClass;
276       this.useScope = useScope;
277       this.samParamCount = samMethod.getParameterList().getParametersCount();
278       this.booleanCompatible = FunctionalExpressionKey.isBooleanCompatible(samType);
279       this.isVoid = PsiType.VOID.equals(samType);
280     }
281
282     List<FunctionalExpressionKey> generateKeys() {
283       List<FunctionalExpressionKey> result = new ArrayList<>();
284       for (String lambdaType : new String[]{assertNotNull(samClass.getName()), ""}) {
285         for (int lambdaParamCount : new int[]{FunctionalExpressionKey.UNKNOWN_PARAM_COUNT, samParamCount}) {
286           result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.UNKNOWN, lambdaType));
287           if (isVoid) {
288             result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.VOID, lambdaType));
289           } else {
290             if (booleanCompatible) {
291               result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.BOOLEAN, lambdaType));
292             }
293             result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.NON_VOID, lambdaType));
294           }
295         }
296       }
297
298       return result;
299     }
300
301     @NotNull
302     private Set<VirtualFile> getMostLikelyFiles() {
303       Set<VirtualFile> files = ContainerUtil.newLinkedHashSet();
304       ReadAction.run(() -> {
305         if (!samClass.isValid()) return;
306
307         String className = samClass.getName();
308         Project project = samClass.getProject();
309         if (className == null) return;
310
311         Set<String> likelyNames = ContainerUtil.newLinkedHashSet(className);
312         StubIndex.getInstance().processElements(JavaMethodParameterTypesIndex.getInstance().getKey(), className,
313                                                 project, useScope, PsiMethod.class, method -> {
314             ProgressManager.checkCanceled();
315             likelyNames.add(method.getName());
316             return true;
317           });
318
319         PsiSearchHelperImpl helper = (PsiSearchHelperImpl)PsiSearchHelper.SERVICE.getInstance(project);
320         Processor<VirtualFile> processor = Processors.cancelableCollectProcessor(files);
321         for (String word : likelyNames) {
322           helper.processFilesWithText(useScope, UsageSearchContext.IN_CODE, true, word, processor);
323         }
324       });
325       return files;
326     }
327
328   }
329
330 }