diff --git a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java index 85351652f9..62a9c5dc2a 100644 --- a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java +++ b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java @@ -600,18 +600,26 @@ public void assertHoverContains(String hoverOver, String snippet) throws Excepti } public void assertTrimmedHover(String hoverOver, String expectedHover) throws Exception { - int hoverPosition = getHoverPosition(hoverOver,1); + assertTrimmedHover(hoverOver, 1, expectedHover); + } + + public void assertTrimmedHover(String hoverOver, int occurence, String expectedHover) throws Exception { + int hoverPosition = getHoverPosition(hoverOver,occurence); Hover hover = harness.getHover(doc, doc.toPosition(hoverPosition)); assertEquals(expectedHover.trim(), hoverString(hover).trim()); } - public void assertNoHover(String hoverOver) throws Exception { - int hoverPosition = getRawText().indexOf(hoverOver) + hoverOver.length() / 2; + public void assertNoHover(String hoverOver, int occurence) throws Exception { + int hoverPosition = getHoverPosition(hoverOver,occurence); Hover hover = harness.getHover(doc, doc.toPosition(hoverPosition)); List> contents = hover.getContents().getLeft(); assertTrue(contents.toString(), contents.isEmpty()); } + public void assertNoHover(String hoverOver) throws Exception { + assertNoHover(hoverOver, 1); + } + /** * Verifies an expected textSnippet is contained in the hover text that is * computed when hovering mouse at position at the end of first occurrence of 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 aedb86d9e5..b19aa07dca 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 @@ -20,6 +20,7 @@ import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.MarkerAnnotation; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.TypeDeclaration; @@ -40,6 +41,7 @@ import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.IType; +import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.StringUtil; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -63,109 +65,116 @@ public AutowiredHoverProvider(BootJavaLanguageServerComponents server) { } @Override - public Collection getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { + public Collection getLiveHoverHints(IJavaProject project, Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { + LiveBean definedBean = getDefinedBeanForTypeDeclaration(ASTUtils.findDeclaringType(annotation)); + // Annotation is MarkerNode, parent is some field, method, variable declaration node. + ASTNode declarationNode = annotation.getParent(); try { - LiveBean definedBean = getDefinedBean(annotation); - if (definedBean != null) { - for (SpringBootApp app : runningApps) { - try { - List relevantBeans = LiveHoverUtils.findRelevantBeans(app, definedBean).collect(Collectors.toList()); - - if (!relevantBeans.isEmpty()) { - for (LiveBean bean : relevantBeans) { - String[] dependencies = bean.getDependencies(); - if (dependencies != null && dependencies.length > 0) { - Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength()); - return ImmutableList.of(hoverRange); - } - } - } - } - catch (Exception e) { - log.error("", e); - } - } - } - } - catch (Exception e) { + Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength()); + return getLiveHoverHints(project, declarationNode, hoverRange, runningApps, definedBean); + } catch (BadLocationException e) { log.error("", e); } + return null; + } + private Collection getLiveHoverHints(IJavaProject project, ASTNode declarationNode, Range range, + SpringBootApp[] runningApps, LiveBean definedBean) { + if (declarationNode != null && definedBean != null) { + for (SpringBootApp app : runningApps) { + List relevantBeans = getRelevantAutowiredBeans(project, declarationNode, app, definedBean); + if (!relevantBeans.isEmpty()) { + return ImmutableList.of(range); + } + } + } return null; } @Override public Hover provideHover(ASTNode node, Annotation annotation, ITypeBinding type, int offset, TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) { - if (runningApps.length > 0) { + LiveBean definedBean = getDefinedBeanForTypeDeclaration(ASTUtils.findDeclaringType(annotation)); + // Annotation is MarkerNode, parent is some field, method, variable declaration node. + ASTNode declarationNode = annotation.getParent(); + return provideHover(definedBean, declarationNode, offset, doc, project, runningApps); + } + + private Hover provideHover(LiveBean definedBean, ASTNode declarationNode, int offset, TextDocument doc, + IJavaProject project, SpringBootApp[] runningApps) { + if (definedBean != null && runningApps.length > 0) { StringBuilder hover = new StringBuilder(); - LiveBean definedBean = getDefinedBean(annotation); - if (definedBean != null) { - - boolean hasContent = false; - - for (SpringBootApp app : runningApps) { - LiveBeansModel beans = app.getBeans(); - List relevantBeans = LiveHoverUtils.findRelevantBeans(app, definedBean).collect(Collectors.toList()); - - if (!relevantBeans.isEmpty()) { - List allDependencyBeans = relevantBeans.stream() - .flatMap(b -> Arrays.stream(b.getDependencies())) - .distinct() - .flatMap(d -> beans.getBeansOfName(d).stream()) - .collect(Collectors.toList()); - - if (!allDependencyBeans.isEmpty()) { - - // parent is marker node, grandparent is some field, method, variable declaration node. - ASTNode declarationNode = node.getParent().getParent(); - List autowiredBeans = findAutowiredBeans(project, declarationNode, allDependencyBeans); - if (autowiredBeans.isEmpty()) { - // Show all relevant dependency beans - autowiredBeans = allDependencyBeans; - } - - if (!autowiredBeans.isEmpty()) { - if (!hasContent) { - hasContent = true; - } else { - hover.append(" \n \n"); - } - hover.append("**Autowired → "); - if (LiveHoverUtils.doBeansFitInline(autowiredBeans, MAX_INLINE_BEANS_STRING_LENGTH, INLINE_BEANS_STRING_SEPARATOR)) { - hover.append(autowiredBeans.stream().map(b -> LiveHoverUtils.showBeanInline(server, project, b)).collect(Collectors.joining(INLINE_BEANS_STRING_SEPARATOR))); - hover.append("**\n"); - } else { - hover.append(autowiredBeans.size()); - hover.append(" beans**\n"); - } + boolean hasContent = false; + + for (SpringBootApp app : runningApps) { + + List autowiredBeans = getRelevantAutowiredBeans(project, declarationNode, app, definedBean); + + if (!autowiredBeans.isEmpty()) { + if (!hasContent) { + hasContent = true; + } else { + hover.append(" \n \n"); + } + hover.append("**Autowired → "); + if (LiveHoverUtils.doBeansFitInline(autowiredBeans, MAX_INLINE_BEANS_STRING_LENGTH, + INLINE_BEANS_STRING_SEPARATOR)) { + hover.append(autowiredBeans.stream().map(b -> LiveHoverUtils.showBeanInline(server, project, b)) + .collect(Collectors.joining(INLINE_BEANS_STRING_SEPARATOR))); + hover.append("**\n"); + } else { + hover.append(autowiredBeans.size()); + hover.append(" beans**\n"); + } // if (autowiredBeans.size() == 1) { // hover.append(LiveHoverUtils.showBeanIdAndTypeInline(server, project, autowiredBeans.get(0))); // } else { // hover.append(autowiredBeans.size()); // hover.append(" beans**\n"); // } - hover.append(autowiredBeans.stream() - .map(b -> "- " + LiveHoverUtils.showBeanWithResource(server, b, " ", project)) - .collect(Collectors.joining("\n")) - ); - hover.append("\n \n"); - hover.append(LiveHoverUtils.niceAppName(app)); - } - } - } - - } - if (hasContent) { - return new Hover(ImmutableList.of(Either.forLeft(hover.toString()))); + hover.append(autowiredBeans.stream() + .map(b -> "- " + LiveHoverUtils.showBeanWithResource(server, b, " ", project)) + .collect(Collectors.joining("\n"))); + hover.append("\n \n"); + hover.append(LiveHoverUtils.niceAppName(app)); } + + } + if (hasContent) { + return new Hover(ImmutableList.of(Either.forLeft(hover.toString()))); } } return null; } + private List getRelevantAutowiredBeans(IJavaProject project, ASTNode declarationNode, SpringBootApp app, LiveBean definedBean) { + LiveBeansModel beans = app.getBeans(); + List relevantBeans = LiveHoverUtils.findRelevantBeans(app, definedBean) + .collect(Collectors.toList()); + + if (!relevantBeans.isEmpty()) { + List allDependencyBeans = relevantBeans.stream() + .flatMap(b -> Arrays.stream(b.getDependencies())).distinct() + .flatMap(d -> beans.getBeansOfName(d).stream()).collect(Collectors.toList()); + + if (!allDependencyBeans.isEmpty()) { + + List autowiredBeans = findAutowiredBeans(project, declarationNode, + allDependencyBeans); + if (autowiredBeans.isEmpty()) { + // Show all relevant dependency beans + autowiredBeans = allDependencyBeans; + } else { + return autowiredBeans; + } + } + } + + return Collections.emptyList(); + } + @SuppressWarnings("unchecked") private List findAutowiredBeans(IJavaProject project, ASTNode declarationNode, Collection beans) { if (declarationNode instanceof MethodDeclaration) { @@ -198,6 +207,9 @@ private List matchBeans(IJavaProject project, Collection bea .map(subType -> matchBeans(project, beans, subType.getFullyQualifiedName())) .filter(relevantBeans -> !relevantBeans.isEmpty()) .blockFirst(); + if (relevant == null) { + relevant = Collections.emptyList(); + } } } } @@ -213,40 +225,71 @@ private List matchBeans(IJavaProject project, Collection bea } } - private LiveBean getDefinedBean(Annotation autowiredAnnotation) { - TypeDeclaration declaringType = ASTUtils.findDeclaringType(autowiredAnnotation); + private LiveBean getDefinedBeanForTypeDeclaration(TypeDeclaration declaringType) { if (declaringType != null) { for (Annotation annotation : ASTUtils.getAnnotations(declaringType)) { if (AnnotationHierarchies.isSubtypeOf(annotation, Annotations.COMPONENT)) { return ComponentInjectionsHoverProvider.getDefinedBeanForComponent(annotation); } } - //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. + // 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) { + 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(); + .type(beanTypeName).build(); } } - return null; } return null; } @Override - public Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset, - TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) { - return null; + public Hover provideHover(MethodDeclaration methodDeclaration, int offset, TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) { + LiveBean definedBean = getDefinedBeanForImplicitAutowiredConstructor(methodDeclaration); + return provideHover(definedBean, methodDeclaration, offset, doc, project, runningApps); } @Override - public Collection getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc, SpringBootApp[] runningApps) { + public Collection getLiveHoverHints(IJavaProject project, MethodDeclaration methodDeclaration, TextDocument doc, + SpringBootApp[] runningApps) { + LiveBean definedBean = getDefinedBeanForImplicitAutowiredConstructor(methodDeclaration); + try { + Range hoverRange = doc.toRange(methodDeclaration.getName().getStartPosition(), methodDeclaration.getName().getLength()); + return getLiveHoverHints(project, methodDeclaration, hoverRange, runningApps, definedBean); + } catch (BadLocationException e) { + log.error("", e); + } + return null; + } + + private LiveBean getDefinedBeanForImplicitAutowiredConstructor(MethodDeclaration methodDeclaration) { + if (methodDeclaration.isConstructor() && !methodDeclaration.parameters().isEmpty()) { + TypeDeclaration typeDeclaration = ASTUtils.findDeclaringType(methodDeclaration); + if (typeDeclaration != null && ASTUtils.hasExactlyOneConstructor(typeDeclaration) && !hasAutowiredAnnotation(methodDeclaration)) { + return getDefinedBeanForTypeDeclaration(typeDeclaration); + } + } return null; } + private boolean hasAutowiredAnnotation(MethodDeclaration constructor) { + List modifiers = constructor.modifiers(); + for (Object modifier : modifiers) { + if (modifier instanceof MarkerAnnotation) { + ITypeBinding typeBinding = ((MarkerAnnotation) modifier).resolveTypeBinding(); + if (typeBinding != null) { + String fqName = typeBinding.getQualifiedName(); + return Annotations.AUTOWIRED.equals(fqName) || Annotations.INJECT.equals(fqName); + } + } + } + return false; + } + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/ConditionalsLiveHoverProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/ConditionalsLiveHoverProvider.java index 20775004bf..fa6ea4256b 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/ConditionalsLiveHoverProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/ConditionalsLiveHoverProvider.java @@ -28,7 +28,6 @@ import org.springframework.ide.vscode.boot.java.handlers.HoverProvider; import org.springframework.ide.vscode.boot.java.livehover.LiveHoverUtils; import org.springframework.ide.vscode.commons.boot.app.cli.LiveConditional; -import org.springframework.ide.vscode.commons.boot.app.cli.LocalSpringBootApp; import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.util.Log; @@ -50,7 +49,7 @@ public Hover provideHover(ASTNode node, Annotation annotation, ITypeBinding type } @Override - public Collection getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { + public Collection getLiveHoverHints(IJavaProject project, Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { try { Optional> val = getMatchedLiveConditionals(annotation, runningApps); if (val.isPresent()) { @@ -156,16 +155,4 @@ protected boolean matchesAnnotation(Annotation annotation, LiveConditional liveC return false; } - @Override - public Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset, - TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) { - return null; - } - - @Override - public Collection getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc, - SpringBootApp[] runningApps) { - return null; - } - } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaHoverProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaHoverProvider.java index 0c6ebe255a..1908b3036f 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaHoverProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaHoverProvider.java @@ -19,6 +19,7 @@ import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MarkerAnnotation; +import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.core.dom.NormalAnnotation; import org.eclipse.jdt.core.dom.SimpleName; @@ -140,6 +141,19 @@ public boolean visit(MarkerAnnotation node) { return super.visit(node); } + + @Override + public boolean visit(MethodDeclaration node) { + try { + extractLiveHintsForMethod(node, document, runningBootApps, result); + } catch (Exception e) { + Log.log(e); + } + + return super.visit(node); + } + + }); } } catch (Exception e) { @@ -149,13 +163,33 @@ public boolean visit(MarkerAnnotation node) { }); } + protected void extractLiveHintsForMethod(MethodDeclaration methodDeclaration, TextDocument doc, + SpringBootApp[] runningApps, Collection result) { + Collection providers = this.hoverProviders.getAll(); + if (!providers.isEmpty()) { + for (HoverProvider provider : providers) { + getProject(doc).ifPresent(project -> { + if (hasActuatorDependency(project)) { + Collection hints = provider.getLiveHoverHints(project, methodDeclaration, doc, runningApps); + if (hints!=null) { + result.addAll(hints); + } + } else { + //Do nothing... we don't want a highlight for the 'no actuator warning' + //ASTUtils.nameRange(doc, annotation).ifPresent(result::add); + } + }); + } + } + } + protected void extractLiveHintsForType(TypeDeclaration typeDeclaration, TextDocument doc, SpringBootApp[] runningApps, Collection result) { Collection providers = this.hoverProviders.getAll(); if (!providers.isEmpty()) { for (HoverProvider provider : providers) { getProject(doc).ifPresent(project -> { if (hasActuatorDependency(project)) { - Collection hints = provider.getLiveHoverHints(typeDeclaration, doc, runningApps); + Collection hints = provider.getLiveHoverHints(project, typeDeclaration, doc, runningApps); if (hints!=null) { result.addAll(hints); } @@ -175,7 +209,7 @@ protected void extractLiveHintsForAnnotation(Annotation annotation, TextDocument for (HoverProvider provider : this.hoverProviders.get(type)) { getProject(doc).ifPresent(project -> { if (hasActuatorDependency(project)) { - Collection hints = provider.getLiveHoverHints(annotation, doc, runningApps); + Collection hints = provider.getLiveHoverHints(project, annotation, doc, runningApps); if (hints!=null) { result.addAll(hints); } @@ -215,10 +249,29 @@ private Hover provideHover(ASTNode node, int offset, TextDocument doc, IJavaProj } // then do additional AST node coverage - if (node instanceof SimpleName && node.getParent() instanceof TypeDeclaration) { - return provideHoverForTypeDeclaration(node, (TypeDeclaration) node.getParent(), offset, doc, project); + if (node instanceof SimpleName) { + ASTNode parent = node.getParent(); + if (parent instanceof TypeDeclaration) { + return provideHoverForTypeDeclaration(node, (TypeDeclaration) parent, offset, doc, project); + } else if (parent instanceof MethodDeclaration) { + return provideHoverForMethodDeclaration((MethodDeclaration) parent, offset, doc, project); + } } + return null; + } + private Hover provideHoverForMethodDeclaration(MethodDeclaration methodDeclaration, int offset, TextDocument doc, + IJavaProject project) { + SpringBootApp[] runningApps = getRunningSpringApps(project); + if (runningApps.length > 0) { + for (HoverProvider provider : this.hoverProviders.getAll()) { + Hover hover = provider.provideHover(methodDeclaration, offset, doc, project, runningApps); + if (hover!=null) { + //TODO: compose multiple hovers somehow instead of just returning the first one? + return hover; + } + } + } return null; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/HoverProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/HoverProvider.java index 4dcf3598a3..948bed6a57 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/HoverProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/HoverProvider.java @@ -15,6 +15,7 @@ import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.Range; @@ -27,10 +28,24 @@ */ public interface HoverProvider { - Hover provideHover(ASTNode node, Annotation annotation, ITypeBinding type, int offset, TextDocument doc, IJavaProject project, SpringBootApp[] runningApps); - Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset, TextDocument doc, IJavaProject project, SpringBootApp[] runningApps); + default Hover provideHover(ASTNode node, Annotation annotation, ITypeBinding type, int offset, TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) { + return null; + } + default Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset, TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) { + return null; + } + default Hover provideHover(MethodDeclaration methodDeclaration, int offset, TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) { + return null; + } - Collection getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps); - Collection getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc, SpringBootApp[] runningApps); + default Collection getLiveHoverHints(IJavaProject project, Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { + return null; + } + default Collection getLiveHoverHints(IJavaProject project,TypeDeclaration typeDeclaration, TextDocument doc, SpringBootApp[] runningApps) { + return null; + } + default Collection getLiveHoverHints(IJavaProject project, MethodDeclaration methodDeclaration, TextDocument doc, SpringBootApp[] runningApps) { + return null; + } } 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 59c7469840..13c861fc5e 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 @@ -29,7 +29,6 @@ import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBean; import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel; import org.springframework.ide.vscode.commons.java.IJavaProject; -import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.util.Log; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -44,7 +43,7 @@ public AbstractInjectedIntoHoverProvider(BootJavaLanguageServerComponents server } @Override - public Collection getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { + public Collection getLiveHoverHints(IJavaProject project, Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { // Highlight if any running app contains an instance of this component try { if (runningApps.length > 0) { @@ -89,7 +88,6 @@ public Hover provideHover(ASTNode node, Annotation annotation, ITypeBinding type for (LiveBean bean : relevantBeans) { addInjectedInto(definedBean, hover, beans, bean, project); - addAutomaticallyWiredContructor(hover, annotation, beans, bean, project); } } } @@ -103,12 +101,6 @@ public Hover provideHover(ASTNode node, Annotation annotation, ITypeBinding type protected abstract LiveBean getDefinedBean(Annotation annotation); - protected void addAutomaticallyWiredContructor(StringBuilder hover, Annotation annotation, LiveBeansModel beans, LiveBean bean, IJavaProject project) { - //This doesn't really belong here, but it accomodates Martin's additional logic to handle implicitly - //@Autowired constructor. - //This does nothing by default as its really only relevant to @Component annotation report. - } - protected void addInjectedInto(LiveBean definedBean, StringBuilder hover, LiveBeansModel beans, LiveBean bean, IJavaProject project) { hover.append("\n\n"); List dependers = beans.getBeansDependingOn(bean.getId()); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ActiveProfilesProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ActiveProfilesProvider.java index a4f674ac33..aa65941a31 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ActiveProfilesProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ActiveProfilesProvider.java @@ -22,7 +22,6 @@ import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.StringLiteral; -import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.messages.Either; @@ -81,7 +80,7 @@ public Hover provideHover( } @Override - public Collection getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { + public Collection getLiveHoverHints(IJavaProject project, Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { if (runningApps.length > 0) { Builder ranges = ImmutableList.builder(); nameRange(doc, annotation).ifPresent(ranges::add); @@ -130,16 +129,4 @@ private static Optional rangeOf(TextDocument doc, StringLiteral node) { } } - @Override - public Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset, - TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) { - return null; - } - - @Override - public Collection getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc, - SpringBootApp[] runningApps) { - return null; - } - } 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 9786a7d337..c1ac6966d8 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,24 +10,14 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.livehover; -import java.util.Collection; import java.util.Optional; -import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; -import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; -import org.eclipse.jdt.core.dom.TypeDeclaration; -import org.eclipse.lsp4j.Hover; -import org.eclipse.lsp4j.Range; import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents; 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.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.util.Optionals; -import org.springframework.ide.vscode.commons.util.text.TextDocument; public class BeanInjectedIntoHoverProvider extends AbstractInjectedIntoHoverProvider { @@ -76,16 +66,4 @@ private Optional getBeanId(Annotation annotation, MethodDeclaration bean ); } - @Override - public Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset, - TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) { - return null; - } - - @Override - public Collection getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc, - SpringBootApp[] runningApps) { - return null; - } - } 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 db255328bb..dfcbb832df 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 @@ -20,8 +20,6 @@ import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.MarkerAnnotation; -import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.Range; @@ -46,49 +44,6 @@ public ComponentInjectionsHoverProvider(BootJavaLanguageServerComponents server) super(server); } - @Override - protected void addAutomaticallyWiredContructor(StringBuilder hover, Annotation annotation, LiveBeansModel beans, LiveBean bean, IJavaProject project) { - TypeDeclaration typeDecl = ASTUtils.findDeclaringType(annotation); - if (typeDecl != null) { - MethodDeclaration[] constructors = ASTUtils.findConstructors(typeDecl); - - if (constructors != null && constructors.length == 1 && !hasAutowiredAnnotation(constructors[0])) { - String[] dependencies = bean.getDependencies(); - - if (dependencies != null && dependencies.length > 0) { - hover.append("\n\n"); - hover.append(LiveHoverUtils.showBean(bean) + " got autowired with:\n\n"); - - boolean firstDependency = true; - for (String injectedBean : dependencies) { - if (!firstDependency) { - hover.append("\n"); - } - List dependencyBeans = beans.getBeansOfName(injectedBean); - for (LiveBean dependencyBean : dependencyBeans) { - hover.append("- " + LiveHoverUtils.showBeanWithResource(server, dependencyBean, " ", project)); - } - firstDependency = false; - } - } - } - } - } - - private boolean hasAutowiredAnnotation(MethodDeclaration constructor) { - List modifiers = constructor.modifiers(); - for (Object modifier : modifiers) { - if (modifier instanceof MarkerAnnotation) { - ITypeBinding typeBinding = ((MarkerAnnotation) modifier).resolveTypeBinding(); - if (typeBinding != null) { - String fqName = typeBinding.getQualifiedName(); - return Annotations.AUTOWIRED.equals(fqName) || Annotations.INJECT.equals(fqName); - } - } - } - return false; - } - @Override protected LiveBean getDefinedBean(Annotation annotation) { return getDefinedBeanForComponent(annotation); @@ -132,7 +87,7 @@ private static String getBeanId(Annotation annotation, ITypeBinding beanType) { } @Override - public Collection getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc, + public Collection getLiveHoverHints(IJavaProject project, TypeDeclaration typeDeclaration, TextDocument doc, SpringBootApp[] runningApps) { if (runningApps.length > 0 && !isComponentAnnotatedType(typeDeclaration)) { try { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java index 01e9370c5e..2e763c506f 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java @@ -22,7 +22,6 @@ import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; -import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.MarkedString; import org.eclipse.lsp4j.Range; @@ -55,7 +54,7 @@ public Hover provideHover(ASTNode node, Annotation annotation, } @Override - public Collection getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { + public Collection getLiveHoverHints(IJavaProject project, Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { try { if (runningApps.length > 0) { List> val = getRequestMappingMethodFromRunningApp(annotation, runningApps); @@ -173,16 +172,4 @@ private void addHoverContent(List> mapping } } - @Override - public Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBinding type, int offset, - TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) { - return null; - } - - @Override - public Collection getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc, - SpringBootApp[] runningApps) { - return null; - } - } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java index c7394b8e21..567c8b87a5 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java @@ -10,7 +10,6 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.utils; -import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; @@ -128,8 +127,7 @@ public static Optional getFirstString(Expression exp) { return Optional.empty(); } - public static TypeDeclaration findDeclaringType(Annotation annotation) { - ASTNode node = annotation; + public static TypeDeclaration findDeclaringType(ASTNode node) { while (node != null && !(node instanceof TypeDeclaration)) { node = node.getParent(); } @@ -137,20 +135,21 @@ public static TypeDeclaration findDeclaringType(Annotation annotation) { return node != null ? (TypeDeclaration) node : null; } - public static MethodDeclaration[] findConstructors(TypeDeclaration typeDecl) { - List constructors = new ArrayList<>(); - + public static boolean hasExactlyOneConstructor(TypeDeclaration typeDecl) { + boolean oneFound = false; MethodDeclaration[] methods = typeDecl.getMethods(); for (MethodDeclaration methodDeclaration : methods) { if (methodDeclaration.isConstructor()) { - constructors.add(methodDeclaration); + if (oneFound) { + return false; + } else { + oneFound = true; + } } } - - return constructors.toArray(new MethodDeclaration[constructors.size()]); + return oneFound; } - public static MethodDeclaration getAnnotatedMethod(Annotation annotation) { ASTNode parent = annotation.getParent(); if (parent instanceof MethodDeclaration) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueHoverProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueHoverProvider.java index 9f104c6b48..9a29cebfee 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueHoverProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueHoverProvider.java @@ -10,7 +10,6 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.value; -import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -67,11 +66,6 @@ else if (exactNode != null && exactNode instanceof StringLiteral && exactNode.ge return null; } - @Override - public Collection getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { - return null; - } - private Hover provideHover(String value, int offset, int nodeStartOffset, TextDocument doc, SpringBootApp[] runningApps) { try { @@ -206,10 +200,4 @@ public Hover provideHover(ASTNode node, TypeDeclaration typeDeclaration, ITypeBi return null; } - @Override - public Collection getLiveHoverHints(TypeDeclaration typeDeclaration, TextDocument doc, - SpringBootApp[] runningApps) { - return null; - } - } 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 1deba0edfb..c43871115f 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 @@ -415,4 +415,165 @@ public void noHoversWhenRunningAppDoesntHaveDependenciesForTheAutowiring() throw ); } + @Test + public void implicitAutowiringSingleConstructor() 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.stereotype.Component;\n" + + "\n" + + "@Component\n" + + "public class SomeComponent {\n" + + "\n" + + " private DepedencyA depA;\n" + + " private DepedencyB depB;\n" + + "\n" + + " public SomeComponent(DependencyA depA, DependencyB depB) {\n" + + " this.depA = depA;\n" + + " this.depB = depB;\n" + + " }\n" + + "}\n" + ); + + editor.assertHighlights("@Component", "SomeComponent"); + + editor.assertTrimmedHover("SomeComponent", 2, + "**Autowired → `dependencyA` `dependencyB`**\n" + + "- Bean: `dependencyA` \n" + + " Type: `com.example.DependencyA`\n" + + "- Bean: `dependencyB` \n" + + " Type: `com.example.DependencyB`\n" + + " \n" + + "Process [PID=111, name=`the-app`]\n" + ); + } + + @Test + public void noImplicitAutowiringForConstructorFromNonBean() throws Exception { + LiveBeansModel beans = LiveBeansModel.builder() + .add(LiveBean.builder() + .id("someOtherComponent") + .type("com.example.SomeOtherComponent") + .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" + + "public class SomeComponent {\n" + + "\n" + + " private DepedencyA depA;\n" + + " private DepedencyB depB;\n" + + "\n" + + " public SomeComponent(DependencyA depA, DependencyB depB) {\n" + + " this.depA = depA;\n" + + " this.depB = depB;\n" + + " }\n" + + "}\n" + ); + + editor.assertHighlights(); + + for (int i = 1; i < 2; i++) { + editor.assertNoHover("SomeComponent", i); + } + } + + @Test + public void noImplicitAutowiringForMultipleConstructors() 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.stereotype.Component;\n" + + "\n" + + "@Component\n" + + "public class SomeComponent {\n" + + "\n" + + " private DepedencyA depA;\n" + + " private DepedencyB depB;\n" + + "\n" + + " public SomeComponent() {\n" + + " }\n" + + "\n" + + " public SomeComponent(DependencyA depA, DependencyB depB) {\n" + + " this.depA = depA;\n" + + " this.depB = depB;\n" + + " }\n" + + "}\n" + ); + + editor.assertHighlights("@Component"); + for (int i = 1; i < 3; i++) { + editor.assertNoHover("SomeComponent", i); + } + } + } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/ComponentInjectionsHoverProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/ComponentInjectionsHoverProviderTest.java index cef834e182..1301aa66b1 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/ComponentInjectionsHoverProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/livehover/test/ComponentInjectionsHoverProviderTest.java @@ -516,22 +516,14 @@ public void componentWithAutomaticallyWiredConstructorInjections() throws Except " }\n" + "}\n" ); - editor.assertHighlights("@Component"); + editor.assertHighlights("@Component", "AutowiredClass"); editor.assertTrimmedHover("@Component", "**Injection report for Bean [id: autowiredClass, type: `com.example.AutowiredClass`]**\n" + "\n" + "Process [PID=111, name=`the-app`]:\n" + "\n" + "Bean [id: autowiredClass, type: `com.example.AutowiredClass`] exists but is **Not injected anywhere**\n" + - "\n\n" + - "Bean [id: autowiredClass, type: `com.example.AutowiredClass`] got autowired with:\n" + - "\n" + - "- Bean: `dependencyA` \n" + - " Type: `com.example.DependencyA` \n" + - " Resource: `" + Paths.get("com/example/DependencyA.class") + "`\n" + - "- Bean: `dependencyB` \n" + - " Type: `com.example.DependencyB` \n" + - " Resource: `com/example/DependencyB.class`" + "\n\n" ); }