Add more test cases for @Component hovers.

Handle the case where bean id is set explicitly in the annotation.
This commit is contained in:
Kris De Volder
2017-10-27 16:58:36 -07:00
parent 68de1dd0c5
commit f95660cfcb
6 changed files with 419 additions and 48 deletions

View File

@@ -34,8 +34,8 @@ import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
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.InjectedIntoProvider;
import org.springframework.ide.vscode.boot.java.profiles.ActiveProfilesProvider;
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;

View File

@@ -0,0 +1,52 @@
/*******************************************************************************
* 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.Expression;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.lsp4j.Range;
import org.springframework.ide.vscode.commons.util.Log;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
public class ASTUtils {
public static Optional<Range> nameRange(TextDocument doc, Annotation annotation) {
try {
int start = annotation.getTypeName().getStartPosition();
int len = annotation.getTypeName().getLength();
if (doc.getSafeChar(start-1)=='@') {
start--; len++;
}
return Optional.of(doc.toRange(start, len));
} catch (Exception e) {
Log.log(e);
return Optional.empty();
}
}
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());
}
}
return Optional.empty();
}
}

View File

@@ -8,7 +8,7 @@
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.profiles;
package org.springframework.ide.vscode.boot.java.livehover;
import java.util.Collection;
import java.util.List;
@@ -33,6 +33,8 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableSet;
import static org.springframework.ide.vscode.boot.java.livehover.ASTUtils.*;
/**
* @author Kris De Volder
*/
@@ -109,20 +111,6 @@ public class ActiveProfilesProvider implements HoverProvider {
return builder.build();
}
private static Optional<Range> nameRange(TextDocument doc, Annotation annotation) {
try {
int start = annotation.getTypeName().getStartPosition();
int len = annotation.getTypeName().getLength();
if (doc.getSafeChar(start-1)=='@') {
start--; len++;
}
return Optional.of(doc.toRange(start, len));
} catch (Exception e) {
Log.log(e);
return Optional.empty();
}
}
private static Optional<Range> rangeOf(TextDocument doc, StringLiteral node) {
try {
int start = node.getStartPosition();

View File

@@ -12,6 +12,7 @@ package org.springframework.ide.vscode.boot.java.livehover;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -19,6 +20,7 @@ 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.Name;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.Range;
@@ -28,6 +30,7 @@ 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;
@@ -39,10 +42,13 @@ public class InjectedIntoProvider implements HoverProvider {
//Highlight if any running app contains an instance of this component
try {
if (runningApps.length > 0) {
String beanType = getAnnotatedType(annotation);
if (beanType!=null) {
if (Stream.of(runningApps).anyMatch(app -> !app.getBeans().getBeansOfType(beanType).isEmpty())) {
return ImmutableList.of(doc.toRange(annotation.getStartPosition(), annotation.getLength()));
LiveBean definedBean = getDefinedBean(annotation);
if (definedBean!=null) {
if (Stream.of(runningApps).anyMatch(app -> hasRelevantBeans(app, definedBean))) {
Optional<Range> nameRange = ASTUtils.nameRange(doc, annotation);
if (nameRange.isPresent()) {
return ImmutableList.of(nameRange.get());
}
}
}
}
@@ -52,31 +58,63 @@ public class InjectedIntoProvider 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) {
String beanType = getAnnotatedType(annotation);
if (beanType!=null) {
LiveBean definedBean = getDefinedBean(annotation);
if (definedBean!=null) {
StringBuilder hover = new StringBuilder();
hover.append("**Injection report for `"+beanType+"'**\n\n");
hover.append("**Injection report for "+showBean(definedBean)+"**\n\n");
boolean hasInterestingApp = false;
for (SpringBootApp app : runningApps) {
LiveBeansModel beans = app.getBeans();
boolean isInteresting = !app.getBeans().getBeansOfType(beanType).isEmpty();
if (isInteresting) {
hasInterestingApp = true;
hover.append(niceAppName(app)+":");
for (LiveBean bean : beans.getBeansOfType(beanType)) {
List<LiveBean> relevantBeans = findRelevantBeans(app, definedBean).collect(Collectors.toList());
if (!relevantBeans.isEmpty()) {
if (!hasInterestingApp) {
hasInterestingApp = true;
} else {
hover.append("\n\n");
}
hover.append(niceAppName(app)+":");
for (LiveBean bean : relevantBeans) {
hover.append("\n\n");
hover.append("Bean with id: `"+bean.getId()+"`\n");
List<LiveBean> dependers = beans.getBeansDependingOn(bean.getId());
if (dependers.isEmpty()) {
hover.append("**Not injected anywhere**\n");
hover.append(showBean(bean)+" exists but is **Not injected anywhere**");
} else {
hover.append("injected into:\n\n");
hover.append(showBean(definedBean) + " injected into:\n\n");
boolean firstDependency = true;
for (LiveBean dependingBean : dependers) {
hover.append("- "+dependingBean.getType()+"\n");
if (!firstDependency) {
hover.append("\n");
}
hover.append("- "+showBean(dependingBean));
firstDependency = false;
}
}
}
@@ -93,14 +131,27 @@ public class InjectedIntoProvider implements HoverProvider {
return null;
}
private String getAnnotatedType(Annotation annotation) {
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;
ITypeBinding binding = typeDecl.resolveBinding();
if (binding!=null) {
return binding.getQualifiedName();
}
return typeDecl.resolveBinding();
}
return null;
}

View File

@@ -89,19 +89,16 @@ public class InjectedIntoProviderTest {
"}\n"
);
editor.assertHighlights("@Component");
editor.assertHoverExactText("@Component",
"**Injection report for `com.example.FooImplementation'**\n" +
editor.assertTrimmedHover("@Component",
"**Injection report for Bean [id: fooImplementation, type: `com.example.FooImplementation`]**\n" +
"\n" +
"Process [PID=111, name=`the-app`]:\n" +
"\n" +
"Bean with id: `fooImplementation`\n" +
"**Not injected anywhere**\n"
"Bean [id: fooImplementation, type: `com.example.FooImplementation`] exists but is **Not injected anywhere**\n"
);
}
@Test public void componentWithOneInjection() throws Exception {
//Like componentWithNoInjections but checks whether we are properly using the bean id
// to determine injections.
LiveBeansModel beans = LiveBeansModel.builder()
.add(LiveBean.builder()
.id("fooImplementation")
@@ -114,6 +111,65 @@ public class InjectedIntoProviderTest {
.dependencies("fooImplementation")
.build()
)
.add(LiveBean.builder()
.id("irrelevantBean")
.type("com.example.IrrelevantBean")
.dependencies("myController")
.build()
)
.build();
mockAppProvider.builder()
.isSpringBootApp(true)
.processId("111")
.processName("the-app")
.beans(beans)
.build();
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.stereotype.Component;\n" +
"\n" +
"@Component\n" +
"public class FooImplementation implements Foo {\n" +
"\n" +
" @Override\n" +
" public void doSomeFoo() {\n" +
" System.out.println(\"Foo do do do!\");\n" +
" }\n" +
"}\n"
);
editor.assertHighlights("@Component");
editor.assertTrimmedHover("@Component",
"**Injection report for Bean [id: fooImplementation, type: `com.example.FooImplementation`]**\n" +
"\n" +
"Process [PID=111, name=`the-app`]:\n" +
"\n" +
"Bean [id: fooImplementation, type: `com.example.FooImplementation`] injected into:\n" +
"\n" +
"- Bean [id: myController, type: `com.example.MyController`]\n"
);
}
@Test public void componentWithMultipleInjections() throws Exception {
LiveBeansModel beans = LiveBeansModel.builder()
.add(LiveBean.builder()
.id("fooImplementation")
.type("com.example.FooImplementation")
.build()
)
.add(LiveBean.builder()
.id("myController")
.type("com.example.MyController")
.dependencies("fooImplementation")
.build()
)
.add(LiveBean.builder()
.id("otherBean")
.type("com.example.OtherBean")
.dependencies("fooImplementation")
.build()
)
.build();
mockAppProvider.builder()
.isSpringBootApp(true)
@@ -137,18 +193,236 @@ public class InjectedIntoProviderTest {
"}\n"
);
editor.assertHighlights("@Component");
editor.assertHoverExactText("@Component",
"**Injection report for `com.example.FooImplementation'**\n" +
editor.assertTrimmedHover("@Component",
"**Injection report for Bean [id: fooImplementation, type: `com.example.FooImplementation`]**\n" +
"\n" +
"Process [PID=111, name=`the-app`]:\n" +
"\n" +
"Bean with id: `fooImplementation`\n" +
"injected into:\n" +
"Bean [id: fooImplementation, type: `com.example.FooImplementation`] injected into:\n" +
"\n" +
"- com.example.MyController\n"
"- Bean [id: myController, type: `com.example.MyController`]\n" +
"- Bean [id: otherBean, type: `com.example.OtherBean`]\n"
);
}
@Test public void componentWithMultipleInjectionsAndMultipleProcesses() throws Exception {
LiveBeansModel beans = LiveBeansModel.builder()
.add(LiveBean.builder()
.id("fooImplementation")
.type("com.example.FooImplementation")
.build()
)
.add(LiveBean.builder()
.id("myController")
.type("com.example.MyController")
.dependencies("fooImplementation")
.build()
)
.add(LiveBean.builder()
.id("otherBean")
.type("com.example.OtherBean")
.dependencies("fooImplementation")
.build()
)
.build();
for (int i = 1; i <= 2; i++) {
mockAppProvider.builder()
.isSpringBootApp(true)
.processId("100"+i)
.processName("app-instance-"+i)
.beans(beans)
.build();
}
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.stereotype.Component;\n" +
"\n" +
"@Component\n" +
"public class FooImplementation implements Foo {\n" +
"\n" +
" @Override\n" +
" public void doSomeFoo() {\n" +
" System.out.println(\"Foo do do do!\");\n" +
" }\n" +
"}\n"
);
editor.assertHighlights("@Component");
editor.assertTrimmedHover("@Component",
"**Injection report for Bean [id: fooImplementation, type: `com.example.FooImplementation`]**\n" +
"\n" +
"Process [PID=1001, name=`app-instance-1`]:\n" +
"\n" +
"Bean [id: fooImplementation, type: `com.example.FooImplementation`] injected into:\n" +
"\n" +
"- Bean [id: myController, type: `com.example.MyController`]\n" +
"- Bean [id: otherBean, type: `com.example.OtherBean`]\n" +
"\n" +
"Process [PID=1002, name=`app-instance-2`]:\n" +
"\n" +
"Bean [id: fooImplementation, type: `com.example.FooImplementation`] injected into:\n" +
"\n" +
"- Bean [id: myController, type: `com.example.MyController`]\n" +
"- Bean [id: otherBean, type: `com.example.OtherBean`]\n"
);
}
@Test public void onlyShowInfoForRelevantBeanId() throws Exception {
LiveBeansModel beans = LiveBeansModel.builder()
.add(LiveBean.builder()
.id("fooImplementation")
.type("com.example.FooImplementation")
.build()
)
.add(LiveBean.builder()
.id("alternateFooImplementation")
.type("com.example.FooImplementation")
.build()
)
.add(LiveBean.builder()
.id("myController")
.type("com.example.MyController")
.dependencies("fooImplementation")
.build()
)
.add(LiveBean.builder()
.id("otherBean")
.type("com.example.OtherBean")
.dependencies("alternateFooImplementation")
.build()
)
.build();
mockAppProvider.builder()
.isSpringBootApp(true)
.processId("111")
.processName("the-app")
.beans(beans)
.build();
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.stereotype.Component;\n" +
"\n" +
"@Component\n" +
"public class FooImplementation implements Foo {\n" +
"\n" +
" @Override\n" +
" public void doSomeFoo() {\n" +
" System.out.println(\"Foo do do do!\");\n" +
" }\n" +
"}\n"
);
editor.assertHighlights("@Component");
editor.assertHoverExactText("@Component",
"**Injection report for Bean [id: fooImplementation, type: `com.example.FooImplementation`]**\n" +
"\n" +
"Process [PID=111, name=`the-app`]:\n" +
"\n" +
"Bean [id: fooImplementation, type: `com.example.FooImplementation`] injected into:\n" +
"\n" +
"- Bean [id: myController, type: `com.example.MyController`]"
);
}
@Test public void explicitComponentId() throws Exception {
LiveBeansModel beans = LiveBeansModel.builder()
.add(LiveBean.builder()
.id("fooImplementation")
.type("com.example.FooImplementation")
.build()
)
.add(LiveBean.builder()
.id("alternateFooImplementation")
.type("com.example.FooImplementation")
.build()
)
.add(LiveBean.builder()
.id("myController")
.type("com.example.MyController")
.dependencies("fooImplementation")
.build()
)
.add(LiveBean.builder()
.id("otherBean")
.type("com.example.OtherBean")
.dependencies("alternateFooImplementation")
.build()
)
.build();
mockAppProvider.builder()
.isSpringBootApp(true)
.processId("111")
.processName("the-app")
.beans(beans)
.build();
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.stereotype.Component;\n" +
"\n" +
"@Component(\"alternateFooImplementation\")\n" +
"public class FooImplementation implements Foo {\n" +
"\n" +
" @Override\n" +
" public void doSomeFoo() {\n" +
" System.out.println(\"Foo do do do!\");\n" +
" }\n" +
"}\n"
);
editor.assertHighlights("@Component");
editor.assertTrimmedHover("@Component",
"**Injection report for Bean [id: alternateFooImplementation, type: `com.example.FooImplementation`]**\n" +
"\n" +
"Process [PID=111, name=`the-app`]:\n" +
"\n" +
"Bean [id: alternateFooImplementation, type: `com.example.FooImplementation`] injected into:\n" +
"\n" +
"- Bean [id: otherBean, type: `com.example.OtherBean`]\n"
);
}
@Test public void hoHoversWhenRunningAppDoesntHaveTheComponent() throws Exception {
LiveBeansModel beans = LiveBeansModel.builder()
.add(LiveBean.builder()
.id("whateverBean")
.type("com.example.UnrelatedComponent")
.build()
)
.add(LiveBean.builder()
.id("myController")
.type("com.example.UnrelatedComponent")
.dependencies("whateverBean")
.build()
)
.build();
mockAppProvider.builder()
.isSpringBootApp(true)
.processId("111")
.processName("unrelated-app")
.beans(beans)
.build();
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.stereotype.Component;\n" +
"\n" +
"@Component\n" +
"public class FooImplementation implements Foo {\n" +
"\n" +
" @Override\n" +
" public void doSomeFoo() {\n" +
" System.out.println(\"Foo do do do!\");\n" +
" }\n" +
"}\n"
);
editor.assertHighlights(/*MONE*/);
editor.assertNoHover("@Component");
}
@Test public void hoHoversWhenNoRunningApps() throws Exception {
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +