diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java index 301f6557d..bf9309c24 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java @@ -10,20 +10,21 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.autowired; -import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; 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.MarkedString; 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.boot.java.livehover.LiveHoverUtils; 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; @@ -38,50 +39,6 @@ import com.google.common.collect.ImmutableList; */ public class AutowiredHoverProvider 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) { - try { - List> hoverContent = new ArrayList<>(); - - for (SpringBootAppProvider bootApp : runningApps) { - try { - LiveBeansModel liveBeans = bootApp.getBeans(); - if (liveBeans != null && !liveBeans.isEmpty()) { - addLiveHoverContent(annotation, doc, liveBeans, bootApp, hoverContent); - } - } - catch (Exception e) { - e.printStackTrace(); - } - } - - if (hoverContent.size() > 0) { - Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength()); - Hover hover = new Hover(); - - hover.setContents(hoverContent); - hover.setRange(hoverRange); - - return CompletableFuture.completedFuture(hover); - } - } catch (Exception e) { - Log.log(e); - } - - return null; - } - @Override public Collection getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { try { @@ -96,7 +53,7 @@ public class AutowiredHoverProvider implements HoverProvider { } } catch (Exception e) { - e.printStackTrace(); + Log.log(e); } } } @@ -109,12 +66,15 @@ public class AutowiredHoverProvider implements HoverProvider { public Range getLiveHoverHint(Annotation annotation, TextDocument doc, LiveBeansModel beansModel) { try { - String type = findDeclaredType(annotation); - if (type != null && beansModel != null) { - List beansOfType = beansModel.getBeansOfType(type); - if (!beansOfType.isEmpty()) { - Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength()); - return hoverRange; + TypeDeclaration declaringType = ASTUtils.findDeclaringType(annotation); + if (declaringType != null) { + String type = declaringType.resolveBinding().getQualifiedName(); + if (type != null && beansModel != null) { + List beansOfType = beansModel.getBeansOfType(type); + if (!beansOfType.isEmpty()) { + Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength()); + return hoverRange; + } } } } @@ -125,53 +85,68 @@ public class AutowiredHoverProvider implements HoverProvider { return null; } - public void addLiveHoverContent(Annotation annotation, TextDocument doc, LiveBeansModel beansModel, SpringBootAppProvider bootApp, List> hoverContent) { - String type = findDeclaredType(annotation); - if (type != null && beansModel != null) { - List beansOfType = beansModel.getBeansOfType(type); + @Override + public CompletableFuture provideHover(ASTNode node, Annotation annotation, ITypeBinding type, int offset, + TextDocument doc, SpringBootApp[] runningApps) { + if (runningApps.length > 0) { - if (!beansOfType.isEmpty()) { - String processId = bootApp.getProcessID(); - String processName = bootApp.getProcessName(); + StringBuilder hover = new StringBuilder(); - for (LiveBean liveBean : beansOfType) { - String[] dependencies = liveBean.getDependencies(); + LiveBean definedBean = ASTUtils.getDefinedBean(annotation); + if (definedBean != null) { - if (dependencies != null && dependencies.length > 0) { - hoverContent.add(Either.forLeft("bean: " + liveBean.getId())); - hoverContent.add(Either.forLeft("injected beans:")); + hover.append("**Injection report for " + LiveHoverUtils.showBean(definedBean) + "**\n\n"); - for (String dependency : dependencies) { - List dependencyBeans = beansModel.getBeansOfName(dependency); - for (LiveBean dependencyBean : dependencyBeans) { - hoverContent.add(Either.forLeft("- '" + dependencyBean.getId() + "' - from: " + dependencyBean.getResource())); - } + boolean hasInterestingApp = false; + for (SpringBootApp app : runningApps) { + LiveBeansModel beans = app.getBeans(); + List relevantBeans = LiveHoverUtils.findRelevantBeans(app, definedBean).collect(Collectors.toList()); + + if (!relevantBeans.isEmpty()) { + if (!hasInterestingApp) { + hasInterestingApp = true; + } else { + hover.append("\n\n"); + } + hover.append(LiveHoverUtils.niceAppName(app) + ":"); + + for (LiveBean bean : relevantBeans) { + hover.append("\n\n"); + addAutomaticallyWired(hover, annotation, beans, bean); } } - else { - // TODO: no dependencies found - } } + if (hasInterestingApp) { + System.out.println(hover); + return CompletableFuture + .completedFuture(new Hover(ImmutableList.of(Either.forLeft(hover.toString())))); + } + } + } + return null; + } - hoverContent.add(Either.forLeft("Process ID: " + processId)); - hoverContent.add(Either.forLeft("Process Name: " + processName)); + private void addAutomaticallyWired(StringBuilder hover, Annotation annotation, LiveBeansModel beans, LiveBean bean) { + TypeDeclaration typeDecl = ASTUtils.findDeclaringType(annotation); + if (typeDecl != null) { + String[] dependencies = bean.getDependencies(); + + if (dependencies != null && dependencies.length > 0) { + hover.append(LiveHoverUtils.showBean(bean) + " got autowired with:\n\n"); + + boolean firstDependency = true; + for (String injectedBean : dependencies) { + if (!firstDependency) { + hover.append("\n"); + } + List dependencyBeans = beans.getBeansOfName(injectedBean); + for (LiveBean dependencyBean : dependencyBeans) { + hover.append("- " + LiveHoverUtils.showBean(dependencyBean)); + } + firstDependency = false; + } } } } - private String findDeclaredType(Annotation annotation) { - ASTNode node = annotation; - while (node != null && !(node instanceof TypeDeclaration)) { - node = node.getParent(); - } - - if (node != null) { - TypeDeclaration typeDecl = (TypeDeclaration) node; - return typeDecl.resolveBinding().getQualifiedName(); - } - else { - return null; - } - } - } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/SpringBootAppProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/SpringBootAppProvider.java deleted file mode 100644 index 5bf1fda5d..000000000 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/SpringBootAppProvider.java +++ /dev/null @@ -1,24 +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.autowired; - -import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel; - -/** - * @author Martin Lippert - */ -public interface SpringBootAppProvider { - - public LiveBeansModel getBeans() throws Exception; - public String getProcessID(); - public String getProcessName(); - -} diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/SpringBootAppProviderImpl.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/SpringBootAppProviderImpl.java deleted file mode 100644 index 3563d0376..000000000 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/SpringBootAppProviderImpl.java +++ /dev/null @@ -1,42 +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.autowired; - -import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; -import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel; - -/** - * @author Martin Lippert - */ -public class SpringBootAppProviderImpl implements SpringBootAppProvider { - - private SpringBootApp bootApp; - - public SpringBootAppProviderImpl(SpringBootApp bootApp) { - this.bootApp = bootApp; - } - - @Override - public LiveBeansModel getBeans() throws Exception { - return bootApp.getBeans(); - } - - @Override - public String getProcessID() { - return bootApp.getProcessID(); - } - - @Override - public String getProcessName() { - return bootApp.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 c8047ae58..82167630f 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 @@ -29,11 +29,11 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.springframework.ide.vscode.boot.java.autowired.Constants; import org.springframework.ide.vscode.boot.java.handlers.HoverProvider; import org.springframework.ide.vscode.boot.java.livehover.ASTUtils; +import org.springframework.ide.vscode.boot.java.livehover.LiveHoverUtils; 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; @@ -45,9 +45,9 @@ public class ComponentInjectionsHoverProvider implements HoverProvider { // Highlight if any running app contains an instance of this component try { if (runningApps.length > 0) { - LiveBean definedBean = getDefinedBean(annotation); + LiveBean definedBean = ASTUtils.getDefinedBean(annotation); if (definedBean != null) { - if (Stream.of(runningApps).anyMatch(app -> hasRelevantBeans(app, definedBean))) { + if (Stream.of(runningApps).anyMatch(app -> LiveHoverUtils.hasRelevantBeans(app, definedBean))) { Optional nameRange = ASTUtils.nameRange(doc, annotation); if (nameRange.isPresent()) { return ImmutableList.of(nameRange.get()); @@ -61,40 +61,20 @@ public class ComponentInjectionsHoverProvider implements HoverProvider { 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); + LiveBean definedBean = ASTUtils.getDefinedBean(annotation); if (definedBean != null) { StringBuilder hover = new StringBuilder(); - hover.append("**Injection report for " + showBean(definedBean) + "**\n\n"); + hover.append("**Injection report for " + LiveHoverUtils.showBean(definedBean) + "**\n\n"); boolean hasInterestingApp = false; for (SpringBootApp app : runningApps) { LiveBeansModel beans = app.getBeans(); - List relevantBeans = findRelevantBeans(app, definedBean).collect(Collectors.toList()); + List relevantBeans = LiveHoverUtils.findRelevantBeans(app, definedBean).collect(Collectors.toList()); if (!relevantBeans.isEmpty()) { if (!hasInterestingApp) { @@ -102,7 +82,7 @@ public class ComponentInjectionsHoverProvider implements HoverProvider { } else { hover.append("\n\n"); } - hover.append(niceAppName(app) + ":"); + hover.append(LiveHoverUtils.niceAppName(app) + ":"); for (LiveBean bean : relevantBeans) { addInjectedInto(definedBean, hover, beans, bean); @@ -124,15 +104,15 @@ public class ComponentInjectionsHoverProvider implements HoverProvider { hover.append("\n\n"); List dependers = beans.getBeansDependingOn(bean.getId()); if (dependers.isEmpty()) { - hover.append(showBean(bean) + " exists but is **Not injected anywhere**\n"); + hover.append(LiveHoverUtils.showBean(bean) + " exists but is **Not injected anywhere**\n"); } else { - hover.append(showBean(definedBean) + " injected into:\n\n"); + hover.append(LiveHoverUtils.showBean(definedBean) + " injected into:\n\n"); boolean firstDependency = true; for (LiveBean dependingBean : dependers) { if (!firstDependency) { hover.append("\n"); } - hover.append("- " + showBean(dependingBean)); + hover.append("- " + LiveHoverUtils.showBean(dependingBean)); firstDependency = false; } } @@ -147,7 +127,7 @@ public class ComponentInjectionsHoverProvider implements HoverProvider { String[] dependencies = bean.getDependencies(); if (dependencies != null && dependencies.length > 0) { - hover.append(showBean(bean) + " got autowired with:\n\n"); + hover.append(LiveHoverUtils.showBean(bean) + " got autowired with:\n\n"); boolean firstDependency = true; for (String injectedBean : dependencies) { @@ -156,7 +136,7 @@ public class ComponentInjectionsHoverProvider implements HoverProvider { } List dependencyBeans = beans.getBeansOfName(injectedBean); for (LiveBean dependencyBean : dependencyBeans) { - hover.append("- " + showBean(dependencyBean)); + hover.append("- " + LiveHoverUtils.showBean(dependencyBean)); } firstDependency = false; } @@ -165,35 +145,6 @@ public class ComponentInjectionsHoverProvider implements HoverProvider { } } - 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) { 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 a55aea6e7..f1d3337e3 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 @@ -17,12 +17,15 @@ 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.ITypeBinding; 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.boot.app.cli.livebean.LiveBean; 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; public class ASTUtils { @@ -75,4 +78,31 @@ public class ASTUtils { return constructors.toArray(new MethodDeclaration[constructors.size()]); } + + public static LiveBean getDefinedBean(Annotation annotation) { + TypeDeclaration declaringType = ASTUtils.findDeclaringType(annotation); + if (declaringType != null) { + ITypeBinding beanType = declaringType.resolveBinding(); + if (beanType != null) { + String id = getBeanId(annotation, beanType); + if (StringUtil.hasText(id)) { + return LiveBean.builder().id(id).type(beanType.getQualifiedName()).build(); + } + } + } + return null; + } + + public static 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; + } + } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/LiveHoverUtils.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/LiveHoverUtils.java new file mode 100644 index 000000000..b881841b5 --- /dev/null +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/LiveHoverUtils.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * 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.livehover; + +import java.util.stream.Stream; + +import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; +import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBean; + +public class LiveHoverUtils { + + public static String showBean(LiveBean bean) { + return "Bean [id: " + bean.getId() + ", type: `" + bean.getType() + "`]"; + } + + public static String niceAppName(SpringBootApp app) { + return "Process [PID=" + app.getProcessID() + ", name=`" + app.getProcessName() + "`]"; + } + + public static boolean hasRelevantBeans(SpringBootApp app, LiveBean definedBean) { + return findRelevantBeans(app, definedBean).findAny().isPresent(); + } + + public static Stream findRelevantBeans(SpringBootApp app, LiveBean definedBean) { + return app.getBeans().getBeansOfName(definedBean.getId()).stream() + .filter(bean -> definedBean.getType().equals(bean.getType())); + } + +} diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/AutowiredHoverProviderTest.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/AutowiredHoverProviderTest.java index 8e0d05537..305e00b3f 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/AutowiredHoverProviderTest.java +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/AutowiredHoverProviderTest.java @@ -10,219 +10,188 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.autowired.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.AutowiredHoverProvider; -import org.springframework.ide.vscode.boot.java.autowired.SpringBootAppProvider; +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 AutowiredHoverProviderTest { + 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 JavaProjectFinder projectFinder; + 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 testLiveHoverHintForAutowiredOnConstructor() throws Exception { - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-autowired/").toURI()); - harness.intialize(directory); + 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(); - String docURI = "file://" + directory.getAbsolutePath() + "/src/main/java/org/test/MyAutowiredComponent.java"; - TextDocument document = createTempTextDocument(docURI); - IJavaProject project = projectFinder.find(new TextDocumentIdentifier(docURI)).get(); + 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" + ); - CompilationUnit cu = parse(document, project); - - int offset = document.toOffset(new Position(11, 4)); - ASTNode node = NodeFinder.perform(cu, offset, 0).getParent(); - - AutowiredHoverProvider provider = new AutowiredHoverProvider(); - String beansJSON = new String(Files.readAllBytes(new File(directory, "runtime-bean-information.json").toPath())); - - Range hint = provider.getLiveHoverHint((Annotation)node, document, LiveBeansModel.parse(beansJSON)); - assertNotNull(hint); - - assertEquals(11, hint.getStart().getLine()); - assertEquals(1, hint.getStart().getCharacter()); - assertEquals(11, hint.getEnd().getLine()); - assertEquals(11, hint.getEnd().getCharacter()); + editor.assertHighlights("@Component", "@Autowired"); + editor.assertTrimmedHover("@Autowired", + "**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`] got autowired with:\n" + + "\n" + + "- Bean [id: dependencyA, type: `com.example.DependencyA`]\n" + + "- Bean [id: dependencyB, type: `com.example.DependencyB`]\n" + ); } @Test - public void testNoLiveHoverHintForAutowiredOnConstructorWithNoLiveAppData() throws Exception { - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-autowired/").toURI()); - harness.intialize(directory); + public void noHoversWhenNoRunningApps() throws Exception { + 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" + ); - 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(11, 4)); - ASTNode node = NodeFinder.perform(cu, offset, 0).getParent(); - - AutowiredHoverProvider provider = new AutowiredHoverProvider(); - Range hint = provider.getLiveHoverHint((Annotation)node, document, LiveBeansModel.parse(null)); - assertNull(hint); + editor.assertHighlights(/*MONE*/); + editor.assertNoHover("@Autowired"); } @Test - public void testNoLiveHoverHintForAutowiredOnConstructorWithWrongLiveAppData() throws Exception { - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-autowired/").toURI()); - harness.intialize(directory); + 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(); - String docURI = "file://" + directory.getAbsolutePath() + "/src/main/java/org/test/MyAutowiredComponent.java"; - TextDocument document = createTempTextDocument(docURI); - IJavaProject project = projectFinder.find(new TextDocumentIdentifier(docURI)).get(); + 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" + ); - CompilationUnit cu = parse(document, project); - - int offset = document.toOffset(new Position(11, 4)); - ASTNode node = NodeFinder.perform(cu, offset, 0).getParent(); - - AutowiredHoverProvider provider = new AutowiredHoverProvider(); - String beansJSON = new String(Files.readAllBytes(new File(directory, "wrong-runtime-bean-information.json").toPath())); - - Range hint = provider.getLiveHoverHint((Annotation)node, document, LiveBeansModel.parse(beansJSON)); - assertNull(hint); + editor.assertHighlights(/*MONE*/); + editor.assertNoHover("@Autowired"); } - @Test - public void testLiveHoverContentForAutowiredOnConstructor() throws Exception { - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-autowired/").toURI()); - harness.intialize(directory); - - 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(11, 4)); - ASTNode node = NodeFinder.perform(cu, offset, 0).getParent(); - - AutowiredHoverProvider provider = new AutowiredHoverProvider(); - LiveBeansModel beansModel = LiveBeansModel.parse(new String(Files.readAllBytes(new File(directory, "runtime-bean-information.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, offset, document, new SpringBootAppProvider[] {bootApp}); - Hover hover = hoverFuture.get(); - assertNotNull(hover); - - assertEquals(11, hover.getRange().getStart().getLine()); - assertEquals(1, hover.getRange().getStart().getCharacter()); - assertEquals(11, hover.getRange().getEnd().getLine()); - assertEquals(11, hover.getRange().getEnd().getCharacter()); - - List> contents = hover.getContents(); - assertEquals(6, contents.size()); - - assertTrue(contents.get(0).getLeft().contains("myAutowiredComponent")); - 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")); - } - - private TextDocument createTempTextDocument(String docURI) throws Exception { - Path path = Paths.get(new URI(docURI)); - String content = new String(Files.readAllBytes(path)); - - TextDocument doc = new TextDocument(docURI, LanguageId.PLAINTEXT, 0, content); - return doc; - } - - 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); - - 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; - } - - 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); - } - - }