Injection report on live hover over @Bean
This commit is contained in:
@@ -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());
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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())
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user