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