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 3f293b3fc..8bb2b3757 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 @@ -71,32 +71,30 @@ public class AutowiredHoverProvider implements HoverProvider { } @Override - public Collection getLiveHintCodeLenses(IJavaProject project, Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { + public Collection getLiveHintCodeLenses(IJavaProject project, Annotation annotation, TextDocument doc, + SpringBootApp[] runningApps) { + ImmutableList.Builder builder = ImmutableList.builder(); if (runningApps.length > 0) { LiveBean definedBean = getDefinedBeanForTypeDeclaration(ASTUtils.findDeclaringType(annotation)); - // Annotation is MarkerNode, parent is some field, method, variable declaration node. + // Annotation is MarkerNode, parent is some field, method, variable declaration + // node. ASTNode declarationNode = annotation.getParent(); try { Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength()); - return getLiveHoverHints(project, declarationNode, hoverRange, runningApps, definedBean); + for (SpringBootApp app : runningApps) { + List relevantBeans = getRelevantAutowiredBeans(project, declarationNode, app, + definedBean); + if (!relevantBeans.isEmpty()) { + builder.addAll(LiveHoverUtils.createCodeLensesForBeans(hoverRange, relevantBeans, + BEANS_PREFIX_PLAIN_TEXT, MAX_INLINE_BEANS_STRING_LENGTH, + INLINE_BEANS_STRING_SEPARATOR)); + } + } } 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 LiveHoverUtils.createCodeLensesForBeans(range, relevantBeans, BEANS_PREFIX_PLAIN_TEXT, MAX_INLINE_BEANS_STRING_LENGTH, INLINE_BEANS_STRING_SEPARATOR); - } - } - } - return null; + return builder.build(); } @Override @@ -125,16 +123,12 @@ public class AutowiredHoverProvider implements HoverProvider { StringBuilder hover = new StringBuilder(); - boolean hasContent = false; - for (SpringBootApp app : runningApps) { List autowiredBeans = getRelevantAutowiredBeans(project, declarationNode, app, definedBean); if (!autowiredBeans.isEmpty()) { - if (!hasContent) { - hasContent = true; - } else { + if (hover.length() > 0) { hover.append(" \n \n"); } createHoverContentForBeans(sourceLinks, project, hover, autowiredBeans); @@ -145,7 +139,7 @@ public class AutowiredHoverProvider implements HoverProvider { } } - if (hasContent) { + if (hover.length() > 0) { return new Hover(ImmutableList.of(Either.forLeft(hover.toString()))); } } @@ -189,7 +183,7 @@ public class AutowiredHoverProvider implements HoverProvider { } @SuppressWarnings("unchecked") - private static List findAutowiredBeans(IJavaProject project, ASTNode declarationNode, Collection beans) { + public static List findAutowiredBeans(IJavaProject project, ASTNode declarationNode, Collection beans) { if (declarationNode instanceof MethodDeclaration) { MethodDeclaration methodDeclaration = (MethodDeclaration)declarationNode; return ((List)methodDeclaration.parameters()).stream() @@ -325,16 +319,54 @@ public class AutowiredHoverProvider implements HoverProvider { } @Override - public Collection getLiveHintCodeLenses(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); + public Hover provideMethodParameterHover(SingleVariableDeclaration parameter, int offset, TextDocument doc, + IJavaProject project, SpringBootApp[] runningApps) { + MethodDeclaration method = (MethodDeclaration) parameter.getParent(); + LiveBean definedBean = getDefinedBeanForImplicitAutowiredConstructor(method); + Hover hover = provideHover(definedBean, parameter, offset, doc, project, runningApps); + if (hover != null) { + SimpleName name = parameter.getName(); + try { + hover.setRange(doc.toRange(name.getStartPosition(), name.getLength())); + } catch (BadLocationException e) { + log.error("", e); + } } - return null; + return hover; + } + + @Override + public Collection getLiveHintCodeLenses(IJavaProject project, MethodDeclaration methodDeclaration, + TextDocument doc, SpringBootApp[] runningApps) { + ImmutableList.Builder builder = ImmutableList.builder(); + LiveBean definedBean = getDefinedBeanForImplicitAutowiredConstructor(methodDeclaration); + if (definedBean != null) { + try { + Range hoverRange = doc.toRange(methodDeclaration.getName().getStartPosition(), + methodDeclaration.getName().getLength()); + + for (SpringBootApp app : runningApps) { + List relevantBeans = getRelevantAutowiredBeans(project, methodDeclaration, app, + definedBean); + if (!relevantBeans.isEmpty()) { + + // CodeLens for the method + builder.addAll(LiveHoverUtils.createCodeLensesForBeans(hoverRange, relevantBeans, + BEANS_PREFIX_PLAIN_TEXT, MAX_INLINE_BEANS_STRING_LENGTH, + INLINE_BEANS_STRING_SEPARATOR)); + + // CodeLenses for the method parameters. Only ranges just to provide a highlight + // for the hover + builder.addAll(LiveHoverUtils.createCodeLensForMethodParameters(app, project, methodDeclaration, doc, relevantBeans)); + + } + } + } catch (BadLocationException e) { + log.error("", e); + } + + } + return builder.build(); } private LiveBean getDefinedBeanForImplicitAutowiredConstructor(MethodDeclaration methodDeclaration) { 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 584c451bc..8acf4c699 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 @@ -10,6 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.handlers; +import java.net.URI; import java.util.Collection; import java.util.LinkedHashSet; import java.util.Optional; @@ -24,6 +25,7 @@ import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.core.dom.NormalAnnotation; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.Hover; @@ -101,7 +103,7 @@ public class BootJavaHoverProvider implements HoverHandler { if (!project.isPresent()) return new CodeLens[0]; if (!hasActuatorDependency(project.get())) return new CodeLens[0]; - return server.getCompilationUnitCache().withCompilationUnit(document, cu -> { + return server.getCompilationUnitCache().withCompilationUnit(project.get(), URI.create(document.getUri()), cu -> { Collection result = new LinkedHashSet<>(); try { if (cu != null) { @@ -210,7 +212,7 @@ public class BootJavaHoverProvider implements HoverHandler { private Hover provideHover(TextDocument document, int offset) throws Exception { IJavaProject project = getProject(document).orElse(null); if (project != null) { - return server.getCompilationUnitCache().withCompilationUnit(document, cu -> { + return server.getCompilationUnitCache().withCompilationUnit(project, URI.create(document.getUri()), cu -> { ASTNode node = NodeFinder.perform(cu, offset, 0); if (node != null) { return provideHover(node, offset, document, project); @@ -239,6 +241,23 @@ public class BootJavaHoverProvider implements HoverHandler { return provideHoverForTypeDeclaration(node, (TypeDeclaration) parent, offset, doc, project); } else if (parent instanceof MethodDeclaration) { return provideHoverForMethodDeclaration((MethodDeclaration) parent, offset, doc, project); + } else if (parent instanceof SingleVariableDeclaration && parent.getParent() instanceof MethodDeclaration) { + return provideHoverForMethodParameter((SingleVariableDeclaration) parent, offset, doc, project); + } + } + return null; + } + + private Hover provideHoverForMethodParameter(SingleVariableDeclaration parameter, int offset, TextDocument doc, + IJavaProject project) { + SpringBootApp[] runningApps = getRunningSpringApps(project); + + if (runningApps.length > 0) { + for (HoverProvider provider : this.hoverProviders.getAll()) { + Hover hover = provider.provideMethodParameterHover(parameter, offset, doc, project, runningApps); + if (hover != null) { + 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 2bbd04600..5b9b9b25c 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 @@ -16,6 +16,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.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.Hover; @@ -31,13 +32,19 @@ public interface HoverProvider { 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; } + default Hover provideMethodParameterHover(SingleVariableDeclaration parameter, int offset, TextDocument doc, IJavaProject project, SpringBootApp[] runningApps) { + return null; + } + default Collection getLiveHintCodeLenses(IJavaProject project, Annotation annotation, 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 a5bab7e44..f272b7e11 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 @@ -10,7 +10,6 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.livehover; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -65,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(), annotation); + List codeLenses = assembleCodeLenses(project, runningApps, definedBean, doc, nameRange.get(), annotation); return codeLenses.isEmpty() ? ImmutableList.of(new CodeLens(nameRange.get())) : codeLenses; } } @@ -97,31 +96,38 @@ public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider return null; } - protected List assembleCodeLenses(IJavaProject project, SpringBootApp[] runningApps, LiveBean definedBean, Range range, ASTNode astNode) { - List codeLensList = new ArrayList<>(); - for (SpringBootApp app : runningApps) { + protected List assembleCodeLenses(IJavaProject project, SpringBootApp[] runningApps, LiveBean definedBean, + TextDocument doc, Range range, ASTNode node) { + for (SpringBootApp app : runningApps) { - List relevantBeans = LiveHoverUtils.findRelevantBeans(app, definedBean); + List relevantBeans = LiveHoverUtils.findRelevantBeans(app, definedBean); - if (!relevantBeans.isEmpty()) { - List injectedBeans = getRelevantInjectedIntoBeans(project, app, definedBean, relevantBeans); - ImmutableList.Builder builder = ImmutableList.builder(); - 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_PLAIN_TEXT, MAX_INLINE_BEANS_STRING_LENGTH, INLINE_BEANS_STRING_SEPARATOR); - 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_PLAIN_TEXT, MAX_INLINE_BEANS_STRING_LENGTH, - INLINE_BEANS_STRING_SEPARATOR)); - - return builder.build(); + if (!relevantBeans.isEmpty()) { + List injectedBeans = getRelevantInjectedIntoBeans(project, app, definedBean, relevantBeans); + ImmutableList.Builder builder = ImmutableList.builder(); + 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_PLAIN_TEXT, MAX_INLINE_BEANS_STRING_LENGTH, INLINE_BEANS_STRING_SEPARATOR); + builder.addAll( + injectedCodeLenses.isEmpty() ? ImmutableList.of(new CodeLens(range)) : injectedCodeLenses); } + + // Wired beans code lenses + List wiredBeans = findWiredBeans(project, app, relevantBeans, node); + builder.addAll(assembleCodeLenseForAutowired(wiredBeans, project, app, doc, range, node)); + + List codeLenses = builder.build(); + return codeLenses.isEmpty() ? ImmutableList.of(new CodeLens(range)) : codeLenses; + } } - return codeLensList; + return ImmutableList.of(); + } + + protected List assembleCodeLenseForAutowired(List wiredBeans, IJavaProject project, SpringBootApp app, TextDocument doc, Range nameRange, ASTNode astNode) { + return LiveHoverUtils.createCodeLensesForBeans(nameRange, wiredBeans, + AutowiredHoverProvider.BEANS_PREFIX_PLAIN_TEXT, MAX_INLINE_BEANS_STRING_LENGTH, + INLINE_BEANS_STRING_SEPARATOR); } protected List findWiredBeans(IJavaProject project, SpringBootApp app, List relevantBeans, ASTNode astNode) { @@ -131,8 +137,6 @@ public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider protected Hover assembleHover(IJavaProject project, SpringBootApp[] runningApps, LiveBean definedBean, ASTNode astNode) { StringBuilder hover = new StringBuilder(); - boolean hasContent = false; - for (SpringBootApp app : runningApps) { List relevantBeans = LiveHoverUtils.findRelevantBeans(app, definedBean); @@ -140,9 +144,7 @@ public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider if (!relevantBeans.isEmpty()) { List injectedBeans = getRelevantInjectedIntoBeans(project, app, definedBean, relevantBeans); - if (!hasContent) { - hasContent = true; - } else { + if (hover.length() > 0) { hover.append(" \n \n"); } @@ -167,7 +169,7 @@ public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider } } - if (hasContent) { + if (hover.length() > 0) { return new Hover(ImmutableList.of(Either.forLeft(hover.toString()))); } else { 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 c13ab5b42..6bfece4cd 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 @@ -16,7 +16,16 @@ 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.SingleVariableDeclaration; +import org.eclipse.lsp4j.CodeLens; +import org.eclipse.lsp4j.Hover; +import org.eclipse.lsp4j.Range; +import org.gradle.internal.impldep.com.google.common.collect.ImmutableList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.boot.java.autowired.AutowiredHoverProvider; import org.springframework.ide.vscode.boot.java.links.SourceLinks; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; @@ -24,9 +33,12 @@ 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; +import org.springframework.ide.vscode.commons.util.text.TextDocument; public class BeanInjectedIntoHoverProvider extends AbstractInjectedIntoHoverProvider { + private static final Logger log = LoggerFactory.getLogger(BeanInjectedIntoHoverProvider.class); + public BeanInjectedIntoHoverProvider(SourceLinks sourceLinks) { super(sourceLinks); } @@ -75,12 +87,79 @@ public class BeanInjectedIntoHoverProvider extends AbstractInjectedIntoHoverProv @Override protected List findWiredBeans(IJavaProject project, SpringBootApp app, List relevantBeans, ASTNode astNode) { if (astNode instanceof Annotation) { + // @Bean annotation case MethodDeclaration beanMethod = ASTUtils.getAnnotatedMethod((Annotation) astNode); if (beanMethod != null) { return AutowiredHoverProvider.getRelevantAutowiredBeans(project, beanMethod, app, relevantBeans); } + } else if (astNode instanceof SingleVariableDeclaration) { + // Bean method parameter case + return AutowiredHoverProvider.getRelevantAutowiredBeans(project, astNode, app, relevantBeans); } return Collections.emptyList(); } + @Override + protected List assembleCodeLenseForAutowired(List wiredBeans, IJavaProject project, + SpringBootApp app, TextDocument doc, Range nameRange, ASTNode astNode) { + ImmutableList.Builder builder = ImmutableList.builder(); + + // Code lens for the @Bean annotation + builder.addAll(super.assembleCodeLenseForAutowired(wiredBeans, project, app, doc, nameRange, astNode)); + + if (astNode instanceof Annotation) { + // Add code lenses for method parameters + MethodDeclaration beanMethod = ASTUtils.getAnnotatedMethod((Annotation) astNode); + if (beanMethod != null) { + builder.addAll(LiveHoverUtils.createCodeLensForMethodParameters(app, project, beanMethod, doc, wiredBeans)); + } + } + + return builder.build(); + } + + @Override + public Hover provideMethodParameterHover(SingleVariableDeclaration parameter, int offset, TextDocument doc, + IJavaProject project, SpringBootApp[] runningApps) { + try { + if (runningApps.length > 0) { + Range range = ASTUtils.nodeRegion(doc, parameter.getName()).asRange(); + MethodDeclaration method = (MethodDeclaration) parameter.getParent(); + Annotation beanAnnotation = getBeanAnnotation(method); + if (beanAnnotation != null) { + LiveBean definedBean = getDefinedBean(beanAnnotation); + if (definedBean != null) { + Hover hover = assembleHover(project, runningApps, definedBean, parameter); + if (hover != null) { + hover.setRange(range); + } + return hover; + } + + } + } + } catch (Exception e) { + log.error("", e); + } + return null; + } + + private static Annotation getBeanAnnotation(MethodDeclaration method) { + List modifiers = method.modifiers(); + for (Object modifier : modifiers) { + if (modifier instanceof Annotation) { + Annotation annotation = (Annotation) modifier; + ITypeBinding typeBinding = annotation.resolveTypeBinding(); + if (typeBinding != null) { + String fqName = typeBinding.getQualifiedName(); + if (Annotations.BEAN.equals(fqName)) { + return annotation; + } + } + } + } + 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 76d555d63..55d041e38 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 @@ -111,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(), typeDeclaration); + List codeLenses = assembleCodeLenses(project, runningApps, definedBean, doc, nameRange.get(), typeDeclaration); return codeLenses.isEmpty() ? ImmutableList.of(new CodeLens(nameRange.get())) : codeLenses; } } @@ -146,8 +146,6 @@ public class ComponentInjectionsHoverProvider extends AbstractInjectedIntoHoverP return null; } - - @Override protected List findWiredBeans(IJavaProject project, SpringBootApp app, List relevantBeans, ASTNode astNode) { 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 75c0ba6e3..9873ee7de 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 @@ -17,22 +17,32 @@ import java.util.List; import java.util.Optional; import java.util.stream.Collectors; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.Range; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.boot.java.autowired.AutowiredHoverProvider; import org.springframework.ide.vscode.boot.java.links.SourceLinks; +import org.springframework.ide.vscode.boot.java.utils.ASTUtils; import org.springframework.ide.vscode.boot.java.utils.SpringResource; 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.boot.app.cli.livebean.LiveBeansModel; import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.Renderables; import org.springframework.ide.vscode.commons.util.StringUtil; +import org.springframework.ide.vscode.commons.util.text.TextDocument; import com.google.common.collect.ImmutableList; public class LiveHoverUtils { + private static final Logger log = LoggerFactory.getLogger(LiveHoverUtils.class); + public static final LiveBean CANT_MATCH_PROPER_BEAN = LiveBean.builder().id("UNKNOWN").build(); public static String showBean(LiveBean bean) { @@ -161,7 +171,7 @@ public class LiveHoverUtils { try { return niceAppName(app.getProcessID(), app.getProcessName()); } catch (Exception e) { - e.printStackTrace(); + log.error("", e); return app.toString(); } } @@ -189,6 +199,27 @@ public class LiveHoverUtils { } + @SuppressWarnings("unchecked") + public static List createCodeLensForMethodParameters(SpringBootApp app, IJavaProject project, MethodDeclaration method, TextDocument doc, List wiredBeans) { + ImmutableList.Builder builder = ImmutableList.builder(); + method.parameters().forEach(p -> { + if (p instanceof SingleVariableDeclaration) { + SingleVariableDeclaration parameter = (SingleVariableDeclaration) p; + List parameterMatchingBean = AutowiredHoverProvider.findAutowiredBeans(project, parameter, wiredBeans); + if (parameterMatchingBean.size() == 0) { + log.warn("No Live Bean matching parameter `" + parameter.getName().getIdentifier() + " for method " + method); + } else { + try { + builder.add(new CodeLens(ASTUtils.nodeRegion(doc, parameter.getName()).asRange())); + } catch (BadLocationException e) { + // ignore + } + } + } + }); + return builder.build(); + } + public static StringBuilder createBeansTitlePlainText(Collection beans, String prefix, int maxInlineBeansStringLength, String beansSeparator) { StringBuilder sb = new StringBuilder(prefix); if (LiveHoverUtils.doBeansFitInline(beans, maxInlineBeansStringLength, beansSeparator)) { 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 8f5d18559..bff49b62b 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 @@ -507,7 +507,7 @@ public class AutowiredHoverProviderTest { "}\n" ); - editor.assertHighlights("@Component", "SomeComponent"); + editor.assertHighlights("@Component", "SomeComponent", "depA", "depB"); editor.assertTrimmedHover("SomeComponent", 2, "**← `DependencyA` `DependencyB`**\n" + @@ -519,6 +519,25 @@ public class AutowiredHoverProviderTest { "Bean id: `someComponent` \n" + "Process [PID=111, name=`the-app`]\n" ); + + editor.assertTrimmedHover("depA", 2, + "**← `DependencyA`**\n" + + "- Bean: `dependencyA` \n" + + " Type: `com.example.DependencyA`\n" + + " \n" + + "Bean id: `someComponent` \n" + + "Process [PID=111, name=`the-app`]\n" + ); + + editor.assertTrimmedHover("depB", 2, + "**← `DependencyB`**\n" + + "- Bean: `dependencyB` \n" + + " Type: `com.example.DependencyB`\n" + + " \n" + + "Bean id: `someComponent` \n" + + "Process [PID=111, name=`the-app`]\n" + ); + } @Test @@ -718,7 +737,7 @@ public class AutowiredHoverProviderTest { " private IDependency a;\n" + " private IDependency b;\n" + "\n" + - " public SomeComponent(@Qualifier(\"dependencyA\") IDependency a, @Qualifier(\"dependencyB\") IDependency b) {\n" + + " public SomeComponent(@Qualifier(\"dependencyA\") IDependency depA, @Qualifier(\"dependencyB\") IDependency depB) {\n" + " this.a = a;\n" + " this.b = b;\n" + " }\n" + @@ -726,7 +745,7 @@ public class AutowiredHoverProviderTest { "}\n" ); - editor.assertHighlights("@Component", "SomeComponent"); + editor.assertHighlights("@Component", "SomeComponent", "depA", "depB"); editor.assertTrimmedHover("SomeComponent", 2, "**← `DependencyA` `DependencyB`**\n" + "- Bean: `dependencyA` \n" + @@ -737,6 +756,25 @@ public class AutowiredHoverProviderTest { "Bean id: `someComponent` \n" + "Process [PID=111, name=`the-app`]\n" ); + + editor.assertTrimmedHover("depA", + "**← `DependencyA`**\n" + + "- Bean: `dependencyA` \n" + + " Type: `com.example.DependencyA`\n" + + " \n" + + "Bean id: `someComponent` \n" + + "Process [PID=111, name=`the-app`]\n" + ); + + editor.assertTrimmedHover("depB", + "**← `DependencyB`**\n" + + "- Bean: `dependencyB` \n" + + " Type: `com.example.DependencyB`\n" + + " \n" + + "Bean id: `someComponent` \n" + + "Process [PID=111, name=`the-app`]\n" + ); + } @Test 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 60da3571c..15bbfb07c 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 @@ -549,12 +549,12 @@ public class BeanInjectedIntoHoverProviderTest { "public class LocalConfig {\n" + " \n" + " @Bean(\"fooImplementation\")\n" + - " Foo someFoo(DependencyA a) {\n" + + " Foo someFoo(DependencyA depA) {\n" + " return new FooImplementation();\n" + " }\n" + "}" ); - editor.assertHighlights("@Bean"); + editor.assertHighlights("@Bean", "depA"); editor.assertTrimmedHover("@Bean", "**← `DependencyA`**\n" + "- Bean: `depA` \n" + @@ -563,6 +563,16 @@ public class BeanInjectedIntoHoverProviderTest { "Bean id: `fooImplementation` \n" + "Process [PID=111, name=`the-app`]" ); + + editor.assertTrimmedHover("depA", + "**← `DependencyA`**\n" + + "- Bean: `depA` \n" + + " Type: `hello.DependencyA`\n" + + " \n" + + "Bean id: `fooImplementation` \n" + + "Process [PID=111, name=`the-app`]" + ); + } @Test @@ -603,12 +613,12 @@ public class BeanInjectedIntoHoverProviderTest { "public class LocalConfig {\n" + " \n" + " @Bean(\"fooImplementation\")\n" + - " Foo someFoo(DependencyA a, DependencyB b) {\n" + + " Foo someFoo(DependencyA depA, DependencyB depB) {\n" + " return new FooImplementation();\n" + " }\n" + "}" ); - editor.assertHighlights("@Bean"); + editor.assertHighlights("@Bean", "depA", "depB"); editor.assertTrimmedHover("@Bean", "**← `DependencyA` `DependencyB`**\n" + "- Bean: `depA` \n" + @@ -619,6 +629,25 @@ public class BeanInjectedIntoHoverProviderTest { "Bean id: `fooImplementation` \n" + "Process [PID=111, name=`the-app`]" ); + + editor.assertTrimmedHover("depA", + "**← `DependencyA`**\n" + + "- Bean: `depA` \n" + + " Type: `hello.DependencyA`\n" + + " \n" + + "Bean id: `fooImplementation` \n" + + "Process [PID=111, name=`the-app`]" + ); + + editor.assertTrimmedHover("depB", + "**← `DependencyB`**\n" + + "- Bean: `depB` \n" + + " Type: `hello.DependencyB`\n" + + " \n" + + "Bean id: `fooImplementation` \n" + + "Process [PID=111, name=`the-app`]" + ); + } @Test @@ -664,7 +693,7 @@ public class BeanInjectedIntoHoverProviderTest { " }\n" + "}" ); - editor.assertHighlights("@Bean"); + editor.assertHighlights("@Bean", "deps"); editor.assertTrimmedHover("@Bean", "**← `DependencyA` `DependencyB`**\n" + "- Bean: `depA` \n" + @@ -675,7 +704,18 @@ public class BeanInjectedIntoHoverProviderTest { "Bean id: `fooImplementation` \n" + "Process [PID=111, name=`the-app`]" ); - } + + editor.assertTrimmedHover("deps", + "**← `DependencyA` `DependencyB`**\n" + + "- Bean: `depA` \n" + + " Type: `hello.DependencyA`\n" + + "- Bean: `depB` \n" + + " Type: `hello.DependencyB`\n" + + " \n" + + "Bean id: `fooImplementation` \n" + + "Process [PID=111, name=`the-app`]" + ); +} @Test public void beanWithQualifierWiring() throws Exception { @@ -715,7 +755,7 @@ public class BeanInjectedIntoHoverProviderTest { " }\n" + "}" ); - editor.assertHighlights("@Bean"); + editor.assertHighlights("@Bean", "deps"); editor.assertTrimmedHover("@Bean", "**← `DependencyB`**\n" + "- Bean: `depB` \n" + @@ -724,5 +764,15 @@ public class BeanInjectedIntoHoverProviderTest { "Bean id: `fooImplementation` \n" + "Process [PID=111, name=`the-app`]" ); + + editor.assertTrimmedHover("deps", + "**← `DependencyB`**\n" + + "- Bean: `depB` \n" + + " Type: `hello.DependencyB`\n" + + " \n" + + "Bean id: `fooImplementation` \n" + + "Process [PID=111, name=`the-app`]" + ); + } } 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 8243defbb..6feea840e 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 @@ -501,7 +501,7 @@ public class ComponentInjectionsHoverProviderTest { " }\n" + "}\n" ); - editor.assertHighlights("@Component", "AutowiredClass"); + editor.assertHighlights("@Component", "AutowiredClass", "depA", "depB"); editor.assertTrimmedHover("@Component", "**← `DependencyA` `DependencyB`**\n" + "- Bean: `dependencyA` \n" +