replaced live hover implementation for autowired annotation with new one

This commit is contained in:
Martin Lippert
2017-10-30 12:04:15 +01:00
parent 16b8d4aaad
commit b98315876b
7 changed files with 293 additions and 397 deletions

View File

@@ -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<Hover> 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<Hover> provideHover(ASTNode node, Annotation annotation,
ITypeBinding type, int offset, TextDocument doc, SpringBootAppProvider[] runningApps) {
try {
List<Either<String, MarkedString>> 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<Range> 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<LiveBean> 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<LiveBean> 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<Either<String, MarkedString>> hoverContent) {
String type = findDeclaredType(annotation);
if (type != null && beansModel != null) {
List<LiveBean> beansOfType = beansModel.getBeansOfType(type);
@Override
public CompletableFuture<Hover> 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<LiveBean> 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<LiveBean> 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<LiveBean> 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;
}
}
}

View File

@@ -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();
}

View File

@@ -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();
}
}

View File

@@ -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<Range> 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<LiveBean> 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<Hover> 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<LiveBean> relevantBeans = findRelevantBeans(app, definedBean).collect(Collectors.toList());
List<LiveBean> 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<LiveBean> 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<LiveBean> 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<String> 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) {

View File

@@ -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<String> 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;
}
}

View File

@@ -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<LiveBean> findRelevantBeans(SpringBootApp app, LiveBean definedBean) {
return app.getBeans().getBeansOfName(definedBean.getId()).stream()
.filter(bean -> definedBean.getType().equals(bean.getType()));
}
}

View File

@@ -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<Hover> 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<Either<String, MarkedString>> 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<String, String> 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<Path> classpathEntries = classpath.getClasspathEntries();
return classpathEntries
.filter(path -> path.toFile().exists())
.map(path -> path.toAbsolutePath().toString()).toArray(String[]::new);
}
}