diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java index d793a6dd7..6d2b72a0d 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java @@ -20,7 +20,7 @@ import org.eclipse.lsp4j.InitializeResult; import org.springframework.ide.vscode.boot.java.autowired.AutowiredHoverProvider; import org.springframework.ide.vscode.boot.java.beans.BeansSymbolProvider; import org.springframework.ide.vscode.boot.java.beans.ComponentSymbolProvider; -import org.springframework.ide.vscode.boot.java.beans.ComponentInjectedIntoHoverProvider; +import org.springframework.ide.vscode.boot.java.beans.ComponentInjectionsHoverProvider; import org.springframework.ide.vscode.boot.java.conditionals.ConditionalsLiveHoverProvider; import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeLensEngine; import org.springframework.ide.vscode.boot.java.handlers.BootJavaCompletionEngine; @@ -261,7 +261,7 @@ public class BootJavaLanguageServer extends SimpleLanguageServer { providers.put(ActiveProfilesProvider.ANNOTATION, new ActiveProfilesProvider()); providers.put(org.springframework.ide.vscode.boot.java.autowired.Constants.SPRING_AUTOWIRED, new AutowiredHoverProvider()); - providers.put(org.springframework.ide.vscode.boot.java.beans.Constants.SPRING_COMPONENT, new ComponentInjectedIntoHoverProvider()); + providers.put(org.springframework.ide.vscode.boot.java.beans.Constants.SPRING_COMPONENT, new ComponentInjectionsHoverProvider()); providers.put(org.springframework.ide.vscode.boot.java.conditionals.Constants.CONDITIONAL, new ConditionalsLiveHoverProvider()); providers.put(org.springframework.ide.vscode.boot.java.conditionals.Constants.CONDITIONAL_ON_BEAN, new ConditionalsLiveHoverProvider()); diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentInjectedIntoHoverProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentInjectedIntoHoverProvider.java deleted file mode 100644 index 56cfceda5..000000000 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentInjectedIntoHoverProvider.java +++ /dev/null @@ -1,158 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Pivotal, Inc. - initial API and implementation - *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.beans; - -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -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.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.handlers.HoverProvider; -import org.springframework.ide.vscode.boot.java.livehover.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.boot.app.cli.livebean.LiveBeansModel; -import org.springframework.ide.vscode.commons.util.Log; -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 ComponentInjectedIntoHoverProvider implements HoverProvider { - - @Override - public Collection getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { - // Highlight if any running app contains an instance of this component - try { - if (runningApps.length > 0) { - LiveBean definedBean = getDefinedBean(annotation); - if (definedBean != null) { - if (Stream.of(runningApps).anyMatch(app -> hasRelevantBeans(app, definedBean))) { - Optional nameRange = ASTUtils.nameRange(doc, annotation); - if (nameRange.isPresent()) { - return ImmutableList.of(nameRange.get()); - } - } - } - } - } catch (Exception e) { - Log.log(e); - } - return ImmutableList.of(); - } - - private boolean hasRelevantBeans(SpringBootApp app, LiveBean definedBean) { - return findRelevantBeans(app, definedBean).findAny().isPresent(); - } - - private Stream findRelevantBeans(SpringBootApp app, LiveBean definedBean) { - return app.getBeans().getBeansOfName(definedBean.getId()).stream() - .filter(bean -> definedBean.getType().equals(bean.getType())); - } - - private LiveBean getDefinedBean(Annotation annotation) { - ITypeBinding beanType = getAnnotatedType(annotation); - if (beanType != null) { - String id = getBeanId(annotation, beanType); - if (StringUtil.hasText(id)) { - return LiveBean.builder().id(id).type(beanType.getQualifiedName()).build(); - } - } - return null; - } - - @Override - public CompletableFuture provideHover(ASTNode node, Annotation annotation, ITypeBinding type, int offset, - TextDocument doc, SpringBootApp[] runningApps) { - if (runningApps.length > 0) { - LiveBean definedBean = getDefinedBean(annotation); - if (definedBean != null) { - StringBuilder hover = new StringBuilder(); - hover.append("**Injection report for " + showBean(definedBean) + "**\n\n"); - boolean hasInterestingApp = false; - for (SpringBootApp app : runningApps) { - LiveBeansModel beans = app.getBeans(); - List relevantBeans = findRelevantBeans(app, definedBean).collect(Collectors.toList()); - if (!relevantBeans.isEmpty()) { - if (!hasInterestingApp) { - hasInterestingApp = true; - } else { - hover.append("\n\n"); - } - hover.append(niceAppName(app) + ":"); - for (LiveBean bean : relevantBeans) { - hover.append("\n\n"); - List dependers = beans.getBeansDependingOn(bean.getId()); - if (dependers.isEmpty()) { - hover.append(showBean(bean) + " exists but is **Not injected anywhere**"); - } else { - hover.append(showBean(definedBean) + " injected into:\n\n"); - boolean firstDependency = true; - for (LiveBean dependingBean : dependers) { - if (!firstDependency) { - hover.append("\n"); - } - hover.append("- " + showBean(dependingBean)); - firstDependency = false; - } - } - } - } - } - if (hasInterestingApp) { - System.out.println(hover); - return CompletableFuture - .completedFuture(new Hover(ImmutableList.of(Either.forLeft(hover.toString())))); - } - } - } - return null; - } - - private String getBeanId(Annotation annotation, ITypeBinding beanType) { - Optional explicitId = ASTUtils.getValueAttribute(annotation); - if (explicitId.isPresent()) { - return explicitId.get(); - } - String typeName = beanType.getName(); - if (StringUtil.hasText(typeName)) { - return Character.toLowerCase(typeName.charAt(0)) + typeName.substring(1); - } - return null; - } - - private String showBean(LiveBean bean) { - return "Bean [id: " + bean.getId() + ", type: `" + bean.getType() + "`]"; - } - - private ITypeBinding getAnnotatedType(Annotation annotation) { - ASTNode parent = annotation.getParent(); - if (parent instanceof TypeDeclaration) { - TypeDeclaration typeDecl = (TypeDeclaration) parent; - return typeDecl.resolveBinding(); - } - return null; - } - - private String niceAppName(SpringBootApp app) { - return "Process [PID=" + app.getProcessID() + ", name=`" + app.getProcessName() + "`]"; - } - -} diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentInjectionsHoverProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentInjectionsHoverProvider.java index 01b395e77..c8047ae58 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentInjectionsHoverProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentInjectionsHoverProvider.java @@ -10,10 +10,12 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans; -import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; @@ -22,18 +24,16 @@ 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.MarkedString; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.springframework.ide.vscode.boot.java.autowired.Constants; -import org.springframework.ide.vscode.boot.java.autowired.SpringBootAppProvider; -import org.springframework.ide.vscode.boot.java.autowired.SpringBootAppProviderImpl; import org.springframework.ide.vscode.boot.java.handlers.HoverProvider; +import org.springframework.ide.vscode.boot.java.livehover.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.boot.app.cli.livebean.LiveBeansModel; -import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.Log; +import org.springframework.ide.vscode.commons.util.StringUtil; import org.springframework.ide.vscode.commons.util.text.TextDocument; import com.google.common.collect.ImmutableList; @@ -41,106 +41,159 @@ import com.google.common.collect.ImmutableList; public class ComponentInjectionsHoverProvider implements HoverProvider { @Override - public CompletableFuture provideHover(ASTNode node, Annotation annotation, - ITypeBinding type, int offset, TextDocument doc, SpringBootApp[] runningApps) { - SpringBootAppProvider[] bootApps = new SpringBootAppProvider[runningApps.length]; - for (int i = 0; i < runningApps.length; i++) { - bootApps[i] = new SpringBootAppProviderImpl(runningApps[i]); - } - return provideHover(node, annotation, type, offset, doc, bootApps); - } - - public CompletableFuture provideHover(ASTNode node, Annotation annotation, - ITypeBinding type, int offset, TextDocument doc, SpringBootAppProvider[] runningApps) { + public Collection getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { + // Highlight if any running app contains an instance of this component try { - Range range = null; - - TypeDeclaration typeDecl = findDeclaredType(annotation); - if (typeDecl != null) { - MethodDeclaration constructor = findConstructor(typeDecl); - - if (constructor != null && !hasAutowiredAnnotation(constructor)) { - range = doc.toRange(constructor.getName().getStartPosition(), constructor.getName().getLength()); - - List> hoverContent = new ArrayList<>(); - for (SpringBootAppProvider bootApp : runningApps) { - try { - LiveBeansModel liveBeans = bootApp.getBeans(); - if (liveBeans != null && !liveBeans.isEmpty()) { - addLiveHoverContent(typeDecl, doc, liveBeans, bootApp, hoverContent); - } + if (runningApps.length > 0) { + LiveBean definedBean = getDefinedBean(annotation); + if (definedBean != null) { + if (Stream.of(runningApps).anyMatch(app -> hasRelevantBeans(app, definedBean))) { + Optional nameRange = ASTUtils.nameRange(doc, annotation); + if (nameRange.isPresent()) { + return ImmutableList.of(nameRange.get()); } - catch (Exception e) { - e.printStackTrace(); - } - } - - if (hoverContent.size() > 0) { - Hover hover = new Hover(); - - hover.setContents(hoverContent); - hover.setRange(range); - - return CompletableFuture.completedFuture(hover); } } } - } catch (Exception e) { Log.log(e); } + return ImmutableList.of(); + } + private boolean hasRelevantBeans(SpringBootApp app, LiveBean definedBean) { + return findRelevantBeans(app, definedBean).findAny().isPresent(); + } + + private Stream findRelevantBeans(SpringBootApp app, LiveBean definedBean) { + return app.getBeans().getBeansOfName(definedBean.getId()).stream() + .filter(bean -> definedBean.getType().equals(bean.getType())); + } + + private LiveBean getDefinedBean(Annotation annotation) { + ITypeBinding beanType = getAnnotatedType(annotation); + if (beanType != null) { + String id = getBeanId(annotation, beanType); + if (StringUtil.hasText(id)) { + return LiveBean.builder().id(id).type(beanType.getQualifiedName()).build(); + } + } return null; } @Override - public Collection getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { - try { - for (SpringBootApp bootApp : runningApps) { - try { - LiveBeansModel liveBeans = bootApp.getBeans(); - if (liveBeans != null && !liveBeans.isEmpty()) { - Range range = getLiveHoverHint(annotation, doc, liveBeans); - if (range != null) { - return ImmutableList.of(range); + public CompletableFuture provideHover(ASTNode node, Annotation annotation, ITypeBinding type, int offset, + TextDocument doc, SpringBootApp[] runningApps) { + if (runningApps.length > 0) { + + LiveBean definedBean = getDefinedBean(annotation); + if (definedBean != null) { + StringBuilder hover = new StringBuilder(); + hover.append("**Injection report for " + showBean(definedBean) + "**\n\n"); + + boolean hasInterestingApp = false; + for (SpringBootApp app : runningApps) { + LiveBeansModel beans = app.getBeans(); + List relevantBeans = findRelevantBeans(app, definedBean).collect(Collectors.toList()); + + if (!relevantBeans.isEmpty()) { + if (!hasInterestingApp) { + hasInterestingApp = true; + } else { + hover.append("\n\n"); + } + hover.append(niceAppName(app) + ":"); + + for (LiveBean bean : relevantBeans) { + addInjectedInto(definedBean, hover, beans, bean); + addAutomaticallyWired(hover, annotation, beans, bean); } } } - catch (Exception e) { - e.printStackTrace(); + if (hasInterestingApp) { + System.out.println(hover); + return CompletableFuture + .completedFuture(new Hover(ImmutableList.of(Either.forLeft(hover.toString())))); } } } - catch (Exception e) { - Log.log(e); - } - return null; } - public Range getLiveHoverHint(Annotation annotation, TextDocument doc, LiveBeansModel beansModel) { - try { - TypeDeclaration type = findDeclaredType(annotation); - if (type != null && beansModel != null) { - String typeName = type.resolveBinding().getQualifiedName(); - List beansOfType = beansModel.getBeansOfType(typeName); + private void addInjectedInto(LiveBean definedBean, StringBuilder hover, LiveBeansModel beans, LiveBean bean) { + hover.append("\n\n"); + List dependers = beans.getBeansDependingOn(bean.getId()); + if (dependers.isEmpty()) { + hover.append(showBean(bean) + " exists but is **Not injected anywhere**\n"); + } else { + hover.append(showBean(definedBean) + " injected into:\n\n"); + boolean firstDependency = true; + for (LiveBean dependingBean : dependers) { + if (!firstDependency) { + hover.append("\n"); + } + hover.append("- " + showBean(dependingBean)); + firstDependency = false; + } + } + } - if (!beansOfType.isEmpty()) { - MethodDeclaration constructor = findConstructor(type); - if (constructor != null && !hasAutowiredAnnotation(constructor)) { - Range hoverRange = doc.toRange(constructor.getName().getStartPosition(), constructor.getName().getLength()); - return hoverRange; + private void addAutomaticallyWired(StringBuilder hover, Annotation annotation, LiveBeansModel beans, LiveBean bean) { + 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(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("- " + showBean(dependencyBean)); + } + firstDependency = false; } } } } - catch (BadLocationException e) { - Log.log(e); - } + } + private String getBeanId(Annotation annotation, ITypeBinding beanType) { + Optional explicitId = ASTUtils.getValueAttribute(annotation); + if (explicitId.isPresent()) { + return explicitId.get(); + } + String typeName = beanType.getName(); + if (StringUtil.hasText(typeName)) { + return Character.toLowerCase(typeName.charAt(0)) + typeName.substring(1); + } return null; } + private String showBean(LiveBean bean) { + return "Bean [id: " + bean.getId() + ", type: `" + bean.getType() + "`]"; + } + + private ITypeBinding getAnnotatedType(Annotation annotation) { + ASTNode parent = annotation.getParent(); + if (parent instanceof TypeDeclaration) { + TypeDeclaration typeDecl = (TypeDeclaration) parent; + return typeDecl.resolveBinding(); + } + return null; + } + + private String niceAppName(SpringBootApp app) { + return "Process [PID=" + app.getProcessID() + ", name=`" + app.getProcessName() + "`]"; + } + private boolean hasAutowiredAnnotation(MethodDeclaration constructor) { List modifiers = constructor.modifiers(); for (Object modifier : modifiers) { @@ -154,58 +207,4 @@ public class ComponentInjectionsHoverProvider implements HoverProvider { return false; } - - public void addLiveHoverContent(TypeDeclaration declaringType, TextDocument doc, LiveBeansModel beansModel, SpringBootAppProvider bootApp, List> hoverContent) { - String type = declaringType.resolveBinding().getQualifiedName(); - if (type != null && beansModel != null) { - List beansOfType = beansModel.getBeansOfType(type); - - if (!beansOfType.isEmpty()) { - String processId = bootApp.getProcessID(); - String processName = bootApp.getProcessName(); - - for (LiveBean liveBean : beansOfType) { - String[] dependencies = liveBean.getDependencies(); - - if (dependencies != null && dependencies.length > 0) { - hoverContent.add(Either.forLeft("bean: " + liveBean.getId())); - hoverContent.add(Either.forLeft("injected beans:")); - - for (String dependency : dependencies) { - List dependencyBeans = beansModel.getBeansOfName(dependency); - for (LiveBean dependencyBean : dependencyBeans) { - hoverContent.add(Either.forLeft("- '" + dependencyBean.getId() + "' - from: " + dependencyBean.getResource())); - } - } - } - else { - // TODO: no dependencies found - } - } - - hoverContent.add(Either.forLeft("Process ID: " + processId)); - hoverContent.add(Either.forLeft("Process Name: " + processName)); - } - } - } - - private TypeDeclaration findDeclaredType(Annotation annotation) { - ASTNode node = annotation; - while (node != null && !(node instanceof TypeDeclaration)) { - node = node.getParent(); - } - - return node != null ? (TypeDeclaration) node : null; - } - - private MethodDeclaration findConstructor(TypeDeclaration typeDecl) { - MethodDeclaration[] methods = typeDecl.getMethods(); - for (MethodDeclaration methodDeclaration : methods) { - if (methodDeclaration.isConstructor()) { - return methodDeclaration; - } - } - - return null; - } } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ASTUtils.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ASTUtils.java index deb9fba56..a55aea6e7 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ASTUtils.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/ASTUtils.java @@ -10,12 +10,17 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.livehover; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; +import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.SingleMemberAnnotation; import org.eclipse.jdt.core.dom.StringLiteral; +import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Range; import org.springframework.ide.vscode.commons.util.Log; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -49,4 +54,25 @@ public class ASTUtils { return Optional.empty(); } + public static TypeDeclaration findDeclaringType(Annotation annotation) { + ASTNode node = annotation; + while (node != null && !(node instanceof TypeDeclaration)) { + node = node.getParent(); + } + + return node != null ? (TypeDeclaration) node : null; + } + + public static MethodDeclaration[] findConstructors(TypeDeclaration typeDecl) { + List constructors = new ArrayList<>(); + + MethodDeclaration[] methods = typeDecl.getMethods(); + for (MethodDeclaration methodDeclaration : methods) { + if (methodDeclaration.isConstructor()) { + constructors.add(methodDeclaration); + } + } + + return constructors.toArray(new MethodDeclaration[constructors.size()]); + } } diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ComponentInjectedIntoHoverProviderTest.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ComponentInjectedIntoHoverProviderTest.java deleted file mode 100644 index 8e1c5862b..000000000 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ComponentInjectedIntoHoverProviderTest.java +++ /dev/null @@ -1,445 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Pivotal, Inc. - initial API and implementation - *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.beans.test; - -import static org.junit.Assert.assertTrue; - -import java.time.Duration; - -import org.junit.Before; -import org.junit.Test; -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.maven.java.MavenJavaProject; -import org.springframework.ide.vscode.commons.util.text.LanguageId; -import org.springframework.ide.vscode.languageserver.testharness.Editor; -import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; -import org.springframework.ide.vscode.project.harness.MockRunningAppProvider; -import org.springframework.ide.vscode.project.harness.ProjectsHarness; -import org.springframework.ide.vscode.project.harness.ProjectsHarness.CustomizableProjectContent; -import org.springframework.ide.vscode.project.harness.ProjectsHarness.ProjectCustomizer; - -public class ComponentInjectedIntoHoverProviderTest { - - private static final ProjectCustomizer FOO_INTERFACE = (CustomizableProjectContent p) -> { - p.createType("com.examle.Foo", - "package com.example;\n" + - "\n" + - "public interface Foo {\n" + - " void doSomeFoo();\n" + - "}\n" - ); - }; - - private BootLanguageServerHarness harness; - private ProjectsHarness projects = ProjectsHarness.INSTANCE; - - private MockRunningAppProvider mockAppProvider; - - @Before - public void setup() throws Exception { - mockAppProvider = new MockRunningAppProvider(); - harness = BootLanguageServerHarness.builder() - .mockDefaults() - .runningAppProvider(mockAppProvider.provider) - .watchDogInterval(Duration.ofMillis(100)) - .build(); - - MavenJavaProject jp = projects.mavenProject("empty-boot-15-web-app", FOO_INTERFACE); - assertTrue(jp.getClasspath().findType("com.example.Foo").exists()); - harness.useProject(projects.mavenProject("empty-boot-15-web-app")); - harness.intialize(null); - } - - @Test public void componentWithNoInjections() throws Exception { - LiveBeansModel beans = LiveBeansModel.builder() - .add(LiveBean.builder() - .id("fooImplementation") - .type("com.example.FooImplementation") - .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 FooImplementation implements Foo {\n" + - "\n" + - " @Override\n" + - " public void doSomeFoo() {\n" + - " System.out.println(\"Foo do do do!\");\n" + - " }\n" + - "}\n" - ); - editor.assertHighlights("@Component"); - editor.assertTrimmedHover("@Component", - "**Injection report for Bean [id: fooImplementation, type: `com.example.FooImplementation`]**\n" + - "\n" + - "Process [PID=111, name=`the-app`]:\n" + - "\n" + - "Bean [id: fooImplementation, type: `com.example.FooImplementation`] exists but is **Not injected anywhere**\n" - ); - } - - @Test public void componentWithOneInjection() throws Exception { - LiveBeansModel beans = LiveBeansModel.builder() - .add(LiveBean.builder() - .id("fooImplementation") - .type("com.example.FooImplementation") - .build() - ) - .add(LiveBean.builder() - .id("myController") - .type("com.example.MyController") - .dependencies("fooImplementation") - .build() - ) - .add(LiveBean.builder() - .id("irrelevantBean") - .type("com.example.IrrelevantBean") - .dependencies("myController") - .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 FooImplementation implements Foo {\n" + - "\n" + - " @Override\n" + - " public void doSomeFoo() {\n" + - " System.out.println(\"Foo do do do!\");\n" + - " }\n" + - "}\n" - ); - editor.assertHighlights("@Component"); - editor.assertTrimmedHover("@Component", - "**Injection report for Bean [id: fooImplementation, type: `com.example.FooImplementation`]**\n" + - "\n" + - "Process [PID=111, name=`the-app`]:\n" + - "\n" + - "Bean [id: fooImplementation, type: `com.example.FooImplementation`] injected into:\n" + - "\n" + - "- Bean [id: myController, type: `com.example.MyController`]\n" - ); - } - - @Test public void componentWithMultipleInjections() throws Exception { - LiveBeansModel beans = LiveBeansModel.builder() - .add(LiveBean.builder() - .id("fooImplementation") - .type("com.example.FooImplementation") - .build() - ) - .add(LiveBean.builder() - .id("myController") - .type("com.example.MyController") - .dependencies("fooImplementation") - .build() - ) - .add(LiveBean.builder() - .id("otherBean") - .type("com.example.OtherBean") - .dependencies("fooImplementation") - .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 FooImplementation implements Foo {\n" + - "\n" + - " @Override\n" + - " public void doSomeFoo() {\n" + - " System.out.println(\"Foo do do do!\");\n" + - " }\n" + - "}\n" - ); - editor.assertHighlights("@Component"); - editor.assertTrimmedHover("@Component", - "**Injection report for Bean [id: fooImplementation, type: `com.example.FooImplementation`]**\n" + - "\n" + - "Process [PID=111, name=`the-app`]:\n" + - "\n" + - "Bean [id: fooImplementation, type: `com.example.FooImplementation`] injected into:\n" + - "\n" + - "- Bean [id: myController, type: `com.example.MyController`]\n" + - "- Bean [id: otherBean, type: `com.example.OtherBean`]\n" - ); - } - - @Test public void componentWithMultipleInjectionsAndMultipleProcesses() throws Exception { - LiveBeansModel beans = LiveBeansModel.builder() - .add(LiveBean.builder() - .id("fooImplementation") - .type("com.example.FooImplementation") - .build() - ) - .add(LiveBean.builder() - .id("myController") - .type("com.example.MyController") - .dependencies("fooImplementation") - .build() - ) - .add(LiveBean.builder() - .id("otherBean") - .type("com.example.OtherBean") - .dependencies("fooImplementation") - .build() - ) - .build(); - for (int i = 1; i <= 2; i++) { - mockAppProvider.builder() - .isSpringBootApp(true) - .processId("100"+i) - .processName("app-instance-"+i) - .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 FooImplementation implements Foo {\n" + - "\n" + - " @Override\n" + - " public void doSomeFoo() {\n" + - " System.out.println(\"Foo do do do!\");\n" + - " }\n" + - "}\n" - ); - editor.assertHighlights("@Component"); - editor.assertTrimmedHover("@Component", - "**Injection report for Bean [id: fooImplementation, type: `com.example.FooImplementation`]**\n" + - "\n" + - "Process [PID=1001, name=`app-instance-1`]:\n" + - "\n" + - "Bean [id: fooImplementation, type: `com.example.FooImplementation`] injected into:\n" + - "\n" + - "- Bean [id: myController, type: `com.example.MyController`]\n" + - "- Bean [id: otherBean, type: `com.example.OtherBean`]\n" + - "\n" + - "Process [PID=1002, name=`app-instance-2`]:\n" + - "\n" + - "Bean [id: fooImplementation, type: `com.example.FooImplementation`] injected into:\n" + - "\n" + - "- Bean [id: myController, type: `com.example.MyController`]\n" + - "- Bean [id: otherBean, type: `com.example.OtherBean`]\n" - ); - } - - @Test public void onlyShowInfoForRelevantBeanId() throws Exception { - LiveBeansModel beans = LiveBeansModel.builder() - .add(LiveBean.builder() - .id("fooImplementation") - .type("com.example.FooImplementation") - .build() - ) - .add(LiveBean.builder() - .id("alternateFooImplementation") - .type("com.example.FooImplementation") - .build() - ) - .add(LiveBean.builder() - .id("myController") - .type("com.example.MyController") - .dependencies("fooImplementation") - .build() - ) - .add(LiveBean.builder() - .id("otherBean") - .type("com.example.OtherBean") - .dependencies("alternateFooImplementation") - .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 FooImplementation implements Foo {\n" + - "\n" + - " @Override\n" + - " public void doSomeFoo() {\n" + - " System.out.println(\"Foo do do do!\");\n" + - " }\n" + - "}\n" - ); - editor.assertHighlights("@Component"); - editor.assertHoverExactText("@Component", - "**Injection report for Bean [id: fooImplementation, type: `com.example.FooImplementation`]**\n" + - "\n" + - "Process [PID=111, name=`the-app`]:\n" + - "\n" + - "Bean [id: fooImplementation, type: `com.example.FooImplementation`] injected into:\n" + - "\n" + - "- Bean [id: myController, type: `com.example.MyController`]" - ); - } - - @Test public void explicitComponentId() throws Exception { - LiveBeansModel beans = LiveBeansModel.builder() - .add(LiveBean.builder() - .id("fooImplementation") - .type("com.example.FooImplementation") - .build() - ) - .add(LiveBean.builder() - .id("alternateFooImplementation") - .type("com.example.FooImplementation") - .build() - ) - .add(LiveBean.builder() - .id("myController") - .type("com.example.MyController") - .dependencies("fooImplementation") - .build() - ) - .add(LiveBean.builder() - .id("otherBean") - .type("com.example.OtherBean") - .dependencies("alternateFooImplementation") - .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(\"alternateFooImplementation\")\n" + - "public class FooImplementation implements Foo {\n" + - "\n" + - " @Override\n" + - " public void doSomeFoo() {\n" + - " System.out.println(\"Foo do do do!\");\n" + - " }\n" + - "}\n" - ); - editor.assertHighlights("@Component"); - editor.assertTrimmedHover("@Component", - "**Injection report for Bean [id: alternateFooImplementation, type: `com.example.FooImplementation`]**\n" + - "\n" + - "Process [PID=111, name=`the-app`]:\n" + - "\n" + - "Bean [id: alternateFooImplementation, type: `com.example.FooImplementation`] injected into:\n" + - "\n" + - "- Bean [id: otherBean, type: `com.example.OtherBean`]\n" - ); - } - - @Test public void noHoversWhenRunningAppDoesntHaveTheComponent() throws Exception { - LiveBeansModel beans = LiveBeansModel.builder() - .add(LiveBean.builder() - .id("whateverBean") - .type("com.example.UnrelatedComponent") - .build() - ) - .add(LiveBean.builder() - .id("myController") - .type("com.example.UnrelatedComponent") - .dependencies("whateverBean") - .build() - ) - .build(); - mockAppProvider.builder() - .isSpringBootApp(true) - .processId("111") - .processName("unrelated-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 FooImplementation implements Foo {\n" + - "\n" + - " @Override\n" + - " public void doSomeFoo() {\n" + - " System.out.println(\"Foo do do do!\");\n" + - " }\n" + - "}\n" - ); - editor.assertHighlights(/*MONE*/); - editor.assertNoHover("@Component"); - } - - @Test public void noHoversWhenNoRunningApps() throws Exception { - Editor editor = harness.newEditor(LanguageId.JAVA, - "package com.example;\n" + - "\n" + - "import org.springframework.stereotype.Component;\n" + - "\n" + - "@Component\n" + - "public class FooImplementation implements Foo {\n" + - "\n" + - " @Override\n" + - " public void doSomeFoo() {\n" + - " System.out.println(\"Foo do do do!\");\n" + - " }\n" + - "}\n" - ); - editor.assertHighlights(/*MONE*/); - editor.assertNoHover("@Component"); - } - -} diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ComponentInjectionsHoverProviderTest.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ComponentInjectionsHoverProviderTest.java index d11351f20..2330617ae 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ComponentInjectionsHoverProviderTest.java +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ComponentInjectionsHoverProviderTest.java @@ -10,201 +10,563 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans.test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import java.io.File; -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Stream; +import java.time.Duration; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.dom.AST; -import org.eclipse.jdt.core.dom.ASTNode; -import org.eclipse.jdt.core.dom.ASTParser; -import org.eclipse.jdt.core.dom.Annotation; -import org.eclipse.jdt.core.dom.CompilationUnit; -import org.eclipse.jdt.core.dom.NodeFinder; -import org.eclipse.lsp4j.Hover; -import org.eclipse.lsp4j.MarkedString; -import org.eclipse.lsp4j.Position; -import org.eclipse.lsp4j.Range; -import org.eclipse.lsp4j.TextDocumentIdentifier; -import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.junit.Before; import org.junit.Test; -import org.springframework.ide.vscode.boot.java.autowired.SpringBootAppProvider; -import org.springframework.ide.vscode.boot.java.beans.ComponentInjectionsHoverProvider; +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.IClasspath; -import org.springframework.ide.vscode.commons.java.IJavaProject; -import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; -import org.springframework.ide.vscode.commons.util.BadLocationException; +import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject; import org.springframework.ide.vscode.commons.util.text.LanguageId; -import org.springframework.ide.vscode.commons.util.text.TextDocument; +import org.springframework.ide.vscode.languageserver.testharness.Editor; import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; +import org.springframework.ide.vscode.project.harness.MockRunningAppProvider; import org.springframework.ide.vscode.project.harness.ProjectsHarness; +import org.springframework.ide.vscode.project.harness.ProjectsHarness.CustomizableProjectContent; +import org.springframework.ide.vscode.project.harness.ProjectsHarness.ProjectCustomizer; -/** - * @author Martin Lippert - */ public class ComponentInjectionsHoverProviderTest { - private JavaProjectFinder projectFinder; + private static final ProjectCustomizer FOO_INTERFACE = (CustomizableProjectContent p) -> { + p.createType("com.examle.Foo", + "package com.example;\n" + + "\n" + + "public interface Foo {\n" + + " void doSomeFoo();\n" + + "}\n" + ); + + p.createType("com.examle.DependencyA", + "package com.example;\n" + + "\n" + + "public class DependencyA {\n" + + "}\n" + ); + + p.createType("com.examle.DependencyB", + "package com.example;\n" + + "\n" + + "public class DependencyB {\n" + + "}\n" + ); + + }; + private BootLanguageServerHarness harness; + private ProjectsHarness projects = ProjectsHarness.INSTANCE; + + private MockRunningAppProvider mockAppProvider; @Before public void setup() throws Exception { - harness = BootLanguageServerHarness.builder().build(); - projectFinder = harness.getProjectFinder(); + mockAppProvider = new MockRunningAppProvider(); + harness = BootLanguageServerHarness.builder() + .mockDefaults() + .runningAppProvider(mockAppProvider.provider) + .watchDogInterval(Duration.ofMillis(100)) + .build(); + + MavenJavaProject jp = projects.mavenProject("empty-boot-15-web-app", FOO_INTERFACE); + assertTrue(jp.getClasspath().findType("com.example.Foo").exists()); + harness.useProject(projects.mavenProject("empty-boot-15-web-app")); + harness.intialize(null); } @Test - public void testLiveHoverHintForAutomaicallywiredConstructor() throws Exception { - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-autowired/").toURI()); - harness.intialize(directory); + public void componentWithNoInjections() throws Exception { + LiveBeansModel beans = LiveBeansModel.builder() + .add(LiveBean.builder() + .id("fooImplementation") + .type("com.example.FooImplementation") + .build() + ) + .build(); + mockAppProvider.builder() + .isSpringBootApp(true) + .processId("111") + .processName("the-app") + .beans(beans) + .build(); - String docURI = "file://" + directory.getAbsolutePath() + "/src/main/java/org/test/MyAutomaticallyWiredComponent.java"; - TextDocument document = createTempTextDocument(docURI); - IJavaProject project = projectFinder.find(new TextDocumentIdentifier(docURI)).get(); - - CompilationUnit cu = parse(document, project); - - int offset = document.toOffset(new Position(4, 2)); - ASTNode node = NodeFinder.perform(cu, offset, 0).getParent(); - - ComponentInjectionsHoverProvider provider = new ComponentInjectionsHoverProvider(); - String beansJSON = new String(Files.readAllBytes(new File(directory, "runtime-bean-information-automatically-wired.json").toPath())); - - Range hint = provider.getLiveHoverHint((Annotation)node, document, LiveBeansModel.parse(beansJSON)); - assertNotNull(hint); - - assertEquals(10, hint.getStart().getLine()); - assertEquals(8, hint.getStart().getCharacter()); - assertEquals(10, hint.getEnd().getLine()); - assertEquals(37, hint.getEnd().getCharacter()); + Editor editor = harness.newEditor(LanguageId.JAVA, + "package com.example;\n" + + "\n" + + "import org.springframework.stereotype.Component;\n" + + "\n" + + "@Component\n" + + "public class FooImplementation implements Foo {\n" + + "\n" + + " @Override\n" + + " public void doSomeFoo() {\n" + + " System.out.println(\"Foo do do do!\");\n" + + " }\n" + + "}\n" + ); + editor.assertHighlights("@Component"); + editor.assertTrimmedHover("@Component", + "**Injection report for Bean [id: fooImplementation, type: `com.example.FooImplementation`]**\n" + + "\n" + + "Process [PID=111, name=`the-app`]:\n" + + "\n" + + "Bean [id: fooImplementation, type: `com.example.FooImplementation`] exists but is **Not injected anywhere**\n" + ); } @Test - public void testNoLiveHoverHintForComponentWhenAutowiredAnnotationIsUsed() throws Exception { - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-autowired/").toURI()); - harness.intialize(directory); + public void componentWithOneInjection() throws Exception { + LiveBeansModel beans = LiveBeansModel.builder() + .add(LiveBean.builder() + .id("fooImplementation") + .type("com.example.FooImplementation") + .build() + ) + .add(LiveBean.builder() + .id("myController") + .type("com.example.MyController") + .dependencies("fooImplementation") + .build() + ) + .add(LiveBean.builder() + .id("irrelevantBean") + .type("com.example.IrrelevantBean") + .dependencies("myController") + .build() + ) + .build(); + mockAppProvider.builder() + .isSpringBootApp(true) + .processId("111") + .processName("the-app") + .beans(beans) + .build(); - String docURI = "file://" + directory.getAbsolutePath() + "/src/main/java/org/test/MyAutowiredComponent.java"; - TextDocument document = createTempTextDocument(docURI); - IJavaProject project = projectFinder.find(new TextDocumentIdentifier(docURI)).get(); - - CompilationUnit cu = parse(document, project); - - int offset = document.toOffset(new Position(5, 2)); - ASTNode node = NodeFinder.perform(cu, offset, 0).getParent(); - - ComponentInjectionsHoverProvider provider = new ComponentInjectionsHoverProvider(); - String beansJSON = new String(Files.readAllBytes(new File(directory, "runtime-bean-information.json").toPath())); - - Range hint = provider.getLiveHoverHint((Annotation)node, document, LiveBeansModel.parse(beansJSON)); - assertNull(hint); + Editor editor = harness.newEditor(LanguageId.JAVA, + "package com.example;\n" + + "\n" + + "import org.springframework.stereotype.Component;\n" + + "\n" + + "@Component\n" + + "public class FooImplementation implements Foo {\n" + + "\n" + + " @Override\n" + + " public void doSomeFoo() {\n" + + " System.out.println(\"Foo do do do!\");\n" + + " }\n" + + "}\n" + ); + editor.assertHighlights("@Component"); + editor.assertTrimmedHover("@Component", + "**Injection report for Bean [id: fooImplementation, type: `com.example.FooImplementation`]**\n" + + "\n" + + "Process [PID=111, name=`the-app`]:\n" + + "\n" + + "Bean [id: fooImplementation, type: `com.example.FooImplementation`] injected into:\n" + + "\n" + + "- Bean [id: myController, type: `com.example.MyController`]\n" + ); } @Test - public void testLiveHoverContentForAutomaicallywiredConstructor() throws Exception { - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-autowired/").toURI()); - harness.intialize(directory); + public void componentWithMultipleInjections() throws Exception { + LiveBeansModel beans = LiveBeansModel.builder() + .add(LiveBean.builder() + .id("fooImplementation") + .type("com.example.FooImplementation") + .build() + ) + .add(LiveBean.builder() + .id("myController") + .type("com.example.MyController") + .dependencies("fooImplementation") + .build() + ) + .add(LiveBean.builder() + .id("otherBean") + .type("com.example.OtherBean") + .dependencies("fooImplementation") + .build() + ) + .build(); + mockAppProvider.builder() + .isSpringBootApp(true) + .processId("111") + .processName("the-app") + .beans(beans) + .build(); - String docURI = "file://" + directory.getAbsolutePath() + "/src/main/java/org/test/MyAutomaticallyWiredComponent.java"; - TextDocument document = createTempTextDocument(docURI); - IJavaProject project = projectFinder.find(new TextDocumentIdentifier(docURI)).get(); - - CompilationUnit cu = parse(document, project); - - int offset = document.toOffset(new Position(4, 2)); - ASTNode node = NodeFinder.perform(cu, offset, 0).getParent(); - - ComponentInjectionsHoverProvider provider = new ComponentInjectionsHoverProvider(); - LiveBeansModel beansModel = LiveBeansModel.parse(new String(Files.readAllBytes(new File(directory, "runtime-bean-information-automatically-wired.json").toPath()))); - - SpringBootAppProvider bootApp = new SpringBootAppProvider() { - @Override - public String getProcessName() { - return "test process name"; - } - - @Override - public String getProcessID() { - return "test process id"; - } - - @Override - public LiveBeansModel getBeans() throws Exception { - return beansModel; - } - }; - CompletableFuture hoverFuture = provider.provideHover(null, (Annotation) node, null, 0, document, new SpringBootAppProvider[] {bootApp}); - Hover hover = hoverFuture.get(); - - assertNotNull(hover); - - assertEquals(10, hover.getRange().getStart().getLine()); - assertEquals(8, hover.getRange().getStart().getCharacter()); - assertEquals(10, hover.getRange().getEnd().getLine()); - assertEquals(37, hover.getRange().getEnd().getCharacter()); - - List> contents = hover.getContents(); - assertEquals(6, contents.size()); - - assertTrue(contents.get(0).getLeft().contains("myAutomaticallyWiredComponent")); - assertTrue(contents.get(2).getLeft().contains("dependencyA")); - assertTrue(contents.get(3).getLeft().contains("dependencyB")); - assertTrue(contents.get(4).getLeft().contains("test process id")); - assertTrue(contents.get(5).getLeft().contains("test process name")); + Editor editor = harness.newEditor(LanguageId.JAVA, + "package com.example;\n" + + "\n" + + "import org.springframework.stereotype.Component;\n" + + "\n" + + "@Component\n" + + "public class FooImplementation implements Foo {\n" + + "\n" + + " @Override\n" + + " public void doSomeFoo() {\n" + + " System.out.println(\"Foo do do do!\");\n" + + " }\n" + + "}\n" + ); + editor.assertHighlights("@Component"); + editor.assertTrimmedHover("@Component", + "**Injection report for Bean [id: fooImplementation, type: `com.example.FooImplementation`]**\n" + + "\n" + + "Process [PID=111, name=`the-app`]:\n" + + "\n" + + "Bean [id: fooImplementation, type: `com.example.FooImplementation`] injected into:\n" + + "\n" + + "- Bean [id: myController, type: `com.example.MyController`]\n" + + "- Bean [id: otherBean, type: `com.example.OtherBean`]\n" + ); } - private TextDocument createTempTextDocument(String docURI) throws Exception { - Path path = Paths.get(new URI(docURI)); - String content = new String(Files.readAllBytes(path)); + @Test + public void componentWithMultipleInjectionsAndMultipleProcesses() throws Exception { + LiveBeansModel beans = LiveBeansModel.builder() + .add(LiveBean.builder() + .id("fooImplementation") + .type("com.example.FooImplementation") + .build() + ) + .add(LiveBean.builder() + .id("myController") + .type("com.example.MyController") + .dependencies("fooImplementation") + .build() + ) + .add(LiveBean.builder() + .id("otherBean") + .type("com.example.OtherBean") + .dependencies("fooImplementation") + .build() + ) + .build(); + for (int i = 1; i <= 2; i++) { + mockAppProvider.builder() + .isSpringBootApp(true) + .processId("100"+i) + .processName("app-instance-"+i) + .beans(beans) + .build(); + } - TextDocument doc = new TextDocument(docURI, LanguageId.PLAINTEXT, 0, content); - return doc; + Editor editor = harness.newEditor(LanguageId.JAVA, + "package com.example;\n" + + "\n" + + "import org.springframework.stereotype.Component;\n" + + "\n" + + "@Component\n" + + "public class FooImplementation implements Foo {\n" + + "\n" + + " @Override\n" + + " public void doSomeFoo() {\n" + + " System.out.println(\"Foo do do do!\");\n" + + " }\n" + + "}\n" + ); + editor.assertHighlights("@Component"); + editor.assertTrimmedHover("@Component", + "**Injection report for Bean [id: fooImplementation, type: `com.example.FooImplementation`]**\n" + + "\n" + + "Process [PID=1001, name=`app-instance-1`]:\n" + + "\n" + + "Bean [id: fooImplementation, type: `com.example.FooImplementation`] injected into:\n" + + "\n" + + "- Bean [id: myController, type: `com.example.MyController`]\n" + + "- Bean [id: otherBean, type: `com.example.OtherBean`]\n" + + "\n" + + "Process [PID=1002, name=`app-instance-2`]:\n" + + "\n" + + "Bean [id: fooImplementation, type: `com.example.FooImplementation`] injected into:\n" + + "\n" + + "- Bean [id: myController, type: `com.example.MyController`]\n" + + "- Bean [id: otherBean, type: `com.example.OtherBean`]\n" + ); } - private CompilationUnit parse(TextDocument document, IJavaProject project) - throws Exception, BadLocationException { - ASTParser parser = ASTParser.newParser(AST.JLS8); - Map options = JavaCore.getOptions(); - JavaCore.setComplianceOptions(JavaCore.VERSION_1_8, options); - parser.setCompilerOptions(options); - parser.setKind(ASTParser.K_COMPILATION_UNIT); - parser.setStatementsRecovery(true); - parser.setBindingsRecovery(true); - parser.setResolveBindings(true); + @Test + public void onlyShowInfoForRelevantBeanId() throws Exception { + LiveBeansModel beans = LiveBeansModel.builder() + .add(LiveBean.builder() + .id("fooImplementation") + .type("com.example.FooImplementation") + .build() + ) + .add(LiveBean.builder() + .id("alternateFooImplementation") + .type("com.example.FooImplementation") + .build() + ) + .add(LiveBean.builder() + .id("myController") + .type("com.example.MyController") + .dependencies("fooImplementation") + .build() + ) + .add(LiveBean.builder() + .id("otherBean") + .type("com.example.OtherBean") + .dependencies("alternateFooImplementation") + .build() + ) + .build(); + mockAppProvider.builder() + .isSpringBootApp(true) + .processId("111") + .processName("the-app") + .beans(beans) + .build(); - String[] classpathEntries = getClasspathEntries(project); - String[] sourceEntries = new String[] {}; - parser.setEnvironment(classpathEntries, sourceEntries, null, true); - - String docURI = document.getUri(); - String unitName = docURI.substring(docURI.lastIndexOf("/")); - parser.setUnitName(unitName); - parser.setSource(document.get(0, document.getLength()).toCharArray()); - - CompilationUnit cu = (CompilationUnit) parser.createAST(null); - return cu; + Editor editor = harness.newEditor(LanguageId.JAVA, + "package com.example;\n" + + "\n" + + "import org.springframework.stereotype.Component;\n" + + "\n" + + "@Component\n" + + "public class FooImplementation implements Foo {\n" + + "\n" + + " @Override\n" + + " public void doSomeFoo() {\n" + + " System.out.println(\"Foo do do do!\");\n" + + " }\n" + + "}\n" + ); + editor.assertHighlights("@Component"); + editor.assertHoverExactText("@Component", + "**Injection report for Bean [id: fooImplementation, type: `com.example.FooImplementation`]**\n" + + "\n" + + "Process [PID=111, name=`the-app`]:\n" + + "\n" + + "Bean [id: fooImplementation, type: `com.example.FooImplementation`] injected into:\n" + + "\n" + + "- Bean [id: myController, type: `com.example.MyController`]" + ); } - private String[] getClasspathEntries(IJavaProject project) throws Exception { - IClasspath classpath = project.getClasspath(); - Stream classpathEntries = classpath.getClasspathEntries(); - return classpathEntries - .filter(path -> path.toFile().exists()) - .map(path -> path.toAbsolutePath().toString()).toArray(String[]::new); + @Test + public void explicitComponentId() throws Exception { + LiveBeansModel beans = LiveBeansModel.builder() + .add(LiveBean.builder() + .id("fooImplementation") + .type("com.example.FooImplementation") + .build() + ) + .add(LiveBean.builder() + .id("alternateFooImplementation") + .type("com.example.FooImplementation") + .build() + ) + .add(LiveBean.builder() + .id("myController") + .type("com.example.MyController") + .dependencies("fooImplementation") + .build() + ) + .add(LiveBean.builder() + .id("otherBean") + .type("com.example.OtherBean") + .dependencies("alternateFooImplementation") + .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(\"alternateFooImplementation\")\n" + + "public class FooImplementation implements Foo {\n" + + "\n" + + " @Override\n" + + " public void doSomeFoo() {\n" + + " System.out.println(\"Foo do do do!\");\n" + + " }\n" + + "}\n" + ); + editor.assertHighlights("@Component"); + editor.assertTrimmedHover("@Component", + "**Injection report for Bean [id: alternateFooImplementation, type: `com.example.FooImplementation`]**\n" + + "\n" + + "Process [PID=111, name=`the-app`]:\n" + + "\n" + + "Bean [id: alternateFooImplementation, type: `com.example.FooImplementation`] injected into:\n" + + "\n" + + "- Bean [id: otherBean, type: `com.example.OtherBean`]\n" + ); } + @Test + public void noHoversWhenRunningAppDoesntHaveTheComponent() throws Exception { + LiveBeansModel beans = LiveBeansModel.builder() + .add(LiveBean.builder() + .id("whateverBean") + .type("com.example.UnrelatedComponent") + .build() + ) + .add(LiveBean.builder() + .id("myController") + .type("com.example.UnrelatedComponent") + .dependencies("whateverBean") + .build() + ) + .build(); + mockAppProvider.builder() + .isSpringBootApp(true) + .processId("111") + .processName("unrelated-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 FooImplementation implements Foo {\n" + + "\n" + + " @Override\n" + + " public void doSomeFoo() {\n" + + " System.out.println(\"Foo do do do!\");\n" + + " }\n" + + "}\n" + ); + editor.assertHighlights(/*MONE*/); + editor.assertNoHover("@Component"); + } + + @Test + public void noHoversWhenNoRunningApps() throws Exception { + Editor editor = harness.newEditor(LanguageId.JAVA, + "package com.example;\n" + + "\n" + + "import org.springframework.stereotype.Component;\n" + + "\n" + + "@Component\n" + + "public class FooImplementation implements Foo {\n" + + "\n" + + " @Override\n" + + " public void doSomeFoo() {\n" + + " System.out.println(\"Foo do do do!\");\n" + + " }\n" + + "}\n" + ); + editor.assertHighlights(/*MONE*/); + editor.assertNoHover("@Component"); + } + + @Test + public void componentWithAutomaticallyWiredConstructorInjections() throws Exception { + LiveBeansModel beans = LiveBeansModel.builder() + .add(LiveBean.builder() + .id("autowiredClass") + .type("com.example.AutowiredClass") + .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 AutowiredClass {\n" + + "\n" + + " public AutowiredClass(DependencyA depA, DependencyB depB) {\n" + + " }\n" + + "}\n" + ); + editor.assertHighlights("@Component"); + 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" + + "Bean [id: autowiredClass, type: `com.example.AutowiredClass`] got autowired with:\n" + + "\n" + + "- Bean [id: dependencyA, type: `com.example.DependencyA`]\n" + + "- Bean [id: dependencyB, type: `com.example.DependencyB`]\n" + ); + } + + @Test + public void componentWithAutowiredConstructorNoAdditionalHovers() throws Exception { + LiveBeansModel beans = LiveBeansModel.builder() + .add(LiveBean.builder() + .id("autowiredClass") + .type("com.example.AutowiredClass") + .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 AutowiredClass {\n" + + "\n" + + " @Autowired\n" + + " public AutowiredClass(DependencyA depA, DependencyB depB) {\n" + + " }\n" + + "}\n" + ); + editor.assertHighlights("@Component", "@Autowired"); + 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" + ); + } }