2 * Copyright 2000-2016 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.psi.impl.search;
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;
56 import java.util.concurrent.atomic.AtomicInteger;
58 import static com.intellij.util.ObjectUtils.assertNotNull;
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;
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();
70 PsiManager manager = ReadAction.compute(() -> p.getElementToSearch().getManager());
71 manager.startBatchFilesProcessingMode();
73 processOffsets(descriptors, (file, offsets) -> {
74 fileCount.incrementAndGet();
75 exprCount.addAndGet(offsets.size());
76 return processFile(consumer, descriptors, file, offsets);
80 manager.finishBatchFilesProcessingMode();
82 if (exprCount.get() > 0) {
83 LOG.debug("Loaded " + exprCount.get() + " fun-expressions in " + fileCount.get() + " files");
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));
95 private static List<SamDescriptor> calcDescriptors(@NotNull SearchParameters queryParameters) {
96 List<SamDescriptor> descriptors = new ArrayList<>();
98 ReadAction.run(() -> {
99 PsiClass aClass = queryParameters.getElementToSearch();
100 if (!aClass.isValid() || !aClass.isInterface()) {
103 Project project = aClass.getProject();
104 if (InjectedLanguageManager.getInstance(project).isInjectedFragment(aClass.getContainingFile()) || !hasJava8Modules(project)) {
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;
114 SearchScope scope = samClass.getUseScope().intersectWith(queryParameters.getEffectiveSearchScope());
116 final GlobalSearchScope excludedScope =
117 CompilerReferenceService.getInstance(project).getScopeWithoutCodeReferences(samClass, JavaFunctionalExpressionCompilerSearchAdapter.INSTANCE);
118 if (excludedScope != null) {
119 scope = scope.intersectWith(GlobalSearchScope.notScope(excludedScope));
122 descriptors.add(new SamDescriptor(samClass, saMethod, samType, GlobalSearchScopeUtil.toGlobalSearchScope(scope, project)));
130 private static Set<VirtualFile> getLikelyFiles(List<SamDescriptor> descriptors) {
131 return JBIterable.from(descriptors).flatMap(SamDescriptor::getMostLikelyFiles).toSet();
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);
144 }, new JavaSourceFilterScope(descriptor.useScope));
148 LOG.debug("Found " + result.values().size() + " fun-expressions in " + result.keySet().size() + " files");
152 private static void processOffsets(List<SamDescriptor> descriptors, PairProcessor<VirtualFile, List<Integer>> processor) {
153 if (descriptors.isEmpty()) return;
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))) {
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);
177 private static List<FunExprOccurrence> filterInapplicable(List<PsiClass> samClasses,
179 Collection<FunExprOccurrence> occurrences) {
180 return ReadAction.compute(() -> ContainerUtil.filter(occurrences, it -> it.canHaveType(samClasses, vFile)));
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);
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);
200 if (hasType(descriptors, expression) && !consumer.process(expression)) {
209 private static boolean hasType(List<SamDescriptor> descriptors, PsiFunctionalExpression expression) {
210 if (!canHaveType(expression, ContainerUtil.map(descriptors, d -> d.samClass))) return false;
212 PsiClass actualClass = PsiUtil.resolveClassInType(expression.getFunctionalInterfaceType());
213 return ContainerUtil.exists(descriptors, d -> InheritanceUtil.isInheritorOrSelf(actualClass, d.samClass, true));
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));
235 private static boolean hasJava8Modules(Project project) {
236 final boolean projectLevelIsHigh = PsiUtil.getLanguageLevel(project).isAtLeast(LanguageLevel.JDK_1_8);
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)) {
250 private static Set<PsiClass> processSubInterfaces(PsiClass base) {
251 Set<PsiClass> result = new HashSet<>();
253 void visit(PsiClass c) {
254 if (!result.add(c)) return;
256 DirectClassInheritorsSearch.search(c).forEach(candidate -> {
257 if (candidate.isInterface()) {
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;
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);
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));
288 result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.VOID, lambdaType));
290 if (booleanCompatible) {
291 result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.BOOLEAN, lambdaType));
293 result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.NON_VOID, lambdaType));
302 private Set<VirtualFile> getMostLikelyFiles() {
303 Set<VirtualFile> files = ContainerUtil.newLinkedHashSet();
304 ReadAction.run(() -> {
305 if (!samClass.isValid()) return;
307 String className = samClass.getName();
308 Project project = samClass.getProject();
309 if (className == null) return;
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());
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);