From a4734b8029284bdc36f190b3686491f5b7adbef7 Mon Sep 17 00:00:00 2001 From: BoykoAlex Date: Tue, 21 Aug 2018 16:58:50 -0400 Subject: [PATCH] PT #159866173: Add autowired beans for @Bean hover and codelens --- .../autowired/AutowiredHoverProvider.java | 10 +- .../AbstractInjectedIntoHoverProvider.java | 38 +-- .../BeanInjectedIntoHoverProvider.java | 17 ++ .../ComponentInjectionsHoverProvider.java | 19 +- .../BeanInjectedIntoHoverProviderTest.java | 232 ++++++++++++++++++ 5 files changed, 289 insertions(+), 27 deletions(-) diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java index 1fbdcb52a..5cf86b198 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java @@ -172,7 +172,10 @@ public class AutowiredHoverProvider implements HoverProvider { public static List getRelevantAutowiredBeans(IJavaProject project, ASTNode declarationNode, SpringBootApp app, LiveBean definedBean) { List relevantBeans = LiveHoverUtils.findRelevantBeans(app, definedBean); + return getRelevantAutowiredBeans(project, declarationNode, app, relevantBeans); + } + public static List getRelevantAutowiredBeans(IJavaProject project, ASTNode declarationNode, SpringBootApp app, List relevantBeans) { if (!relevantBeans.isEmpty()) { List allDependencyBeans = LiveHoverUtils.findAllDependencyBeans(app, relevantBeans); @@ -199,12 +202,7 @@ public class AutowiredHoverProvider implements HoverProvider { return ((List)methodDeclaration.parameters()).stream() .filter(p -> p instanceof SingleVariableDeclaration) .map(p -> (SingleVariableDeclaration)p) - .map(singleVariableDeclaration -> { - // Supposed to be a list of one bean for the variable declaration - List matches = findAutowiredBeans(project, singleVariableDeclaration, beans); - return matches.isEmpty() ? null : matches.get(0); - }) - .filter(matchedBean -> matchedBean != null) + .flatMap(singleVariableDeclaration -> findAutowiredBeans(project, singleVariableDeclaration, beans).stream()) .collect(Collectors.toList()); } else if (declarationNode instanceof FieldDeclaration) { FieldDeclaration fieldDeclaration = (FieldDeclaration)declarationNode; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/AbstractInjectedIntoHoverProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/AbstractInjectedIntoHoverProvider.java index 984e2238e..605e404ee 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/AbstractInjectedIntoHoverProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/AbstractInjectedIntoHoverProvider.java @@ -64,7 +64,7 @@ public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider if (Stream.of(runningApps).anyMatch(app -> LiveHoverUtils.hasRelevantBeans(app, definedBean))) { Optional nameRange = ASTUtils.nameRange(doc, annotation); if (nameRange.isPresent()) { - List codeLenses = assembleCodeLenses(project, runningApps, definedBean, nameRange.get(), ASTUtils.getAnnotatedType(annotation) != null); + List codeLenses = assembleCodeLenses(project, runningApps, definedBean, nameRange.get(), annotation); return codeLenses.isEmpty() ? ImmutableList.of(new CodeLens(nameRange.get())) : codeLenses; } } @@ -83,7 +83,7 @@ public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider LiveBean definedBean = getDefinedBean(annotation); if (definedBean != null) { - Hover hover = assembleHover(project, runningApps, definedBean, ASTUtils.getAnnotatedType(annotation) != null); + Hover hover = assembleHover(project, runningApps, definedBean, annotation); if (hover != null) { Optional nameRange = ASTUtils.nameRange(doc, annotation); if (nameRange.isPresent()) { @@ -96,7 +96,7 @@ public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider return null; } - protected List assembleCodeLenses(IJavaProject project, SpringBootApp[] runningApps, LiveBean definedBean, Range range, boolean includeWiredBeans) { + protected List assembleCodeLenses(IJavaProject project, SpringBootApp[] runningApps, LiveBean definedBean, Range range, ASTNode astNode) { List codeLensList = new ArrayList<>(); for (SpringBootApp app : runningApps) { @@ -108,24 +108,26 @@ public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider if (!injectedBeans.isEmpty()) { // Break out of the loop. Just look for the first app with injected into beans List injectedCodeLenses = LiveHoverUtils.createCodeLensesForBeans(range, injectedBeans, BEANS_PREFIX, MAX_INLINE_BEANS_STRING_LENGTH, INLINE_BEANS_STRING_SEPARATOR); - builder.addAll(injectedCodeLenses == null ? ImmutableList.of(new CodeLens(range)) : injectedCodeLenses); - } - if (includeWiredBeans) { - List allDependencyBeans = LiveHoverUtils.findAllDependencyBeans(app, relevantBeans); - List wiredCodeLenses = LiveHoverUtils.createCodeLensesForBeans(range, allDependencyBeans, - AutowiredHoverProvider.BEANS_PREFIX, MAX_INLINE_BEANS_STRING_LENGTH, - INLINE_BEANS_STRING_SEPARATOR); - if (wiredCodeLenses != null) { - builder.addAll(wiredCodeLenses); - } + builder.addAll(injectedCodeLenses.isEmpty() ? ImmutableList.of(new CodeLens(range)) : injectedCodeLenses); } + + // Wired beans code lenses + List wiredBeans = findWiredBeans(project, app, relevantBeans, astNode); + builder.addAll(LiveHoverUtils.createCodeLensesForBeans(range, wiredBeans, + AutowiredHoverProvider.BEANS_PREFIX, MAX_INLINE_BEANS_STRING_LENGTH, + INLINE_BEANS_STRING_SEPARATOR)); + return builder.build(); } } return codeLensList; } - protected Hover assembleHover(IJavaProject project, SpringBootApp[] runningApps, LiveBean definedBean, boolean includeWiredBeans) { + protected List findWiredBeans(IJavaProject project, SpringBootApp app, List relevantBeans, ASTNode astNode) { + return Collections.emptyList(); + } + + protected Hover assembleHover(IJavaProject project, SpringBootApp[] runningApps, LiveBean definedBean, ASTNode astNode) { StringBuilder hover = new StringBuilder(); boolean hasContent = false; @@ -170,11 +172,9 @@ public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider hover.append("\n \n"); } - if (includeWiredBeans) { - List allDependencyBeans = LiveHoverUtils.findAllDependencyBeans(app, relevantBeans); - if (!allDependencyBeans.isEmpty()) { - AutowiredHoverProvider.createHoverContentForBeans(server, definedBean, project, hover, allDependencyBeans); - } + List wiredBeans = findWiredBeans(project, app, relevantBeans, astNode); + if (!wiredBeans.isEmpty()) { + AutowiredHoverProvider.createHoverContentForBeans(server, definedBean, project, hover, wiredBeans); } hover.append(LiveHoverUtils.niceAppName(app)); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/BeanInjectedIntoHoverProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/BeanInjectedIntoHoverProvider.java index c1ac6966d..0ccc773b3 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/BeanInjectedIntoHoverProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/BeanInjectedIntoHoverProvider.java @@ -10,13 +10,19 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.livehover; +import java.util.Collections; +import java.util.List; import java.util.Optional; +import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents; +import org.springframework.ide.vscode.boot.java.autowired.AutowiredHoverProvider; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; +import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBean; +import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.util.Optionals; public class BeanInjectedIntoHoverProvider extends AbstractInjectedIntoHoverProvider { @@ -66,4 +72,15 @@ public class BeanInjectedIntoHoverProvider extends AbstractInjectedIntoHoverProv ); } + @Override + protected List findWiredBeans(IJavaProject project, SpringBootApp app, List relevantBeans, ASTNode astNode) { + if (astNode instanceof Annotation) { + MethodDeclaration beanMethod = ASTUtils.getAnnotatedMethod((Annotation) astNode); + if (beanMethod != null) { + return AutowiredHoverProvider.getRelevantAutowiredBeans(project, beanMethod, app, relevantBeans); + } + } + return Collections.emptyList(); + } + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ComponentInjectionsHoverProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ComponentInjectionsHoverProvider.java index 82e01636f..981a45c00 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ComponentInjectionsHoverProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ComponentInjectionsHoverProvider.java @@ -11,6 +11,7 @@ package org.springframework.ide.vscode.boot.java.livehover; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; @@ -110,7 +111,7 @@ public class ComponentInjectionsHoverProvider extends AbstractInjectedIntoHoverP if (Stream.of(runningApps).anyMatch(app -> LiveHoverUtils.hasRelevantBeans(app, definedBean))) { Optional nameRange = Optional.of(ASTUtils.nodeRegion(doc, typeDeclaration.getName()).asRange()); if (nameRange.isPresent()) { - List codeLenses = assembleCodeLenses(project, runningApps, definedBean, nameRange.get(), true); + List codeLenses = assembleCodeLenses(project, runningApps, definedBean, nameRange.get(), typeDeclaration); return codeLenses.isEmpty() ? ImmutableList.of(new CodeLens(nameRange.get())) : codeLenses; } } @@ -130,7 +131,7 @@ public class ComponentInjectionsHoverProvider extends AbstractInjectedIntoHoverP LiveBean definedBean = getDefinedBeanForType(typeDeclaration, null); if (definedBean != null) { - Hover hover = assembleHover(project, runningApps, definedBean, true); + Hover hover = assembleHover(project, runningApps, definedBean, typeDeclaration); if (hover != null) { SimpleName name = typeDeclaration.getName(); try { @@ -145,6 +146,20 @@ public class ComponentInjectionsHoverProvider extends AbstractInjectedIntoHoverP return null; } + + + @Override + protected List findWiredBeans(IJavaProject project, SpringBootApp app, List relevantBeans, + ASTNode astNode) { + TypeDeclaration typeDeclaration = null; + if (astNode instanceof TypeDeclaration) { + typeDeclaration = (TypeDeclaration) astNode; + } else if (astNode instanceof Annotation) { + typeDeclaration = ASTUtils.getAnnotatedType((Annotation) astNode); + } + return typeDeclaration == null ? Collections.emptyList() : LiveHoverUtils.findAllDependencyBeans(app, relevantBeans); + } + private boolean isComponentAnnotatedType(TypeDeclaration typeDeclaration) { List modifiers = typeDeclaration.modifiers(); for (Object modifier : modifiers) { diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/BeanInjectedIntoHoverProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/BeanInjectedIntoHoverProviderTest.java index e126497e9..2746ec68e 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/BeanInjectedIntoHoverProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/BeanInjectedIntoHoverProviderTest.java @@ -39,6 +39,28 @@ public class BeanInjectedIntoHoverProviderTest { " void doSomeFoo();\n" + "}\n" ); + + p.createType("hello.IDependency", + "package hello;\n" + + "\n" + + "public interface IDependency {\n" + + "}\n" + ); + + p.createType("hello.DependencyA", + "package hello;\n" + + "\n" + + "public class DependencyA implements IDependency {\n" + + "}\n" + ); + + p.createType("hello.DependencyB", + "package hello;\n" + + "\n" + + "public class DependencyB implements IDependency {\n" + + "}\n" + ); + }; private BootJavaLanguageServerHarness harness; @@ -484,4 +506,214 @@ public class BeanInjectedIntoHoverProviderTest { editor.assertHighlights(/*NONE*/); editor.assertNoHover("@Bean"); } + + @Test + public void beanWithOneWiring() throws Exception { + LiveBeansModel beans = LiveBeansModel.builder() + .add(LiveBean.builder() + .id("fooImplementation") + .type("hello.FooImplementation") + .dependencies("depA") + .build() + ) + .add(LiveBean.builder() + .id("depA") + .type("hello.DependencyA") + .build() + ) + .build(); + mockAppProvider.builder() + .isSpringBootApp(true) + .processId("111") + .processName("the-app") + .beans(beans) + .build(); + + Editor editor = harness.newEditor(LanguageId.JAVA, + "package hello;\n" + + "\n" + + "import org.springframework.context.annotation.Bean;\n" + + "import org.springframework.context.annotation.Configuration;\n" + + "import org.springframework.context.annotation.Profile;\n" + + "\n" + + "@Configuration\n" + + "public class LocalConfig {\n" + + " \n" + + " @Bean(\"fooImplementation\")\n" + + " Foo someFoo(DependencyA a) {\n" + + " return new FooImplementation();\n" + + " }\n" + + "}" + ); + editor.assertHighlights("@Bean"); + editor.assertTrimmedHover("@Bean", + "**Injected `fooImplementation` → _not injected anywhere_** \n" + + "**Autowired `fooImplementation` ← `depA`**\n" + + "- Bean: `depA` \n" + + " Type: `hello.DependencyA`\n" + + " \n" + + "Process [PID=111, name=`the-app`]" + ); + } + + @Test + public void beanWithMultipleWirings() throws Exception { + LiveBeansModel beans = LiveBeansModel.builder() + .add(LiveBean.builder() + .id("fooImplementation") + .type("hello.FooImplementation") + .dependencies("depA", "depB") + .build() + ) + .add(LiveBean.builder() + .id("depA") + .type("hello.DependencyA") + .build() + ) + .add(LiveBean.builder() + .id("depB") + .type("hello.DependencyB") + .build() + ) + .build(); + mockAppProvider.builder() + .isSpringBootApp(true) + .processId("111") + .processName("the-app") + .beans(beans) + .build(); + + Editor editor = harness.newEditor(LanguageId.JAVA, + "package hello;\n" + + "\n" + + "import org.springframework.context.annotation.Bean;\n" + + "import org.springframework.context.annotation.Configuration;\n" + + "import org.springframework.context.annotation.Profile;\n" + + "\n" + + "@Configuration\n" + + "public class LocalConfig {\n" + + " \n" + + " @Bean(\"fooImplementation\")\n" + + " Foo someFoo(DependencyA a, DependencyB b) {\n" + + " return new FooImplementation();\n" + + " }\n" + + "}" + ); + editor.assertHighlights("@Bean"); + editor.assertTrimmedHover("@Bean", + "**Injected `fooImplementation` → _not injected anywhere_** \n" + + "**Autowired `fooImplementation` ← `depA` `depB`**\n" + + "- Bean: `depA` \n" + + " Type: `hello.DependencyA`\n" + + "- Bean: `depB` \n" + + " Type: `hello.DependencyB`\n" + + " \n" + + "Process [PID=111, name=`the-app`]" + ); + } + + @Test + public void beanWithCollectionWiring() throws Exception { + LiveBeansModel beans = LiveBeansModel.builder() + .add(LiveBean.builder() + .id("fooImplementation") + .type("hello.FooImplementation") + .dependencies("depA", "depB") + .build() + ) + .add(LiveBean.builder() + .id("depA") + .type("hello.DependencyA") + .build() + ) + .add(LiveBean.builder() + .id("depB") + .type("hello.DependencyB") + .build() + ) + .build(); + mockAppProvider.builder() + .isSpringBootApp(true) + .processId("111") + .processName("the-app") + .beans(beans) + .build(); + + Editor editor = harness.newEditor(LanguageId.JAVA, + "package hello;\n" + + "\n" + + "import org.springframework.context.annotation.Bean;\n" + + "import org.springframework.context.annotation.Configuration;\n" + + "import org.springframework.context.annotation.Profile;\n" + + "\n" + + "@Configuration\n" + + "public class LocalConfig {\n" + + " \n" + + " @Bean(\"fooImplementation\")\n" + + " Foo someFoo(IDependency[] deps) {\n" + + " return new FooImplementation();\n" + + " }\n" + + "}" + ); + editor.assertHighlights("@Bean"); + editor.assertTrimmedHover("@Bean", + "**Injected `fooImplementation` → _not injected anywhere_** \n" + + "**Autowired `fooImplementation` ← `depA` `depB`**\n" + + "- Bean: `depA` \n" + + " Type: `hello.DependencyA`\n" + + "- Bean: `depB` \n" + + " Type: `hello.DependencyB`\n" + + " \n" + + "Process [PID=111, name=`the-app`]" + ); + } + + @Test + public void beanWithQualifierWiring() throws Exception { + LiveBeansModel beans = LiveBeansModel.builder() + .add(LiveBean.builder() + .id("fooImplementation") + .type("hello.FooImplementation") + .dependencies("depA", "depB") + .build() + ) + .add(LiveBean.builder() + .id("depB") + .type("hello.DependencyB") + .build() + ) + .build(); + mockAppProvider.builder() + .isSpringBootApp(true) + .processId("111") + .processName("the-app") + .beans(beans) + .build(); + + Editor editor = harness.newEditor(LanguageId.JAVA, + "package hello;\n" + + "\n" + + "import org.springframework.context.annotation.Bean;\n" + + "import org.springframework.context.annotation.Configuration;\n" + + "import org.springframework.context.annotation.Profile;\n" + + "\n" + + "@Configuration\n" + + "public class LocalConfig {\n" + + " \n" + + " @Bean(\"fooImplementation\")\n" + + " Foo someFoo(@Qualifier(\"depB\") IDependency[] deps) {\n" + + " return new FooImplementation();\n" + + " }\n" + + "}" + ); + editor.assertHighlights("@Bean"); + editor.assertTrimmedHover("@Bean", + "**Injected `fooImplementation` → _not injected anywhere_** \n" + + "**Autowired `fooImplementation` ← `depB`**\n" + + "- Bean: `depB` \n" + + " Type: `hello.DependencyB`\n" + + " \n" + + "Process [PID=111, name=`the-app`]" + ); + } }