diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java index 7ea6dcd5c..2337e786c 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java @@ -22,7 +22,9 @@ import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.boot.java.handlers.HoverProvider; +import org.springframework.ide.vscode.boot.java.livehover.ComponentInjectionsHoverProvider; import org.springframework.ide.vscode.boot.java.livehover.LiveHoverUtils; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; @@ -106,7 +108,6 @@ public class AutowiredHoverProvider implements HoverProvider { } } if (hasInterestingApp && hasAutowiring) { - System.out.println(hover); return CompletableFuture .completedFuture(new Hover(ImmutableList.of(Either.forLeft(hover.toString())))); } @@ -115,25 +116,31 @@ public class AutowiredHoverProvider implements HoverProvider { return null; } - private LiveBean getDefinedBean(Annotation annotation) { - TypeDeclaration declaringType = ASTUtils.findDeclaringType(annotation); + private LiveBean getDefinedBean(Annotation autowiredAnnotation) { + TypeDeclaration declaringType = ASTUtils.findDeclaringType(autowiredAnnotation); if (declaringType != null) { - ITypeBinding beanType = declaringType.resolveBinding(); - if (beanType != null) { - String id = getBeanId(declaringType, beanType); - if (StringUtil.hasText(id)) { - return LiveBean.builder().id(id).type(beanType.getQualifiedName()).build(); + for (Annotation annotation : ASTUtils.getAnnotations(declaringType)) { + String annotationType = ASTUtils.getAnnotationType(annotation); + switch (annotationType) { + case Annotations.COMPONENT: + return ComponentInjectionsHoverProvider.getDefinedBeanForComponent(annotation); + default: + break; } } - } - return null; - } - - private String getBeanId(TypeDeclaration declaringType, ITypeBinding beanType) { - // TODO: take specific bean declarations into account like @Component at declaring type - String typeName = beanType.getName(); - if (StringUtil.hasText(typeName)) { - return Character.toLowerCase(typeName.charAt(0)) + typeName.substring(1); + //TODO: handler below is an attempt to do something that may work in many cases, but is probably + // missing logics for special cases where annotation attributes on the declaring type matter. + ITypeBinding beanType = declaringType.resolveBinding(); + if (beanType!=null) { + String beanTypeName = beanType.getName(); + if (StringUtil.hasText(beanTypeName)) { + return LiveBean.builder() + .id(Character.toLowerCase(beanTypeName.charAt(0)) + beanTypeName.substring(1)) + .type(beanTypeName) + .build(); + } + } + return null; } return null; } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/AbstractInjectedIntoHoverProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/AbstractInjectedIntoHoverProvider.java index 557561e9c..20cd55f41 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/AbstractInjectedIntoHoverProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/AbstractInjectedIntoHoverProvider.java @@ -87,7 +87,6 @@ public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider } } if (hasInterestingApp) { - System.out.println(hover); return CompletableFuture .completedFuture(new Hover(ImmutableList.of(Either.forLeft(hover.toString())))); } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ComponentInjectionsHoverProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ComponentInjectionsHoverProvider.java index 4a0c0fe74..bd73db5a1 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ComponentInjectionsHoverProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ComponentInjectionsHoverProvider.java @@ -68,6 +68,11 @@ public class ComponentInjectionsHoverProvider extends AbstractInjectedIntoHoverP @Override protected LiveBean getDefinedBean(Annotation annotation) { + return getDefinedBeanForComponent(annotation); + } + + public static LiveBean getDefinedBeanForComponent(Annotation annotation) { + //Move to ASTUtils? TypeDeclaration declaringType = ASTUtils.getAnnotatedType(annotation); if (declaringType != null) { ITypeBinding beanType = declaringType.resolveBinding(); @@ -81,7 +86,7 @@ public class ComponentInjectionsHoverProvider extends AbstractInjectedIntoHoverP return null; } - private String getBeanId(Annotation annotation, ITypeBinding beanType) { + private static String getBeanId(Annotation annotation, ITypeBinding beanType) { return ASTUtils.getAttribute(annotation, "value").flatMap(ASTUtils::getFirstString) .orElseGet(() -> { String typeName = beanType.getName(); diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java index 9cf569fab..3030234d9 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java @@ -11,14 +11,17 @@ package org.springframework.ide.vscode.boot.java.utils; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.Optional; import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.ArrayInitializer; import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MemberValuePair; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.NormalAnnotation; @@ -32,6 +35,8 @@ import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion import org.springframework.ide.vscode.commons.util.Log; import org.springframework.ide.vscode.commons.util.text.TextDocument; +import com.google.common.collect.ImmutableList; + public class ASTUtils { public static DocumentRegion nameRegion(TextDocument doc, Annotation annotation) { @@ -161,8 +166,7 @@ public class ASTUtils { } @SuppressWarnings("unchecked") - public - static String[] getExpressionValueAsArray(Expression exp) { + public static String[] getExpressionValueAsArray(Expression exp) { if (exp instanceof ArrayInitializer) { ArrayInitializer array = (ArrayInitializer) exp; return ((List) array.expressions()).stream().map(e -> getExpressionValueAsString(e)) @@ -176,4 +180,28 @@ public class ASTUtils { return null; } + + public static Collection getAnnotations(TypeDeclaration declaringType) { + Object modifiersObj = declaringType.getStructuralProperty(TypeDeclaration.MODIFIERS2_PROPERTY); + if (modifiersObj instanceof List) { + ImmutableList.Builder annotations = ImmutableList.builder(); + for (Object node : (List)modifiersObj) { + if (node instanceof Annotation) { + annotations.add((Annotation) node); + } + } + return annotations.build(); + } + return ImmutableList.of(); + } + + + public static String getAnnotationType(Annotation annotation) { + ITypeBinding binding = annotation.resolveTypeBinding(); + if (binding!=null) { + return binding.getQualifiedName(); + } + return null; + } + } diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/AutowiredHoverProviderTest.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/AutowiredHoverProviderTest.java index 18b3f707e..ce6c78921 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/AutowiredHoverProviderTest.java +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/AutowiredHoverProviderTest.java @@ -241,4 +241,67 @@ public class AutowiredHoverProviderTest { editor.assertNoHover("@Autowired"); } + @Test public void bug_152553935() throws Exception { + //https://www.pivotaltracker.com/story/show/152553935 + LiveBeansModel beans = LiveBeansModel.builder() + .add(LiveBean.builder() + .id("defaultFoo") + .type("com.example.FooImplementation") + .dependencies("defaultFoo") + .dependencies("otherBean") + .build() + ) + .add(LiveBean.builder() + .id("otherBean") + .type("com.example.DependencyA") + .build() + ) + .build(); + mockAppProvider.builder() + .isSpringBootApp(true) + .processId("111") + .processName("the-app") + .beans(beans) + .build(); + + + Editor editor = harness.newEditor( + "package com.example;\n" + + "\n" + + "import org.springframework.beans.factory.annotation.Autowired;\n" + + "import org.springframework.scheduling.TaskScheduler;\n" + + "import org.springframework.stereotype.Component;\n" + + "\n" + + "@Component(\"defaultFoo\")\n" + + "public class FooImplementation implements Foo {\n" + + " \n" + + " private TaskScheduler scheduler;\n" + + " \n" + + " @Autowired Foo self;\n" + + " \n" + + " @Override\n" + + " public void doSomeFoo() {\n" + + " scheduler.scheduleWithFixedDelay(() -> {\n" + + " System.out.println(\"Doo Done done!\");\n" + + " }, 1000);\n" + + " System.out.println(\"Foo do do do do!\");\n" + + " }\n" + + "\n" + + " @Autowired\n" + + " public void setScheduler(TaskScheduler scheduler) {\n" + + " this.scheduler = scheduler;\n" + + " }\n" + + "\n" + + "}" + ); + editor.assertHighlights("@Component", "@Autowired", "@Autowired"); + for (int i = 1; i <= 2; i++) { + editor.assertHoverContains("@Autowired", 1, + "Bean [id: defaultFoo, type: `com.example.FooImplementation`] got autowired with:\n" + + "\n" + + "- Bean: otherBean \n" + + " Type: `com.example.DependencyA`"); + } + } + }