Injection report on live hover over @Bean

This commit is contained in:
Kris De Volder
2017-10-30 15:36:00 -07:00
parent 23253e3227
commit 3e6de74351
12 changed files with 650 additions and 91 deletions

View File

@@ -20,7 +20,6 @@ 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.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;
@@ -35,6 +34,8 @@ import org.springframework.ide.vscode.boot.java.handlers.ReferenceProvider;
import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider;
import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
import org.springframework.ide.vscode.boot.java.livehover.ActiveProfilesProvider;
import org.springframework.ide.vscode.boot.java.livehover.BeanInjectedIntoHoverProvider;
import org.springframework.ide.vscode.boot.java.livehover.ComponentInjectionsHoverProvider;
import org.springframework.ide.vscode.boot.java.requestmapping.LiveAppURLSymbolProvider;
import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingHoverProvider;
import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingSymbolProvider;
@@ -262,6 +263,7 @@ public class BootJavaLanguageServer extends SimpleLanguageServer {
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 ComponentInjectionsHoverProvider());
providers.put(BeanInjectedIntoHoverProvider.ANNOTATION, new BeanInjectedIntoHoverProvider());
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());

View File

@@ -12,6 +12,7 @@ package org.springframework.ide.vscode.boot.java.autowired;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@@ -30,6 +31,8 @@ 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.Optionals;
import org.springframework.ide.vscode.commons.util.StringUtil;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import com.google.common.collect.ImmutableList;
@@ -92,7 +95,7 @@ public class AutowiredHoverProvider implements HoverProvider {
StringBuilder hover = new StringBuilder();
LiveBean definedBean = ASTUtils.getDefinedBean(annotation);
LiveBean definedBean = getDefinedBean(annotation);
if (definedBean != null) {
hover.append("**Injection report for " + LiveHoverUtils.showBean(definedBean) + "**\n\n");
@@ -126,6 +129,31 @@ public class AutowiredHoverProvider implements HoverProvider {
return null;
}
private 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;
}
private String getBeanId(Annotation annotation, ITypeBinding beanType) {
return ASTUtils.getAttribute(annotation, "value").flatMap(ASTUtils::getFirstString)
.orElseGet(() -> {
String typeName = beanType.getName();
if (StringUtil.hasText(typeName)) {
return Character.toLowerCase(typeName.charAt(0)) + typeName.substring(1);
}
return null;
});
}
private void addAutomaticallyWired(StringBuilder hover, Annotation annotation, LiveBeansModel beans, LiveBean bean) {
TypeDeclaration typeDecl = ASTUtils.findDeclaringType(annotation);
if (typeDecl != null) {

View File

@@ -93,7 +93,6 @@ public class BootJavaReferencesHandler implements ReferencesHandler {
ASTNode node = NodeFinder.perform(cu, offset, 0);
if (node != null) {
System.out.println("AST node found: " + node.getClass().getName());
return provideReferencesForAnnotation(node, offset, document);
}

View File

@@ -16,9 +16,12 @@ import java.util.Optional;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.ArrayInitializer;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.TypeDeclaration;
@@ -45,13 +48,51 @@ public class ASTUtils {
}
}
public static Optional<String> getValueAttribute(Annotation annotation) {
if (annotation.isSingleMemberAnnotation()) {
SingleMemberAnnotation sma = (SingleMemberAnnotation) annotation;
Expression valueExp = sma.getValue();
if (valueExp instanceof StringLiteral) {
StringLiteral stringLit = (StringLiteral) valueExp;
return Optional.ofNullable(stringLit.getLiteralValue());
public static Optional<Expression> getAttribute(Annotation annotation, String name) {
try {
if (annotation.isSingleMemberAnnotation() && name.equals("value")) {
SingleMemberAnnotation sma = (SingleMemberAnnotation) annotation;
return Optional.ofNullable(sma.getValue());
} else if (annotation.isNormalAnnotation()) {
NormalAnnotation na = (NormalAnnotation) annotation;
Object attributeObjs = na.getStructuralProperty(NormalAnnotation.VALUES_PROPERTY);
if (attributeObjs instanceof List) {
for (Object atrObj : (List<?>)attributeObjs) {
if (atrObj instanceof MemberValuePair) {
MemberValuePair mvPair = (MemberValuePair) atrObj;
if (name.equals(mvPair.getName().getIdentifier())) {
return Optional.ofNullable(mvPair.getValue());
}
}
}
}
}
} catch (Exception e) {
Log.log(e);
}
return Optional.empty();
}
/**
* For case where a expression can be either a String or a array of Strings and
* we are interested in the first element of the array. (I.e. typical case
* when annotation attribute is of type String[] (because Java allows using a single
* value as a convenient syntax for writing an array of length 1 in that case.
*/
public static Optional<String> getFirstString(Expression exp) {
if (exp instanceof StringLiteral) {
return Optional.ofNullable(((StringLiteral) exp).getLiteralValue());
} else if (exp instanceof ArrayInitializer) {
ArrayInitializer array = (ArrayInitializer) exp;
Object objs = array.getStructuralProperty(ArrayInitializer.EXPRESSIONS_PROPERTY);
if (objs instanceof List) {
List<?> list = (List<?>) objs;
if (!list.isEmpty()) {
Object firstObj = list.get(0);
if (firstObj instanceof Expression) {
return getFirstString((Expression) firstObj);
}
}
}
}
return Optional.empty();
@@ -79,28 +120,19 @@ 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();
}
}
public static MethodDeclaration getAnnotatedMethod(Annotation annotation) {
ASTNode parent = annotation.getParent();
if (parent instanceof MethodDeclaration) {
return (MethodDeclaration)parent;
}
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);
public static TypeDeclaration getAnnotatedType(Annotation annotation) {
ASTNode parent = annotation.getParent();
if (parent instanceof TypeDeclaration) {
return (TypeDeclaration)parent;
}
return null;
}

View File

@@ -8,7 +8,7 @@
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.beans;
package org.springframework.ide.vscode.boot.java.livehover;
import java.util.Collection;
import java.util.List;
@@ -20,16 +20,10 @@ 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.MarkerAnnotation;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.Range;
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;
@@ -38,14 +32,14 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
import com.google.common.collect.ImmutableList;
public class ComponentInjectionsHoverProvider implements HoverProvider {
public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider {
@Override
public Collection<Range> 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 = ASTUtils.getDefinedBean(annotation);
LiveBean definedBean = getDefinedBean(annotation);
if (definedBean != null) {
if (Stream.of(runningApps).anyMatch(app -> LiveHoverUtils.hasRelevantBeans(app, definedBean))) {
Optional<Range> nameRange = ASTUtils.nameRange(doc, annotation);
@@ -66,7 +60,7 @@ public class ComponentInjectionsHoverProvider implements HoverProvider {
TextDocument doc, SpringBootApp[] runningApps) {
if (runningApps.length > 0) {
LiveBean definedBean = ASTUtils.getDefinedBean(annotation);
LiveBean definedBean = getDefinedBean(annotation);
if (definedBean != null) {
StringBuilder hover = new StringBuilder();
hover.append("**Injection report for " + LiveHoverUtils.showBean(definedBean) + "**\n\n");
@@ -86,7 +80,7 @@ public class ComponentInjectionsHoverProvider implements HoverProvider {
for (LiveBean bean : relevantBeans) {
addInjectedInto(definedBean, hover, beans, bean);
addAutomaticallyWired(hover, annotation, beans, bean);
addAutomaticallyWiredContructor(hover, annotation, beans, bean);
}
}
}
@@ -100,13 +94,21 @@ public class ComponentInjectionsHoverProvider implements HoverProvider {
return null;
}
private void addInjectedInto(LiveBean definedBean, StringBuilder hover, LiveBeansModel beans, LiveBean bean) {
protected abstract LiveBean getDefinedBean(Annotation annotation);
protected void addAutomaticallyWiredContructor(StringBuilder hover, Annotation annotation, LiveBeansModel beans, LiveBean bean) {
//This doesn't really belong here, but it accomodates Martin's additional logic to handle implicitly
//@Autowired constructor.
//This does nothing by default as its really only relevant to @Component annotation report.
}
protected void addInjectedInto(LiveBean definedBean, StringBuilder hover, LiveBeansModel beans, LiveBean bean) {
hover.append("\n\n");
List<LiveBean> dependers = beans.getBeansDependingOn(bean.getId());
if (dependers.isEmpty()) {
hover.append(LiveHoverUtils.showBean(bean) + " exists but is **Not injected anywhere**\n");
} else {
hover.append(LiveHoverUtils.showBean(definedBean) + " injected into:\n\n");
hover.append(LiveHoverUtils.showBean(bean) + " injected into:\n\n");
boolean firstDependency = true;
for (LiveBean dependingBean : dependers) {
if (!firstDependency) {
@@ -117,45 +119,4 @@ public class ComponentInjectionsHoverProvider implements HoverProvider {
}
}
}
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(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 boolean hasAutowiredAnnotation(MethodDeclaration constructor) {
List<?> modifiers = constructor.modifiers();
for (Object modifier : modifiers) {
if (modifier instanceof MarkerAnnotation) {
ITypeBinding typeBinding = ((MarkerAnnotation) modifier).resolveTypeBinding();
if (typeBinding != null && typeBinding.getQualifiedName().equals(Constants.SPRING_AUTOWIRED)) {
return true;
}
}
}
return false;
}
}

View File

@@ -0,0 +1,65 @@
/*******************************************************************************
* 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.Optional;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBean;
import org.springframework.ide.vscode.commons.util.Optionals;
public class BeanInjectedIntoHoverProvider extends AbstractInjectedIntoHoverProvider {
public static final String ANNOTATION = "org.springframework.context.annotation.Bean";
@Override
protected LiveBean getDefinedBean(Annotation annotation) {
MethodDeclaration beanMethod = ASTUtils.getAnnotatedMethod(annotation);
if (beanMethod!=null) {
Optional<String> beanId = getBeanId(annotation, beanMethod);
if (beanId.isPresent()) {
//TODO: we could try to be more precise here and determine the bean type from the
//ITypeBinding beanType = null; //null means unknown
// method signature, however, this will typically give us a more abstract type than
// the actual bean type at runtime. So if we we do that we have to deal with that
// somehow. Therefore, for the time being we leave the beanType as `unknown` and
// so do not use the bean type in determining relevant beans.
// Type unresolvedBeanType = beanMethod.getReturnType2();
// if (unresolvedBeanType!=null) {
// beanType = unresolvedBeanType.resolveBinding();
// }
return LiveBean.builder()
.id(beanId.get())
// .type(type)
.build();
}
}
return null;
}
private Optional<String> getBeanId(Annotation annotation, MethodDeclaration beanMethod) {
//Note: must handle all these cases:
// @Bean
// @Bean("beanId")
// @Bean({"beanId", "alias1"})
// @Bean(value="beanId")
// @Bean(value={"beanId", "alias1"})
// @Bean(name="beanId", ...)
// @Bean(name={"beanId", "alias1"}, ...)
return Optionals.tryInOrder(
() -> ASTUtils.getAttribute(annotation, "value").flatMap(ASTUtils::getFirstString),
() -> ASTUtils.getAttribute(annotation, "name").flatMap(ASTUtils::getFirstString),
() -> Optional.ofNullable(beanMethod.getName().getIdentifier())
);
}
}

View File

@@ -0,0 +1,93 @@
/*******************************************************************************
* 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.List;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.springframework.ide.vscode.boot.java.autowired.Constants;
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.StringUtil;
public class ComponentInjectionsHoverProvider extends AbstractInjectedIntoHoverProvider {
@Override protected void addAutomaticallyWiredContructor(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(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 boolean hasAutowiredAnnotation(MethodDeclaration constructor) {
List<?> modifiers = constructor.modifiers();
for (Object modifier : modifiers) {
if (modifier instanceof MarkerAnnotation) {
ITypeBinding typeBinding = ((MarkerAnnotation) modifier).resolveTypeBinding();
if (typeBinding != null && typeBinding.getQualifiedName().equals(Constants.SPRING_AUTOWIRED)) {
return true;
}
}
}
return false;
}
@Override
protected LiveBean getDefinedBean(Annotation annotation) {
TypeDeclaration declaringType = ASTUtils.getAnnotatedType(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;
}
private String getBeanId(Annotation annotation, ITypeBinding beanType) {
return ASTUtils.getAttribute(annotation, "value").flatMap(ASTUtils::getFirstString)
.orElseGet(() -> {
String typeName = beanType.getName();
if (StringUtil.hasText(typeName)) {
return Character.toLowerCase(typeName.charAt(0)) + typeName.substring(1);
}
return null;
});
}
}

View File

@@ -14,11 +14,18 @@ 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;
import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel;
public class LiveHoverUtils {
public static String showBean(LiveBean bean) {
return "Bean [id: " + bean.getId() + ", type: `" + bean.getType() + "`]";
StringBuilder buf = new StringBuilder("Bean [id: " + bean.getId());
String type = bean.getType();
if (type!=null) {
buf.append(", type: `"+type+"`");
}
buf.append(']');
return buf.toString();
}
public static String niceAppName(SpringBootApp app) {
@@ -30,8 +37,16 @@ public class LiveHoverUtils {
}
public static Stream<LiveBean> findRelevantBeans(SpringBootApp app, LiveBean definedBean) {
return app.getBeans().getBeansOfName(definedBean.getId()).stream()
.filter(bean -> definedBean.getType().equals(bean.getType()));
LiveBeansModel beansModel = app.getBeans();
if (beansModel!=null) {
Stream<LiveBean> relevantBeans = beansModel.getBeansOfName(definedBean.getId()).stream();
String type = definedBean.getType();
if (type!=null) {
relevantBeans = relevantBeans.filter(bean -> type.equals(bean.getType()));
}
return relevantBeans;
}
return Stream.empty();
}
}

View File

@@ -105,6 +105,9 @@ public class SpringIndexer {
}
public CompletableFuture<Void> initialize(final Path workspaceRoot) {
if (workspaceRoot==null) {
return CompletableFuture.completedFuture(null);
}
synchronized(this) {
if (this.initializeTask == null) {
initializing.set(true);
@@ -114,9 +117,7 @@ public class SpringIndexer {
this.initializeTask = CompletableFuture.runAsync(new Runnable() {
@Override
public void run() {
System.out.println("start initial scan...");
scanFiles(workspaceRoot.toFile());
System.out.println("initial scan done...!!!");
SpringIndexer.this.updateQueue = new LinkedBlockingQueue<>();
SpringIndexer.this.updateWorker = new Thread(new Runnable() {

View File

@@ -0,0 +1,334 @@
/*******************************************************************************
* 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.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 BeanInjectedIntoHoverProviderTest {
private static final ProjectCustomizer FOO_INTERFACE = (CustomizableProjectContent p) -> {
p.createType("hello.Foo",
"package hello;\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("hello.Foo").exists());
harness.useProject(jp);
harness.intialize(null);
}
@Test
public void beanWithNoInjections() throws Exception {
LiveBeansModel beans = LiveBeansModel.builder()
.add(LiveBean.builder()
.id("myFoo")
.type("hello.FooImplementation")
.build()
)
.build();
mockAppProvider.builder()
.isSpringBootApp(true)
.processId("111")
.processName("the-app")
.beans(beans)
.build();
Editor editor = harness.newEditor(LanguageId.JAVA,
"package hello;\n" +
"\n" +
"import org.springframework.context.annotation.Bean;\n" +
"import org.springframework.context.annotation.Configuration;\n" +
"import org.springframework.context.annotation.Profile;\n" +
"\n" +
"@Configuration\n" +
"public class LocalConfig {\n" +
" \n" +
" @Bean\n" +
" Foo myFoo() {\n" +
" return new FooImplementation();\n" +
" }\n" +
"}"
);
editor.assertHighlights("@Bean");
editor.assertTrimmedHover("@Bean",
"**Injection report for Bean [id: myFoo]**\n" +
"\n" +
"Process [PID=111, name=`the-app`]:\n" +
"\n" +
"Bean [id: myFoo, type: `hello.FooImplementation`] exists but is **Not injected anywhere**\n"
);
}
@Test
public void explicitIdCases() throws Exception {
LiveBeansModel beans = LiveBeansModel.builder()
.add(LiveBean.builder()
.id("beanId")
.type("hello.FooImplementation")
.build()
)
.add(LiveBean.builder()
.id("irrelevantBean") //wrong id
.type("hello.Foo") //right type (but should be ignored!)
.build()
)
.build();
mockAppProvider.builder()
.isSpringBootApp(true)
.processId("111")
.processName("the-app")
.beans(beans)
.build();
String[] beanAnnotations = {
"@Bean(value=\"beanId\")",
"@Bean(value=\"beanId\", destroyMethod=\"cleanup\")",
"@Bean(value= {\"beanId\", \"alias\"}, destroyMethod=\"cleanup\")",
"@Bean(name=\"beanId\")",
"@Bean(name=\"beanId\", destroyMethod=\"cleanup\")",
"@Bean(name= {\"beanId\", \"alias\"}, destroyMethod=\"cleanup\")",
"@Bean(\"beanId\")",
"@Bean({\"beanId\", \"alias\"})",
};
for (String beanAnnotation : beanAnnotations) {
Editor editor = harness.newEditor(
"package hello;\n" +
"\n" +
"import org.springframework.context.annotation.Bean;\n" +
"import org.springframework.context.annotation.Configuration;\n" +
"import org.springframework.context.annotation.Profile;\n" +
"\n" +
"@Configuration\n" +
"public class LocalConfig {\n" +
" \n" +
" "+beanAnnotation+"\n" +
" Foo otherFoo() {\n" +
" return new FooImplementation();\n" +
" }\n" +
"}"
);
editor.assertHighlights("@Bean");
editor.assertTrimmedHover("@Bean",
"**Injection report for Bean [id: beanId]**\n" +
"\n" +
"Process [PID=111, name=`the-app`]:\n" +
"\n" +
"Bean [id: beanId, type: `hello.FooImplementation`] exists but is **Not injected anywhere**\n"
);
}
}
@Test
public void beanWithOneInjection() throws Exception {
LiveBeansModel beans = LiveBeansModel.builder()
.add(LiveBean.builder()
.id("fooImplementation")
.type("hello.FooImplementation")
.build()
)
.add(LiveBean.builder()
.id("myController")
.type("hello.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 hello;\n" +
"\n" +
"import org.springframework.context.annotation.Bean;\n" +
"import org.springframework.context.annotation.Configuration;\n" +
"import org.springframework.context.annotation.Profile;\n" +
"\n" +
"@Configuration\n" +
"public class LocalConfig {\n" +
" \n" +
" @Bean(\"fooImplementation\")\n" +
" Foo someFoo() {\n" +
" return new FooImplementation();\n" +
" }\n" +
"}"
);
editor.assertHighlights("@Bean");
editor.assertTrimmedHover("@Bean",
"**Injection report for Bean [id: fooImplementation]**\n" +
"\n" +
"Process [PID=111, name=`the-app`]:\n" +
"\n" +
"Bean [id: fooImplementation, type: `hello.FooImplementation`] injected into:\n" +
"\n" +
"- Bean [id: myController, type: `hello.MyController`]\n"
);
}
@Test
public void beanWithMultipleInjections() throws Exception {
LiveBeansModel beans = LiveBeansModel.builder()
.add(LiveBean.builder()
.id("fooImplementation")
.type("hello.FooImplementation")
.build()
)
.add(LiveBean.builder()
.id("myController")
.type("hello.MyController")
.dependencies("fooImplementation")
.build()
)
.add(LiveBean.builder()
.id("otherBean")
.type("hello.OtherBean")
.dependencies("fooImplementation")
.build()
)
.build();
mockAppProvider.builder()
.isSpringBootApp(true)
.processId("111")
.processName("the-app")
.beans(beans)
.build();
Editor editor = harness.newEditor(LanguageId.JAVA,
"package hello;\n" +
"\n" +
"import org.springframework.context.annotation.Bean;\n" +
"import org.springframework.context.annotation.Configuration;\n" +
"import org.springframework.context.annotation.Profile;\n" +
"\n" +
"@Configuration\n" +
"public class LocalConfig {\n" +
" \n" +
" @Bean(\"fooImplementation\")\n" +
" Foo someFoo() {\n" +
" return new FooImplementation();\n" +
" }\n" +
"}"
);
editor.assertHighlights("@Bean");
editor.assertTrimmedHover("@Bean",
"**Injection report for Bean [id: fooImplementation]**\n" +
"\n" +
"Process [PID=111, name=`the-app`]:\n" +
"\n" +
"Bean [id: fooImplementation, type: `hello.FooImplementation`] injected into:\n" +
"\n" +
"- Bean [id: myController, type: `hello.MyController`]\n" +
"- Bean [id: otherBean, type: `hello.OtherBean`]\n"
);
}
@Test
public void noHoversWhenRunningAppDoesntHaveTheBean() throws Exception {
LiveBeansModel beans = LiveBeansModel.builder()
.add(LiveBean.builder()
.id("whateverBean")
.type("com.example.UnrelatedBeanType")
.build()
)
.build();
mockAppProvider.builder()
.isSpringBootApp(true)
.processId("111")
.processName("unrelated-app")
.beans(beans)
.build();
Editor editor = harness.newEditor(LanguageId.JAVA,
"package hello;\n" +
"\n" +
"import org.springframework.context.annotation.Bean;\n" +
"import org.springframework.context.annotation.Configuration;\n" +
"import org.springframework.context.annotation.Profile;\n" +
"\n" +
"@Configuration\n" +
"public class LocalConfig {\n" +
" \n" +
" @Bean(\"fooImplementation\")\n" +
" Foo someFoo() {\n" +
" return new FooImplementation();\n" +
" }\n" +
"}"
);
editor.assertHighlights(/*NONE*/);
editor.assertNoHover("@Bean");
}
@Test
public void noHoversWhenNoRunningApps() throws Exception {
Editor editor = harness.newEditor(LanguageId.JAVA,
"package hello;\n" +
"\n" +
"import org.springframework.context.annotation.Bean;\n" +
"import org.springframework.context.annotation.Configuration;\n" +
"import org.springframework.context.annotation.Profile;\n" +
"\n" +
"@Configuration\n" +
"public class LocalConfig {\n" +
" \n" +
" @Bean(\"fooImplementation\")\n" +
" Foo someFoo() {\n" +
" return new FooImplementation();\n" +
" }\n" +
"}"
);
editor.assertHighlights(/*NONE*/);
editor.assertNoHover("@Bean");
}
}

View File

@@ -8,7 +8,7 @@
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.beans.test;
package org.springframework.ide.vscode.boot.java.livehover.test;
import static org.junit.Assert.assertTrue;
@@ -29,7 +29,7 @@ import org.springframework.ide.vscode.project.harness.ProjectsHarness.ProjectCus
public class ComponentInjectionsHoverProviderTest {
private static final ProjectCustomizer FOO_INTERFACE = (CustomizableProjectContent p) -> {
private static final ProjectCustomizer EXTRA_TYPES = (CustomizableProjectContent p) -> {
p.createType("com.examle.Foo",
"package com.example;\n" +
"\n" +
@@ -68,9 +68,9 @@ public class ComponentInjectionsHoverProviderTest {
.watchDogInterval(Duration.ofMillis(100))
.build();
MavenJavaProject jp = projects.mavenProject("empty-boot-15-web-app", FOO_INTERFACE);
MavenJavaProject jp = projects.mavenProject("empty-boot-15-web-app", EXTRA_TYPES);
assertTrue(jp.getClasspath().findType("com.example.Foo").exists());
harness.useProject(projects.mavenProject("empty-boot-15-web-app"));
harness.useProject(jp);
harness.intialize(null);
}