diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java index 3afaf8569..bc7aad8bc 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java @@ -99,6 +99,7 @@ import org.springframework.ide.vscode.commons.languageserver.reconcile.Reconcile import org.springframework.ide.vscode.commons.protocol.STS4LanguageClient; import org.springframework.ide.vscode.commons.protocol.spring.Bean; import org.springframework.ide.vscode.commons.protocol.spring.BeansParams; +import org.springframework.ide.vscode.commons.protocol.spring.MatchingBeansParams; import org.springframework.ide.vscode.commons.protocol.spring.SpringIndex; import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexLanguageServer; import org.springframework.ide.vscode.commons.util.Assert; @@ -927,13 +928,14 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, SpringInd @Override public CompletableFuture> beans(BeansParams params) { - Map allSpringIndex = appContext.getBeansOfType(SpringIndex.class, false, false); - Assert.isLegal(allSpringIndex.size() <= 1, "One at most SpringModel bean is expected"); - if (!allSpringIndex.isEmpty()) { - SpringIndex springIndex = allSpringIndex.values().iterator().next(); - return springIndex.beans(params); - } - return CompletableFuture.completedFuture(Collections.emptyList()); + SpringIndex springIndex = appContext.getBean(SpringIndex.class); + return springIndex.beans(params); + } + + @Override + public CompletableFuture> matchingBeans(MatchingBeansParams params) { + SpringIndex springIndex = appContext.getBean(SpringIndex.class); + return springIndex.matchingBeans(params); } } diff --git a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/MatchingBeansParams.java b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/MatchingBeansParams.java new file mode 100644 index 000000000..74a8380d4 --- /dev/null +++ b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/MatchingBeansParams.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2023 VMware, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VMware, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.protocol.spring; + +public class MatchingBeansParams extends BeansParams { + + private String beanType; + + public String getBeanTypeToMatch() { + return beanType; + } + + public void setBeanTypeToMatch(String beanType) { + this.beanType = beanType; + } + +} diff --git a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/SpringIndex.java b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/SpringIndex.java index 73100efb7..b58f6df8a 100644 --- a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/SpringIndex.java +++ b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/SpringIndex.java @@ -16,5 +16,6 @@ import java.util.concurrent.CompletableFuture; public interface SpringIndex { CompletableFuture> beans(BeansParams params); + CompletableFuture> matchingBeans(MatchingBeansParams params); } diff --git a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/SpringIndexLanguageServer.java b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/SpringIndexLanguageServer.java index ad59a018c..7dd487ad7 100644 --- a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/SpringIndexLanguageServer.java +++ b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/SpringIndexLanguageServer.java @@ -22,5 +22,10 @@ public interface SpringIndexLanguageServer extends LanguageServer { default CompletableFuture> beans(BeansParams params) { throw new UnsupportedOperationException(); } + + @JsonRequest("spring/index/beans/matches") + default CompletableFuture> matchingBeans(MatchingBeansParams params) { + throw new UnsupportedOperationException(); + } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java index 892303d5b..18f8f8671 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java @@ -71,6 +71,7 @@ import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocu import org.springframework.ide.vscode.commons.languageserver.util.SimpleWorkspaceService; import org.springframework.ide.vscode.commons.protocol.spring.Bean; import org.springframework.ide.vscode.commons.protocol.spring.BeansParams; +import org.springframework.ide.vscode.commons.protocol.spring.MatchingBeansParams; import org.springframework.ide.vscode.commons.protocol.spring.SpringIndex; import org.springframework.ide.vscode.commons.util.Futures; import org.springframework.ide.vscode.commons.util.StringUtil; @@ -720,7 +721,21 @@ public class SpringSymbolIndex implements InitializingBean, SpringIndex { else { return CompletableFuture.completedFuture(null); } - + } + + @Override + public CompletableFuture> matchingBeans(MatchingBeansParams params) { + String projectName = params.getProjectName(); + String matchType = params.getBeanTypeToMatch(); + + CompletableFuture latestTask = this.latestScheduledTaskByProject.get(projectName); + + if (latestTask != null) { + return latestTask.thenApply((e) -> Arrays.asList(springIndex.getMatchingBeans(projectName, matchType))); + } + else { + return CompletableFuture.completedFuture(null); + } } /** @@ -989,4 +1004,5 @@ public class SpringSymbolIndex implements InitializingBean, SpringIndex { public void onUpdate(Consumer listener) { listeners.add(listener); } + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringMetamodelIndex.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringMetamodelIndex.java index 8e1e57bfe..689266518 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringMetamodelIndex.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringMetamodelIndex.java @@ -97,4 +97,15 @@ public class SpringMetamodelIndex { } } + public Bean[] getMatchingBeans(String projectName, String matchType) { + Bean[] allBeans = this.beansPerProject.get(projectName); + + if (allBeans != null) { + return Arrays.stream(allBeans).filter(bean -> bean.isTypeCompatibleWith(matchType)).collect(Collectors.toList()).toArray(new Bean[0]); + } + else { + return null; + } + } + } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/metamodel/test/SpringIndexViaLSPMethodTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/metamodel/test/SpringIndexViaLSPMethodTest.java index 383e659aa..12498dde5 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/metamodel/test/SpringIndexViaLSPMethodTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/metamodel/test/SpringIndexViaLSPMethodTest.java @@ -31,6 +31,7 @@ import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.protocol.spring.Bean; import org.springframework.ide.vscode.commons.protocol.spring.BeansParams; +import org.springframework.ide.vscode.commons.protocol.spring.MatchingBeansParams; import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; import org.springframework.ide.vscode.project.harness.ProjectsHarness; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -86,4 +87,35 @@ public class SpringIndexViaLSPMethodTest { assertEquals(11, beans.size()); } + @Test + void testMatchingBeansForObject() throws Exception { + MatchingBeansParams params = new MatchingBeansParams(); + params.setProjectName("test-spring-indexing"); + params.setBeanTypeToMatch("java.lang.Object"); + + CompletableFuture> result = indexer.matchingBeans(params); + + List beans = result.get(5, TimeUnit.SECONDS); + + assertNotNull(beans); + assertEquals(10, beans.size()); + } + + @Test + void testMatchingBeansForSpecificSupertype() throws Exception { + MatchingBeansParams params = new MatchingBeansParams(); + params.setProjectName("test-spring-indexing"); + params.setBeanTypeToMatch("org.test.springdata.CustomerRepository"); + + CompletableFuture> result = indexer.matchingBeans(params); + + List beans = result.get(5, TimeUnit.SECONDS); + + assertNotNull(beans); + assertEquals(1, beans.size()); + + assertEquals("customerRepository", beans.get(0).getName()); + assertEquals("org.test.springdata.CustomerRepository", beans.get(0).getType()); + } + } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/metamodel/test/SpringMetamodelIndexTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/metamodel/test/SpringMetamodelIndexTest.java index 69360464c..0df5046a5 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/metamodel/test/SpringMetamodelIndexTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/metamodel/test/SpringMetamodelIndexTest.java @@ -266,5 +266,78 @@ public class SpringMetamodelIndexTest { Bean bean1 = new Bean("beanName1", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes); assertSame(DefaultValues.EMPTY_INJECTION_POINTS, bean1.getInjectionPoints()); } + + @Test + void testFindNoMatchingBeansWithEmptySupertypes() { + SpringMetamodelIndex index = new SpringMetamodelIndex(); + Bean bean1 = new Bean("beanName1", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes); + Bean bean2 = new Bean("beanName2", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes); + + index.updateBeans("someProject", new Bean[] {bean1, bean2}); + + Bean[] matchingBeans = index.getMatchingBeans("someProject", ""); + assertEquals(0, matchingBeans.length); + + matchingBeans = index.getMatchingBeans("someProject", "sometype"); + assertEquals(0, matchingBeans.length); + } + + @Test + void testFindMatchingBeansWithOneProject() { + SpringMetamodelIndex index = new SpringMetamodelIndex(); + Bean bean1 = new Bean("beanName1", "beanType1", locationForDoc1, emptyInjectionPoints, new String[] {"supertype1", "supertype2"}); + Bean bean2 = new Bean("beanName2", "beanType2", locationForDoc1, emptyInjectionPoints, new String[] {"supertype3", "supertype4", "supertype5"}); + + index.updateBeans("someProject", new Bean[] {bean1, bean2}); + + Bean[] matchingBeans = index.getMatchingBeans("someProject", "supertype2"); + assertEquals(1, matchingBeans.length); + assertSame(bean1, matchingBeans[0]); + + matchingBeans = index.getMatchingBeans("someProject", "beanType1"); + assertEquals(1, matchingBeans.length); + assertSame(bean1, matchingBeans[0]); + + matchingBeans = index.getMatchingBeans("someProject", "supertype5"); + assertEquals(1, matchingBeans.length); + assertSame(bean2, matchingBeans[0]); + + matchingBeans = index.getMatchingBeans("someProject", "sometype"); + assertEquals(0, matchingBeans.length); + + matchingBeans = index.getMatchingBeans("otherProject", "supertype1"); + assertNull(matchingBeans); + } + + @Test + void testFindMatchingBeansWithMultipleProjects() { + SpringMetamodelIndex index = new SpringMetamodelIndex(); + Bean bean1 = new Bean("beanName1", "beanType1", locationForDoc1, emptyInjectionPoints, new String[] {"supertype1", "supertype2"}); + Bean bean2 = new Bean("beanName2", "beanType2", locationForDoc1, emptyInjectionPoints, new String[] {"supertype3", "supertype4, supertype5"}); + + Bean bean3 = new Bean("beanName3", "beanType1", locationForDoc1, emptyInjectionPoints, new String[] {"supertype1", "supertype2"}); + Bean bean4 = new Bean("beanName4", "beanType2", locationForDoc1, emptyInjectionPoints, new String[] {"supertype3", "supertype4, supertype5"}); + + index.updateBeans("projectA", new Bean[] {bean1, bean2}); + index.updateBeans("projectB", new Bean[] {bean3, bean4}); + + Bean[] matchingBeans = index.getMatchingBeans("projectA", "supertype2"); + assertEquals(1, matchingBeans.length); + assertSame(bean1, matchingBeans[0]); + + matchingBeans = index.getMatchingBeans("projectA", "beanType2"); + assertEquals(1, matchingBeans.length); + assertSame(bean2, matchingBeans[0]); + + matchingBeans = index.getMatchingBeans("projectB", "supertype2"); + assertEquals(1, matchingBeans.length); + assertSame(bean3, matchingBeans[0]); + + matchingBeans = index.getMatchingBeans("projectB", "beanType2"); + assertEquals(1, matchingBeans.length); + assertSame(bean4, matchingBeans[0]); + + matchingBeans = index.getMatchingBeans("otherProject", "supertype1"); + } }