enable live hovers for inner classes

This commit is contained in:
Martin Lippert
2017-12-07 21:33:41 +01:00
parent f6739ad44a
commit c102efa838
8 changed files with 258 additions and 29 deletions

View File

@@ -92,6 +92,13 @@ public class ComponentInjectionsHoverProvider extends AbstractInjectedIntoHoverP
return ASTUtils.getAttribute(annotation, "value").flatMap(ASTUtils::getFirstString)
.orElseGet(() -> {
String typeName = beanType.getName();
ITypeBinding declaringClass = beanType.getDeclaringClass();
while (declaringClass != null) {
typeName = declaringClass.getName() + "." + typeName;
declaringClass = declaringClass.getDeclaringClass();
}
if (StringUtil.hasText(typeName)) {
return Character.toLowerCase(typeName.charAt(0)) + typeName.substring(1);
}

View File

@@ -23,9 +23,9 @@ public class LiveHoverUtils {
public static String showBean(LiveBean bean) {
StringBuilder buf = new StringBuilder("Bean [id: " + bean.getId());
String type = bean.getType();
if (type!=null) {
buf.append(", type: `"+type+"`");
String type = bean.getType(true);
if (type != null) {
buf.append(", type: `" + type + "`");
}
buf.append(']');
return buf.toString();
@@ -35,10 +35,10 @@ public class LiveHoverUtils {
String newline = " \n"+indentStr; //Note: the double space before newline makes markdown see it as a real line break
StringBuilder buf = new StringBuilder("Bean: ");
buf.append(bean.getId());
String type = bean.getType();
if (type!=null) {
String type = bean.getType(true);
if (type != null) {
buf.append(newline);
buf.append("Type: `"+type+"`");
buf.append("Type: `" + type + "`");
}
String resource = bean.getResource();
if (StringUtil.hasText(resource)) {

View File

@@ -119,19 +119,28 @@ public class RequestMappingHoverProvider implements HoverProvider {
private boolean methodMatchesAnnotation(Annotation annotation, RequestMapping rm) {
String rqClassName = rm.getFullyQualifiedClassName();
ASTNode parent = annotation.getParent();
if (parent instanceof MethodDeclaration) {
MethodDeclaration methodDec = (MethodDeclaration) parent;
IMethodBinding binding = methodDec.resolveBinding();
return binding.getDeclaringClass().getQualifiedName().equals(rqClassName)
&& binding.getName().equals(rm.getMethodName())
&& Arrays.equals(Arrays.stream(binding.getParameterTypes())
.map(t -> t.getTypeDeclaration().getQualifiedName())
.toArray(String[]::new),
rm.getMethodParameters());
// } else if (parent instanceof TypeDeclaration) {
// TypeDeclaration typeDec = (TypeDeclaration) parent;
// return typeDec.resolveBinding().getQualifiedName().equals(rqClassName);
if (rqClassName != null) {
int chop = rqClassName.indexOf("$$EnhancerBySpringCGLIB$$");
if (chop >= 0) {
rqClassName = rqClassName.substring(0, chop);
}
rqClassName = rqClassName.replace('$', '.');
ASTNode parent = annotation.getParent();
if (parent instanceof MethodDeclaration) {
MethodDeclaration methodDec = (MethodDeclaration) parent;
IMethodBinding binding = methodDec.resolveBinding();
return binding.getDeclaringClass().getQualifiedName().equals(rqClassName)
&& binding.getName().equals(rm.getMethodName())
&& Arrays.equals(Arrays.stream(binding.getParameterTypes())
.map(t -> t.getTypeDeclaration().getQualifiedName())
.toArray(String[]::new),
rm.getMethodParameters());
// } else if (parent instanceof TypeDeclaration) {
// TypeDeclaration typeDec = (TypeDeclaration) parent;
// return typeDec.resolveBinding().getQualifiedName().equals(rqClassName);
}
}
return false;
}

View File

@@ -219,6 +219,66 @@ public class BeanInjectedIntoHoverProviderTest {
);
}
@Test
public void beanFromInnerClassWithOneInjection() 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" +
"public class OuterClass {\n" +
" \n" +
" @Configuration\n" +
" public static class InnerClass {\n" +
" \n" +
" @Bean(\"fooImplementation\")\n" +
" Foo someFoo() {\n" +
" return new FooImplementation();\n" +
" }\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: myController \n" +
" Type: `hello.MyController`\n"
);
}
@Test
public void beanWithFileResource() throws Exception {
Path of = harness.getOutputFolder();

View File

@@ -585,7 +585,8 @@ public class ComponentInjectionsHoverProviderTest {
);
}
@Test public void bug_153072942_SpringBootApplication__withCGLib_is_a_Component() throws Exception {
@Test
public void bug_153072942_SpringBootApplication__withCGLib_is_a_Component() throws Exception {
LiveBeansModel beans = LiveBeansModel.builder()
.add(LiveBean.builder()
.id("demoApplication")
@@ -623,4 +624,88 @@ public class ComponentInjectionsHoverProviderTest {
);
}
@Test
public void componentFromInnerClass() throws Exception {
LiveBeansModel beans = LiveBeansModel.builder()
.add(LiveBean.builder()
.id("demoApplication.InnerClass")
.type("com.example.DemoApplication$InnerClass")
.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.boot.SpringApplication;\n" +
"import org.springframework.boot.autoconfigure.SpringBootApplication;\n" +
"import org.springframework.context.annotation.Bean;\n" +
"import org.springframework.web.client.RestTemplate;\n" +
"\n" +
"public class DemoApplication {\n" +
" \n" +
" @SpringBootApplication\n" +
" public static class InnerClass {\n" +
" \n" +
" public static void main(String[] args) {\n" +
" SpringApplication.run(DemoApplication.InnerClass.class, args);\n" +
" }\n" +
" }\n" +
"}\n"
);
editor.assertHighlights("@SpringBootApplication");
editor.assertHoverContains("@SpringBootApplication",
"**Injection report for Bean [id: demoApplication.InnerClass, type: `com.example.DemoApplication.InnerClass`]**"
);
}
@Test
public void componentFromInnerInnerClass() throws Exception {
LiveBeansModel beans = LiveBeansModel.builder()
.add(LiveBean.builder()
.id("demoApplication.InnerClass.InnerInnerClass")
.type("com.example.DemoApplication$InnerClass$InnerInnerClass")
.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.boot.SpringApplication;\n" +
"import org.springframework.boot.autoconfigure.SpringBootApplication;\n" +
"import org.springframework.context.annotation.Bean;\n" +
"import org.springframework.web.client.RestTemplate;\n" +
"\n" +
"public class DemoApplication {\n" +
" \n" +
" public static class InnerClass {\n" +
" \n" +
" @SpringBootApplication\n" +
" public static class InnerInnerClass {\n" +
" \n" +
" public static void main(String[] args) {\n" +
" SpringApplication.run(DemoApplication.InnerClass.InnerInnerClass.class, args);\n" +
" }\n" +
" }\n" +
" }\n" +
"}\n"
);
editor.assertHighlights("@SpringBootApplication");
editor.assertHoverContains("@SpringBootApplication",
"**Injection report for Bean [id: demoApplication.InnerClass.InnerInnerClass, type: `com.example.DemoApplication.InnerClass.InnerInnerClass`]**"
);
}
}

View File

@@ -64,6 +64,7 @@ public class RequestMappingLiveHoverTest {
harness.intialize(directory);
Editor editor = harness.newEditorFromFileUri(docUri, LanguageId.JAVA);
editor.assertHighlights("@RequestMapping(method=RequestMethod.GET)");
editor.assertHoverContains("@RequestMapping(method=RequestMethod.GET)", "[http://cfapps.io:1111/hello-world](http://cfapps.io:1111/hello-world)\n" +
"\n" +
"Process [PID=22022, name=`test-request-mapping-live-hover`]");
@@ -94,6 +95,8 @@ public class RequestMappingLiveHoverTest {
harness.intialize(directory);
Editor editor = harness.newEditorFromFileUri(docUri, LanguageId.JAVA);
editor.assertHighlights("@RequestMapping(\"/hello\")", "@RequestMapping(\"/goodbye\")");
editor.assertHoverContains("@RequestMapping(\"/hello\")", "[http://cfapps.io:999/hello](http://cfapps.io:999/hello)\n" +
"\n" +
"Process [PID=76543, name=`test-request-mapping-live-hover`]");
@@ -214,12 +217,8 @@ public class RequestMappingLiveHoverTest {
docUri);
editor.assertNoHover("@DeleteMapping(\"/greetings\")");
}
@Test
public void testNoHoverHintMethod() throws Exception {
@@ -261,7 +260,6 @@ public class RequestMappingLiveHoverTest {
docUri);
editor.assertNoHover("@PutMapping(\"/greetings\")");
}
@Test
@@ -756,4 +754,39 @@ public class RequestMappingLiveHoverTest {
}
@Test
public void testMappingHoversInInnerClasses() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI());
String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/InnerClassController.java";
// Build a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("1111")
.processId("22022")
.host("cfapps.io")
.processName("test-request-mapping-live-hover")
// Ugly, but this is real JSON copied from a real live running app. We want the
// mock app to return realistic results if possible
.requestMappings(
"{\"/webjars/**\":{\"bean\":\"resourceHandlerMapping\"},\"/**\":{\"bean\":\"resourceHandlerMapping\"},\"/**/favicon.ico\":{\"bean\":\"faviconHandlerMapping\"},\"{[/hello-world],methods=[GET]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public example.Greeting example.HelloWorldController.sayHello(java.lang.String)\"},\"{[/inner-inner-class]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public java.lang.String example.InnerClassController$InnerController$InnerInnerController.saySomethingSuperInnerClass()\"},\"{[/inner-class]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public java.lang.String example.InnerClassController$InnerController.saySomething()\"},\"{[/person/{name}],methods=[GET]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public java.lang.String example.RestApi.getMapping(java.lang.String)\"},\"{[/delete/{id}],methods=[DELETE]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public java.lang.String example.RestApi.removeMe(int)\"},\"{[/goodbye]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public java.lang.String example.RestApi.goodbye()\"},\"{[/hello]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public java.lang.String example.RestApi.hello()\"},\"{[/postHello],methods=[POST]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public java.lang.String example.RestApi.postMethod(java.lang.String)\"},\"{[/put/{id}],methods=[PUT]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public java.lang.String example.RestApi.putMethod(int,java.lang.String)\"},\"{[/error]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)\"},\"{[/error],produces=[text/html]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)\"}}")
.build();
harness.intialize(directory);
Editor editor = harness.newEditorFromFileUri(docUri, LanguageId.JAVA);
editor.assertHighlights("@RequestMapping(\"/inner-class\")", "@RequestMapping(\"/inner-inner-class\")");
editor.assertHoverContains("@RequestMapping(\"/inner-class\")", "[http://cfapps.io:1111/inner-class](http://cfapps.io:1111/inner-class)\n" +
"\n" +
"Process [PID=22022, name=`test-request-mapping-live-hover`]");
editor.assertHoverContains("@RequestMapping(\"/inner-inner-class\")", "[http://cfapps.io:1111/inner-inner-class](http://cfapps.io:1111/inner-inner-class)\n" +
"\n" +
"Process [PID=22022, name=`test-request-mapping-live-hover`]");
}
}

View File

@@ -0,0 +1,28 @@
package example;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
public class InnerClassController {
@RestController
public static class InnerController {
@RequestMapping("/inner-class")
public String saySomething() {
return "Hello from Inner-Class";
}
@RestController
public static class InnerInnerController {
@RequestMapping("/inner-inner-class")
public String saySomethingSuperInnerClass() {
return "Hello from Inner-Inner-Class";
}
}
}
}