diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java index c870a55a3..bd092c22a 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java @@ -58,7 +58,7 @@ public class RequestMappingHoverProvider implements HoverProvider { public Collection getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { try { if (runningApps.length > 0) { - Optional val = getRequestMappingMethodFromRunningApp(annotation, runningApps); + Optional> val = getRequestMappingMethodFromRunningApp(annotation, runningApps); if (val.isPresent()) { Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength()); return ImmutableList.of(hoverRange); @@ -77,7 +77,7 @@ public class RequestMappingHoverProvider implements HoverProvider { try { List> hoverContent = new ArrayList<>(); - Optional val = getRequestMappingMethodFromRunningApp(annotation, runningApps); + Optional> val = getRequestMappingMethodFromRunningApp(annotation, runningApps); if (val.isPresent()) { addHoverContent(val.get(), hoverContent); @@ -97,13 +97,14 @@ public class RequestMappingHoverProvider implements HoverProvider { return null; } - private Optional getRequestMappingMethodFromRunningApp(Annotation annotation, + private Optional> getRequestMappingMethodFromRunningApp(Annotation annotation, SpringBootApp[] runningApps) { try { + List methods = new ArrayList<>(); for (SpringBootApp app : runningApps) { String mappings = app.getRequestMappings(); - if (mappings!=null) { + if (mappings != null) { JSONObject requestMappings = new JSONObject(mappings); String rawPath = getRawPath(annotation, requestMappings); if (rawPath != null) { @@ -112,12 +113,16 @@ public class RequestMappingHoverProvider implements HoverProvider { String rawMethod = getRawMethod(annotation, requestMappings); JLRMethod parsedMethod = JLRMethodParser.parse(rawMethod); if (methodMatchesAnnotation(annotation, parsedMethod)) { - return Optional.of(new RequestMappingMethod(path, parsedMethod, app)); + methods.add(new RequestMappingMethod(path, parsedMethod, app)); } } } } } + + if (!methods.isEmpty()) { + return Optional.of(methods); + } } catch (Exception e) { Log.log(e); } @@ -141,28 +146,37 @@ public class RequestMappingHoverProvider implements HoverProvider { return false; } - private void addHoverContent(RequestMappingMethod mappingMethod, List> hoverContent) throws Exception { - String processId = mappingMethod.app.getProcessID(); - String processName = mappingMethod.app.getProcessName(); - String path = mappingMethod.requestMappingPath; + private void addHoverContent(List mappingMethods, List> hoverContent) + throws Exception { + for (int i = 0; i < mappingMethods.size() ; i++) { + RequestMappingMethod method = mappingMethods.get(i); + String processId = method.app.getProcessID(); + String processName = method.app.getProcessName(); + String path = method.requestMappingPath; - StringBuilder builder = new StringBuilder(); + StringBuilder builder = new StringBuilder(); - String port = mappingMethod.app.getPort(); - String host = mappingMethod.app.getHost(); - String url = UrlUtil.createUrl(host, port, path); + String port = method.app.getPort(); + String host = method.app.getHost(); + String url = UrlUtil.createUrl(host, port, path); - builder.append("Path: "); - builder.append("["); - builder.append(path); - builder.append("]"); - builder.append("("); - builder.append(url); - builder.append(")"); + builder.append("Path: "); + builder.append("["); + builder.append(path); + builder.append("]"); + builder.append("("); + builder.append(url); + builder.append(")"); + + hoverContent.add(Either.forLeft(builder.toString())); + hoverContent.add(Either.forLeft("Process ID: " + processId)); + hoverContent.add(Either.forLeft("Process Name: " + processName)); + if (i < mappingMethods.size() - 1) { + // Three dashes == line separator in Markdown + hoverContent.add(Either.forLeft("---")); + } + } - hoverContent.add(Either.forLeft(builder.toString())); - hoverContent.add(Either.forLeft("Process ID: " + processId)); - hoverContent.add(Either.forLeft("Process Name: " + processName)); } private String getRawMethod(Annotation annotation, JSONObject mappings) { @@ -170,7 +184,7 @@ public class RequestMappingHoverProvider implements HoverProvider { while (keys.hasNext()) { String key = keys.next(); if (matchesAnnotation(annotation, key)) { - Object ob= mappings.get(key); + Object ob = mappings.get(key); if (ob instanceof JSONObject) { JSONObject methodMap = (JSONObject) ob; return methodMap.getString("method"); @@ -196,18 +210,18 @@ public class RequestMappingHoverProvider implements HoverProvider { if (annotation instanceof SingleMemberAnnotation) { Expression valueContent = ((SingleMemberAnnotation) annotation).getValue(); if (valueContent instanceof StringLiteral) { - mappingPath = ((StringLiteral)valueContent).getLiteralValue(); + mappingPath = ((StringLiteral) valueContent).getLiteralValue(); } } else if (annotation instanceof NormalAnnotation) { List values = ((NormalAnnotation) annotation).values(); for (Object value : values) { if (value instanceof MemberValuePair) { - String name = ((MemberValuePair)value).getName().toString(); + String name = ((MemberValuePair) value).getName().toString(); if (name != null && name.equals("value")) { - Expression valueContent = ((MemberValuePair)value).getValue(); + Expression valueContent = ((MemberValuePair) value).getValue(); if (valueContent instanceof StringLiteral) { - mappingPath = ((StringLiteral)valueContent).getLiteralValue(); + mappingPath = ((StringLiteral) valueContent).getLiteralValue(); } } } diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingLiveHoverTest.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingLiveHoverTest.java index 0fd57e088..c5012cf58 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingLiveHoverTest.java +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingLiveHoverTest.java @@ -132,4 +132,75 @@ public class RequestMappingLiveHoverTest { } + + @Test + public void testMultipleAppsLiveHover() 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/RestApi.java"; + + // Build three different instances of the same app running on different ports with different process IDs + mockAppProvider.builder() + .isSpringBootApp(true) + .port("1000") + .processId("70000") + .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 + .getRequestMappings( + "{\"/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)\"},\"{[/goodbye]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public java.lang.String example.RestApi.goodbye()\"},\"{[/hello]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public java.lang.String example.RestApi.hello()\"},\"{[/error]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public org.springframework.http.ResponseEntity> 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(); + + mockAppProvider.builder() + .isSpringBootApp(true) + .port("1001") + .processId("80000") + .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 + .getRequestMappings( + "{\"/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)\"},\"{[/goodbye]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public java.lang.String example.RestApi.goodbye()\"},\"{[/hello]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public java.lang.String example.RestApi.hello()\"},\"{[/error]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public org.springframework.http.ResponseEntity> 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(); + + mockAppProvider.builder() + .isSpringBootApp(true) + .port("1002") + .processId("90000") + .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 + .getRequestMappings( + "{\"/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)\"},\"{[/goodbye]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public java.lang.String example.RestApi.goodbye()\"},\"{[/hello]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public java.lang.String example.RestApi.hello()\"},\"{[/error]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public org.springframework.http.ResponseEntity> 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.assertHoverContains("@RequestMapping(\"/hello\")", "Path: [/hello](http://cfapps.io:1000/hello)\n" + + "\n" + + "Process ID: 70000\n" + + "\n" + + "Process Name: test-request-mapping-live-hover\n" + + "\n" + + "---\n" + + "\n" + + "Path: [/hello](http://cfapps.io:1001/hello)\n" + + "\n" + + "Process ID: 80000\n" + + "\n" + + "Process Name: test-request-mapping-live-hover\n" + + "\n" + + "---\n" + + "\n" + + "Path: [/hello](http://cfapps.io:1002/hello)\n" + + "\n" + + "Process ID: 90000\n" + + "\n" + + "Process Name: test-request-mapping-live-hover"); + + } + }