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 4e251747b..a0fcc5303 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 @@ -47,6 +47,8 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument; import com.google.common.collect.ImmutableList; +import reactor.core.publisher.Flux; + /** * @author Martin Lippert * @author Alex Boyko @@ -196,42 +198,53 @@ public class AutowiredHoverProvider implements HoverProvider { return ((List)methodDeclaration.parameters()).stream() .filter(p -> p instanceof SingleVariableDeclaration) .map(p -> (SingleVariableDeclaration)p) - .flatMap(p -> findAutowiredBeans(project, p, beans).stream()) + .map(singleVariableDeclaration -> matchBeanByType(project, beans, singleVariableDeclaration.getType().resolveBinding())) + .filter(matchedBean -> matchedBean != null) .collect(Collectors.toList()); } else if (declarationNode instanceof FieldDeclaration) { FieldDeclaration fieldDeclaration = (FieldDeclaration)declarationNode; - return matchBeans(project, beans, fieldDeclaration.getType().resolveBinding()); + LiveBean matchedBean = matchBeanByType(project, beans, fieldDeclaration.getType().resolveBinding()); + if (matchedBean != null) { + return ImmutableList.of(matchedBean); + } } else if (declarationNode instanceof SingleVariableDeclaration) { SingleVariableDeclaration singleVariableDeclaration = (SingleVariableDeclaration)declarationNode; - return matchBeans(project, beans, singleVariableDeclaration.getType().resolveBinding()); + LiveBean matchedBean = matchBeanByType(project, beans, singleVariableDeclaration.getType().resolveBinding()); + if (matchedBean != null) { + return ImmutableList.of(matchedBean); + } } return Collections.emptyList(); } - private static List matchBeans(IJavaProject project, Collection beans, ITypeBinding type) { - List relevant = Collections.emptyList(); + private static LiveBean matchBeanByType(IJavaProject project, Collection beans, ITypeBinding type) { if (type != null) { String fqName = type.getQualifiedName(); if (fqName != null) { - relevant = matchBeans(project, beans, fqName, true); - if (relevant.isEmpty()) { + List matches = matchBeansByFQName(project, beans, fqName, true); + if (!matches.isEmpty()) { + return matches.size() == 1 ? matches.get(0) : LiveHoverUtils.CANT_MATCH_PROPER_BEAN; + } else { IType indexType = project.findType(fqName); if (indexType != null) { - relevant = project.allSubtypesOf(indexType) - .map(subType -> matchBeans(project, beans, subType.getFullyQualifiedName(), false)) + matches = project.allSubtypesOf(indexType) + .map(subType -> matchBeansByFQName(project, beans, subType.getFullyQualifiedName(), false)) .filter(relevantBeans -> !relevantBeans.isEmpty()) + .flatMap(bs -> Flux.fromIterable(bs)) + .buffer(2) .blockFirst(); - if (relevant == null) { - relevant = Collections.emptyList(); + + if (!matches.isEmpty()) { + return matches.size() == 1 ? matches.get(0) : LiveHoverUtils.CANT_MATCH_PROPER_BEAN; } } } } } - return relevant; + return null; } - private static List matchBeans(IJavaProject project, Collection beans, String fqName, boolean allDots) { + private static List matchBeansByFQName(IJavaProject project, Collection beans, String fqName, boolean allDots) { if (fqName != null) { if (allDots) { return beans.stream().filter(b -> fqName.equals(b.getType(true).replace('$', '.'))).collect(Collectors.toList()); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/LiveHoverUtils.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/LiveHoverUtils.java index 21fadba36..bdc80f91b 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/LiveHoverUtils.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/LiveHoverUtils.java @@ -35,6 +35,8 @@ import com.google.common.collect.ImmutableList; public class LiveHoverUtils { + public static final LiveBean CANT_MATCH_PROPER_BEAN = LiveBean.builder().id("UNKNOWN").build(); + public static String showBean(LiveBean bean) { StringBuilder buf = new StringBuilder("Bean [id: " + bean.getId()); String type = bean.getType(true); @@ -48,55 +50,68 @@ public class LiveHoverUtils { public static String showBeanWithResource(BootJavaLanguageServerComponents server, LiveBean bean, String indentStr, IJavaProject project) { String newline = " \n"+indentStr; //Note: the double space before newline makes markdown see it as a real line break - String type = bean.getType(true); + if (bean == CANT_MATCH_PROPER_BEAN) { + return "(Cannot find precise information for the bean)"; + } else { + String type = bean.getType(true); - StringBuilder buf = new StringBuilder("Bean: "); - buf.append('`'); - buf.append(bean.getId()); - buf.append('`'); - SourceLinks sourceLinks = SourceLinkFactory.createSourceLinks(server); - if (type != null) { - // Try creating a URL link to open source for the type - buf.append(newline); - buf.append("Type: "); - Optional url = sourceLinks.sourceLinkUrlForFQName(project, type); - if (url.isPresent()) { - buf.append(Renderables.link(type, url.get()).toMarkdown()); - } else { - buf.append("`" + type + "`"); + StringBuilder buf = new StringBuilder("Bean: "); + buf.append('`'); + buf.append(bean.getId()); + buf.append('`'); + SourceLinks sourceLinks = SourceLinkFactory.createSourceLinks(server); + if (type != null) { + // Try creating a URL link to open source for the type + buf.append(newline); + buf.append("Type: "); + Optional url = sourceLinks.sourceLinkUrlForFQName(project, type); + if (url.isPresent()) { + buf.append(Renderables.link(type, url.get()).toMarkdown()); + } else { + buf.append("`" + type + "`"); + } } + String resource = bean.getResource(); + if (StringUtil.hasText(resource)) { + buf.append(newline); + buf.append("Resource: "); + buf.append(showResource(sourceLinks, resource, project)); + } + return buf.toString(); } - String resource = bean.getResource(); - if (StringUtil.hasText(resource)) { - buf.append(newline); - buf.append("Resource: "); - buf.append(showResource(sourceLinks, resource, project)); - } - return buf.toString(); } public static String getShortDisplayType(LiveBean bean) { - String type = bean.getType(true); - int idx = type.lastIndexOf('.'); - return idx < 0 || idx == type.length() - 1 ? type : type.substring(idx + 1); + if (bean == CANT_MATCH_PROPER_BEAN) { + return CANT_MATCH_PROPER_BEAN.getId(); + } else { + String type = bean.getType(true); + int idx = type.lastIndexOf('.'); + String typeStr = idx < 0 || idx == type.length() - 1 ? type : type.substring(idx + 1); + return typeStr; + } } public static String showBeanInline(BootJavaLanguageServerComponents server, IJavaProject project, LiveBean bean) { - String id = bean.getId(); - String type = bean.getType(true); - StringBuilder sb = new StringBuilder(); - sb.append('`'); - sb.append(id); - sb.append('`'); - String displayId = sb.toString(); - SourceLinks sourceLinks = SourceLinkFactory.createSourceLinks(server); - if (type != null) { - Optional url = sourceLinks.sourceLinkUrlForFQName(project, type); - if (url.isPresent()) { - return Renderables.link(displayId, url.get()).toMarkdown(); + if (bean == CANT_MATCH_PROPER_BEAN) { + return CANT_MATCH_PROPER_BEAN.getId(); + } else { + String id = bean.getId(); + String type = bean.getType(true); + StringBuilder sb = new StringBuilder(); + sb.append('`'); + sb.append(id); + sb.append('`'); + String displayId = sb.toString(); + SourceLinks sourceLinks = SourceLinkFactory.createSourceLinks(server); + if (type != null) { + Optional url = sourceLinks.sourceLinkUrlForFQName(project, type); + if (url.isPresent()) { + return Renderables.link(displayId, url.get()).toMarkdown(); + } } + return displayId; } - return displayId; } public static boolean doBeansFitInline(Collection beans, int maxLength, String delimiter) { @@ -214,4 +229,5 @@ public class LiveHoverUtils { } } + } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/AutowiredHoverProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/AutowiredHoverProviderTest.java index 9b73a8011..76a882859 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/AutowiredHoverProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/AutowiredHoverProviderTest.java @@ -72,17 +72,24 @@ public class AutowiredHoverProviderTest { "}\n" ); + p.createType("com.examle.IDependency", + "package com.example;\n" + + "\n" + + "public interface IDependency {\n" + + "}\n" + ); + p.createType("com.examle.DependencyA", "package com.example;\n" + "\n" + - "public class DependencyA {\n" + + "public class DependencyA implements IDependency {\n" + "}\n" ); p.createType("com.examle.DependencyB", "package com.example;\n" + "\n" + - "public class DependencyB {\n" + + "public class DependencyB implements IDependency {\n" + "}\n" ); @@ -606,6 +613,60 @@ public class AutowiredHoverProviderTest { } } + @Test + public void unableToMatchWiredBean() throws Exception { + LiveBeansModel beans = LiveBeansModel.builder() + .add(LiveBean.builder() + .id("someComponent") + .type("com.example.SomeComponent") + .dependencies("dependencyA", "dependencyB") + .build() + ) + .add(LiveBean.builder() + .id("dependencyA") + .type("com.example.DependencyA") + .build() + ) + .add(LiveBean.builder() + .id("dependencyB") + .type("com.example.DependencyB") + .build() + ) + .build(); + mockAppProvider.builder() + .isSpringBootApp(true) + .processId("111") + .processName("the-app") + .beans(beans) + .build(); + + Editor editor = harness.newEditor(LanguageId.JAVA, + "package com.example;\n" + + "\n" + + "import org.springframework.beans.factory.annotation.Autowired;\n" + + "import org.springframework.stereotype.Component;\n" + + "\n" + + "@Component\n" + + "public class SomeComponent {\n" + + "\n" + + " @Autowired\n" + + " private IDependency dep;\n" + + "\n" + + " public SomeComponent() {\n" + + " }\n" + + "\n" + + "}\n" + ); + + editor.assertHighlights("@Component", "@Autowired"); + editor.assertTrimmedHover("@Autowired", 1, + "**Autowired `someComponent` ← UNKNOWN**\n" + + "- (Cannot find precise information for the bean)\n" + + " \n" + + "Process [PID=111, name=`the-app`]\n" + ); + } + @Test public void anonymousInnerClassBeanWiring() throws Exception { LiveBeansModel beans = LiveBeansModel.builder()