From 22b9dc61a69bc0ffec89a019b511585dfb422cee Mon Sep 17 00:00:00 2001 From: peter Date: Sat, 23 Jul 2016 19:28:22 +0200 Subject: [PATCH] fin functional expressions of SAM interfaces related to the given one --- .../JavaFunctionalExpressionSearcher.java | 153 ++++++++++-------- .../findUsages/FindSubInterfaceLambdas.java | 26 +++ .../lambda/FindFunctionalInterfaceTest.java | 29 +++- 3 files changed, 133 insertions(+), 75 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/findUsages/FindSubInterfaceLambdas.java diff --git a/java/java-indexing-impl/src/com/intellij/psi/impl/search/JavaFunctionalExpressionSearcher.java b/java/java-indexing-impl/src/com/intellij/psi/impl/search/JavaFunctionalExpressionSearcher.java index 56d33c98c236..d714c78f59ac 100644 --- a/java/java-indexing-impl/src/com/intellij/psi/impl/search/JavaFunctionalExpressionSearcher.java +++ b/java/java-indexing-impl/src/com/intellij/psi/impl/search/JavaFunctionalExpressionSearcher.java @@ -35,6 +35,7 @@ import com.intellij.psi.impl.java.stubs.JavaMethodElementType; import com.intellij.psi.impl.java.stubs.index.JavaMethodParameterTypesIndex; import com.intellij.psi.impl.java.stubs.index.JavaStubIndexKeys; import com.intellij.psi.search.*; +import com.intellij.psi.search.searches.DirectClassInheritorsSearch; import com.intellij.psi.search.searches.FunctionalExpressionSearch; import com.intellij.psi.stubs.StubIndex; import com.intellij.psi.stubs.StubIndexKey; @@ -43,16 +44,19 @@ import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.PsiUtilCore; import com.intellij.psi.util.TypeConversionUtil; import com.intellij.util.Processor; -import com.intellij.util.Processors; import com.intellij.util.ThreeState; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashSet; import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.stream.Collectors; import java.util.stream.IntStream; +import static com.intellij.util.ObjectUtils.assertNotNull; + public class JavaFunctionalExpressionSearcher extends QueryExecutorBase { private static final Logger LOG = Logger.getInstance("#" + JavaFunctionalExpressionSearcher.class.getName()); /** @@ -65,56 +69,60 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase consumer) { - final GlobalSearchScope useScope; - final PsiClass aClass; - final Project project; - final int expectedFunExprParamsCount; - final boolean isVoid; + final Set highLevelModules; + final List funInterfaces; - AccessToken token = ReadAction.start(); - try { - aClass = queryParameters.getElementToSearch(); - if (!aClass.isValid() || !LambdaUtil.isFunctionalClass(aClass)) return; + try (AccessToken ignored = ReadAction.start()) { + PsiClass aClass = queryParameters.getElementToSearch(); + if (!aClass.isValid() || !aClass.isInterface()) return; - project = aClass.getProject(); - final Set highLevelModules = getJava8Modules(project); + highLevelModules = getJava8Modules(aClass.getProject()); if (highLevelModules.isEmpty()) return; - useScope = convertToGlobalScope(project, queryParameters.getEffectiveSearchScope()); - - final PsiMethod functionalInterfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(aClass); - LOG.assertTrue(functionalInterfaceMethod != null); - expectedFunExprParamsCount = functionalInterfaceMethod.getParameterList().getParameters().length; - isVoid = PsiType.VOID.equals(functionalInterfaceMethod.getReturnType()); - } finally { - token.finish(); + funInterfaces = ContainerUtil.filter(processSubInterfaces(aClass), LambdaUtil::isFunctionalClass); } - //collect all files with '::' and '->' in useScope - Set candidateFiles = getFilesWithFunctionalExpressionsScope(project, new JavaSourceFilterScope(useScope)); - if (candidateFiles.isEmpty()) { - return; - } + for (PsiClass funInterface : funInterfaces) { + final GlobalSearchScope useScope; + final int expectedFunExprParamsCount; + final boolean isVoid; + try (AccessToken ignored = ReadAction.start()) { + if (!funInterface.isValid()) continue; - MultiMap queries = - collectQueryKeys(useScope, aClass, project, expectedFunExprParamsCount, isVoid, candidateFiles); + useScope = convertToGlobalScope(funInterface.getProject(), funInterface.getUseScope().intersectWith(queryParameters.getEffectiveSearchScope())); - for (PsiFunctionalExpression expression : getCandidates(useScope, project, queries)) { - if (!ReadAction.compute(() -> { - if (expression.isValid() && - InheritanceUtil.isInheritorOrSelf(PsiUtil.resolveClassInType(expression.getFunctionalInterfaceType()), aClass, true)) { - if (!consumer.process(expression)) { - return false; - } + final PsiMethod functionalInterfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(funInterface); + LOG.assertTrue(functionalInterfaceMethod != null); + expectedFunExprParamsCount = functionalInterfaceMethod.getParameterList().getParameters().length; + isVoid = PsiType.VOID.equals(functionalInterfaceMethod.getReturnType()); + } + + MultiMap queries = + collectQueryKeys(useScope, funInterface, expectedFunExprParamsCount, isVoid, highLevelModules); + + for (PsiFunctionalExpression expression : getCandidates(useScope, funInterface.getProject(), queries)) { + if (!processExpression(consumer, funInterface, expression)) { + return; } - - return true; - })) { - return; } } } + private static Boolean processExpression(@NotNull Processor consumer, + PsiClass aClass, + PsiFunctionalExpression expression) { + return ReadAction.compute(() -> { + if (expression.isValid() && + InheritanceUtil.isInheritorOrSelf(PsiUtil.resolveClassInType(expression.getFunctionalInterfaceType()), aClass, true)) { + if (!consumer.process(expression)) { + return false; + } + } + + return true; + }); + } + @NotNull private static Collection getCandidates(GlobalSearchScope useScope, Project project, @@ -144,12 +152,11 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase collectQueryKeys(GlobalSearchScope useScope, PsiClass aClass, - Project project, int samParamCount, boolean samVoid, - Set candidateFiles) { + Set candidateModules) { //collect all methods with parameter of functional interface or free type parameter type - Collection methodCandidates = getCandidateMethodsWithSuitableParams(aClass, project, useScope, candidateFiles, samParamCount, samVoid); + Collection methodCandidates = getCandidateMethodsWithSuitableParams(aClass, useScope, candidateModules, samParamCount, samVoid); MultiMap queries = MultiMap.createSet(); for (FunctionalExpressionKey key : generateKeys(samParamCount, samVoid, "", -1, -1)) { @@ -161,7 +168,7 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase { if (!psiMethod.isValid()) return; - final GlobalSearchScope methodUseScope = convertToGlobalScope(project, psiMethod.getUseScope()); + final GlobalSearchScope methodUseScope = convertToGlobalScope(psiMethod.getProject(), psiMethod.getUseScope()); for (FunctionalExpressionKey key : getQueryKeys(aClass, samParamCount, samVoid, psiMethod)) { queries.putValue(key, methodUseScope); } @@ -241,18 +248,18 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase getCandidateMethodsWithSuitableParams(final PsiClass aClass, - final Project project, + private static Collection getCandidateMethodsWithSuitableParams(final PsiClass samClass, final GlobalSearchScope useScope, - final Set candidateFiles, + final Set candidateModules, int expectedFunExprParamsCount, boolean isVoid) { return ApplicationManager.getApplication().runReadAction(new Computable>() { @Override public Collection compute() { - if (!aClass.isValid()) return Collections.emptyList(); + if (!samClass.isValid()) return Collections.emptyList(); - GlobalSearchScope visibleFromCandidates = combineResolveScopes(project, candidateFiles); + GlobalSearchScope visibleFromCandidates = combineResolveScopes(candidateModules, samClass); + if (visibleFromCandidates == null) return Collections.emptyList(); Set usedMethodNames = new HashSet<>(); StubIndex.getInstance().processAllKeys(JavaStubIndexKeys.FUNCTIONAL_EXPRESSIONS, key -> { @@ -273,7 +280,8 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase key = JavaMethodParameterTypesIndex.getInstance().getKey(); StubIndex index = StubIndex.getInstance(); - index.processElements(key, aClass.getName(), project, useScope.intersectWith(visibleFromCandidates), PsiMethod.class, methodProcessor); + Project project = samClass.getProject(); + index.processElements(key, assertNotNull(samClass.getName()), project, useScope.intersectWith(visibleFromCandidates), PsiMethod.class, methodProcessor); index.processElements(key, JavaMethodElementType.TYPE_PARAMETER_PSEUDO_NAME, project, visibleFromCandidates, PsiMethod.class, methodProcessor); LOG.info("#methods: " + methods.size()); return methods; @@ -281,24 +289,13 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase candidateFiles) { - final PsiManager psiManager = PsiManager.getInstance(project); - Set resolveScopes = ContainerUtil.newLinkedHashSet(ContainerUtil.mapNotNull(candidateFiles, file -> { - PsiFile psiFile = file.isValid() ? psiManager.findFile(file) : null; - return psiFile == null ? null : psiFile.getResolveScope(); - })); - return GlobalSearchScope.union(resolveScopes.toArray(new GlobalSearchScope[resolveScopes.size()])); - } - - @NotNull - private static Set getFilesWithFunctionalExpressionsScope(Project project, GlobalSearchScope useScope) { - final Set files = ContainerUtil.newLinkedHashSet(); - final PsiSearchHelperImpl helper = (PsiSearchHelperImpl)PsiSearchHelper.SERVICE.getInstance(project); - Processor processor = Processors.cancelableCollectProcessor(files); - helper.processFilesWithText(useScope, UsageSearchContext.IN_CODE, true, "::", processor); - helper.processFilesWithText(useScope, UsageSearchContext.IN_CODE, true, "->", processor); - return files; + @Nullable + private static GlobalSearchScope combineResolveScopes(Set candidateModules, PsiClass samClass) { + List scopes = candidateModules.stream() + .map(GlobalSearchScope::moduleWithDependenciesAndLibrariesScope) + .filter(s -> PsiSearchScopeUtil.isInScope(s, samClass)) + .collect(Collectors.toList()); + return scopes.isEmpty() ? null : GlobalSearchScope.union(scopes.toArray(new GlobalSearchScope[scopes.size()])); } @NotNull @@ -330,4 +327,26 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase processSubInterfaces(PsiClass base) { + Set result = new HashSet<>(); + new Object() { + void visit(PsiClass c) { + if (!result.add(c)) return; + + DirectClassInheritorsSearch.search(c).forEach(candidate -> { + if (candidate.isInterface() && isFunctionalCompatible(candidate)) { + visit(candidate); + } + return true; + }); + } + }.visit(base); + return result; + } + + private static boolean isFunctionalCompatible(PsiClass candidate) { + return LambdaUtil.isFunctionalClass(candidate) || + Arrays.stream(candidate.getAllMethods()).filter(m -> !m.hasModifierProperty(PsiModifier.DEFAULT)).count() == 0; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/findUsages/FindSubInterfaceLambdas.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/findUsages/FindSubInterfaceLambdas.java new file mode 100644 index 000000000000..67fbed79c127 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/findUsages/FindSubInterfaceLambdas.java @@ -0,0 +1,26 @@ +interface DumbAware {} +interface MyRunnable { + String foo(); +} +interface DumbAwareRunnable extends MyRunnable, DumbAware {} +interface DumbAwareRunnable2 extends DumbAwareRunnable {} + +interface DumbAwareFunction extends DumbAware { + Object f(Object o); +} + +class Foo { + void foo(T r, Class c) {} + void bar(DumbAwareRunnable r) {} + void bar2(DumbAwareRunnable2 r) {} + + { + DumbAwareRunnable var1 = () -> "var1"; + DumbAwareRunnable2 var2 = () -> "var2"; + DumbAwareFunction var3 = a -> "var3"; + + foo(() -> "c1", DumbAwareRunnable2.class); + bar(() -> "c2"); + bar2(() -> "c3"); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/lambda/FindFunctionalInterfaceTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/lambda/FindFunctionalInterfaceTest.java index f0447ffb6b0c..790fb182348d 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/lambda/FindFunctionalInterfaceTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/lambda/FindFunctionalInterfaceTest.java @@ -61,7 +61,7 @@ public class FindFunctionalInterfaceTest extends LightCodeInsightFixtureTestCase } private void doTestOneExpression() { - myFixture.configureByFile(getTestName(false) + ".java"); + configure(); final PsiClass psiClass = findClassAtCaret(); final Collection expressions = FunctionalExpressionSearch.search(psiClass).findAll(); int size = expressions.size(); @@ -81,7 +81,7 @@ public class FindFunctionalInterfaceTest extends LightCodeInsightFixtureTestCase } public void testFieldFromAnonymousClassScope() throws Exception { - myFixture.configureByFile(getTestName(false) + ".java"); + configure(); final PsiElement elementAtCaret = myFixture.getElementAtCaret(); assertNotNull(elementAtCaret); final PsiField field = PsiTreeUtil.getParentOfType(elementAtCaret, PsiField.class, false); @@ -94,10 +94,23 @@ public class FindFunctionalInterfaceTest extends LightCodeInsightFixtureTestCase } public void testMethodWithClassTypeParameter() { - myFixture.configureByFile(getTestName(false) + ".java"); + configure(); + assertSize(1, FunctionalExpressionSearch.search(findClass("I")).findAll()); + } - PsiClass runnable = JavaPsiFacade.getInstance(getProject()).findClass("I", GlobalSearchScope.allScope(getProject())); - assertSize(1, FunctionalExpressionSearch.search(runnable).findAll()); + public void testFindSubInterfaceLambdas() { + configure(); + assertSize(5, FunctionalExpressionSearch.search(findClass("DumbAwareRunnable")).findAll()); + assertSize(3, FunctionalExpressionSearch.search(findClass("DumbAwareRunnable2")).findAll()); + assertSize(6, FunctionalExpressionSearch.search(findClass("DumbAware")).findAll()); + } + + private PsiClass findClass(String i) { + return JavaPsiFacade.getInstance(getProject()).findClass(i, GlobalSearchScope.allScope(getProject())); + } + + private void configure() { + myFixture.configureByFile(getTestName(false) + ".java"); } public void testClassFromJdk() { @@ -109,16 +122,16 @@ public class FindFunctionalInterfaceTest extends LightCodeInsightFixtureTestCase } public void doTestIndexSearch(String expected) { - myFixture.configureByFile(getTestName(false) + ".java"); + configure(); - PsiClass predicate = JavaPsiFacade.getInstance(getProject()).findClass(Predicate.class.getName(), GlobalSearchScope.allScope(getProject())); + PsiClass predicate = findClass(Predicate.class.getName()); assert predicate != null; final PsiFunctionalExpression next = assertOneElement(FunctionalExpressionSearch.search(predicate).findAll()); assertEquals(expected, next.getText()); } public void testConstructorReferences() { - myFixture.configureByFile(getTestName(false) + ".java"); + configure(); myFixture.addClass("class Bar extends Foo {\n" + " public Bar() { super(() -> 1); }\n" +