Merge branch 'master' of github.com:spring-projects/sts4

This commit is contained in:
Kris De Volder
2017-10-27 13:19:53 -07:00
15 changed files with 991 additions and 296 deletions

View File

@@ -30,6 +30,7 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
import org.springframework.ide.vscode.boot.java.utils.HoverContentUtils;
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.Log;
@@ -39,8 +40,8 @@ import com.google.common.collect.ImmutableList;
/**
*
* Provides live hovers and hints for @ConditionalOn... Spring Boot annotations from running
* spring boot apps.
* Provides live hovers and hints for @ConditionalOn... Spring Boot annotations
* from running spring boot apps.
*/
public class ConditionalsLiveHoverProvider implements HoverProvider {
@@ -98,14 +99,9 @@ public class ConditionalsLiveHoverProvider implements HoverProvider {
List<Either<String, MarkedString>> hoverContent) throws Exception {
for (int i = 0; i < conditions.size(); i++) {
RunningAppConditional condition = conditions.get(i);
hoverContent.add(Either.forLeft("Condition: " + condition.condition));
hoverContent.add(Either.forLeft("Message: " + condition.message));
// If there is more than one instances show process information
if (conditions.size() > 1) {
hoverContent.add(Either.forLeft("Process ID: " + condition.app.getProcessID()));
hoverContent.add(Either.forLeft("Process Name: " + condition.app.getProcessName()));
}
hoverContent.add(Either.forLeft(condition.message));
hoverContent.add(Either
.forLeft(HoverContentUtils.getProcessInformation(condition.app)));
if (i < conditions.size() - 1) {
hoverContent.add(Either.forLeft("---"));

View File

@@ -11,15 +11,15 @@
package org.springframework.ide.vscode.boot.java.requestmapping;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.SymbolKind;
import org.json.JSONObject;
import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider;
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
import org.springframework.ide.vscode.commons.util.Log;
@@ -44,7 +44,12 @@ public class LiveAppURLSymbolProvider {
SpringBootApp[] runningApps = runningAppProvider.getAllRunningSpringApps().toArray(new SpringBootApp[0]);
for (SpringBootApp app : runningApps) {
try {
collectLiveAppSymbols(result, app);
String host = app.getHost();
String port = app.getPort();
Stream<String> urls = app.getRequestMappings().stream()
.flatMap(rm -> Arrays.stream(rm.getSplitPath()))
.map(path -> UrlUtil.createUrl(host, port, path));
urls.forEach(url -> result.add(new SymbolInformation(url, SymbolKind.Method, new Location(url, new Range(new Position(0, 0), new Position(0, 1))))));
}
catch (Exception e) {
Log.log(e);
@@ -57,21 +62,4 @@ public class LiveAppURLSymbolProvider {
return result;
}
private void collectLiveAppSymbols(List<SymbolInformation> result, SpringBootApp app) throws Exception {
String mappings = app.getRequestMappings();
JSONObject requestMappings = new JSONObject(mappings);
Iterator<String> keys = requestMappings.keys();
while (keys.hasNext()) {
String key = keys.next();
String extractedPath = UrlUtil.extractPath(key);
if (extractedPath != null) {
String[] splitPath = UrlUtil.splitPath(extractedPath);
for (String path : splitPath) {
String url = UrlUtil.createUrl(app.getHost(), app.getPort(), path);
result.add(new SymbolInformation(url, SymbolKind.Method, new Location(url, new Range(new Position(0, 0), new Position(0, 1)))));
}
}
}
}
}

View File

@@ -11,38 +11,36 @@
package org.springframework.ide.vscode.boot.java.requestmapping;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
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.Expression;
import org.eclipse.jdt.core.dom.IMethodBinding;
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;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.MarkedString;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.json.JSONObject;
import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
import org.springframework.ide.vscode.commons.java.parser.JLRMethodParser;
import org.springframework.ide.vscode.commons.java.parser.JLRMethodParser.JLRMethod;
import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.Log;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import com.google.common.collect.ImmutableList;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
/**
* @author Martin Lippert
*/
@@ -58,8 +56,8 @@ public class RequestMappingHoverProvider implements HoverProvider {
public Collection<Range> getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) {
try {
if (runningApps.length > 0) {
Optional<List<RequestMappingMethod>> val = getRequestMappingMethodFromRunningApp(annotation, runningApps);
if (val.isPresent()) {
List<Tuple2<RequestMapping, SpringBootApp>> val = getRequestMappingMethodFromRunningApp(annotation, runningApps);
if (!val.isEmpty()) {
Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength());
return ImmutableList.of(hoverRange);
}
@@ -77,10 +75,10 @@ public class RequestMappingHoverProvider implements HoverProvider {
try {
List<Either<String, MarkedString>> hoverContent = new ArrayList<>();
Optional<List<RequestMappingMethod>> val = getRequestMappingMethodFromRunningApp(annotation, runningApps);
List<Tuple2<RequestMapping, SpringBootApp>> val = getRequestMappingMethodFromRunningApp(annotation, runningApps);
if (val.isPresent()) {
addHoverContent(val.get(), hoverContent);
if (!val.isEmpty()) {
addHoverContent(val, hoverContent);
}
Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength());
@@ -97,171 +95,79 @@ public class RequestMappingHoverProvider implements HoverProvider {
return null;
}
private Optional<List<RequestMappingMethod>> getRequestMappingMethodFromRunningApp(Annotation annotation,
private List<Tuple2<RequestMapping, SpringBootApp>> getRequestMappingMethodFromRunningApp(Annotation annotation,
SpringBootApp[] runningApps) {
List<Tuple2<RequestMapping, SpringBootApp>> results = new ArrayList<>();
try {
List<RequestMappingMethod> methods = new ArrayList<>();
for (SpringBootApp app : runningApps) {
String mappings = app.getRequestMappings();
if (mappings != null) {
JSONObject requestMappings = new JSONObject(mappings);
String rawPath = getRawPath(annotation, requestMappings);
if (rawPath != null) {
String path = UrlUtil.extractPath(rawPath);
if (path != null) {
String rawMethod = getRawMethod(annotation, requestMappings);
JLRMethod parsedMethod = JLRMethodParser.parse(rawMethod);
if (methodMatchesAnnotation(annotation, parsedMethod)) {
methods.add(new RequestMappingMethod(path, parsedMethod, app));
}
}
}
Collection<RequestMapping> mappings = app.getRequestMappings();
if (mappings != null && !mappings.isEmpty()) {
mappings.stream()
.filter(rm -> methodMatchesAnnotation(annotation, rm))
.map(rm -> Tuples.of(rm, app))
.findFirst().ifPresent(t -> results.add(t));
}
}
if (!methods.isEmpty()) {
return Optional.of(methods);
}
} catch (Exception e) {
Log.log(e);
}
return Optional.empty();
return results;
}
private boolean methodMatchesAnnotation(Annotation annotation, JLRMethod requestMappingMethod) {
String rqClassName = requestMappingMethod.getFQClassName();
String rqMethod = requestMappingMethod.getMethodName();
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(rqMethod);
} else if (parent instanceof TypeDeclaration) {
TypeDeclaration typeDec = (TypeDeclaration) parent;
return typeDec.resolveBinding().getQualifiedName().equals(rqClassName);
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;
}
private void addHoverContent(List<RequestMappingMethod> mappingMethods, List<Either<String, MarkedString>> 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;
private void addHoverContent(List<Tuple2<RequestMapping, SpringBootApp>> mappingMethods, List<Either<String, MarkedString>> hoverContent) throws Exception {
for (int i = 0; i < mappingMethods.size(); i++) {
Tuple2<RequestMapping, SpringBootApp> mappingMethod = mappingMethods.get(i);
StringBuilder builder = new StringBuilder();
String processId = mappingMethod.getT2().getProcessID();
String processName = mappingMethod.getT2().getProcessName();
String port = mappingMethod.getT2().getPort();
String host = mappingMethod.getT2().getHost();
String port = method.app.getPort();
String host = method.app.getHost();
String url = UrlUtil.createUrl(host, port, path);
List<Renderable> renderableUrls = Arrays.stream(mappingMethod.getT1().getSplitPath()).flatMap(path -> {
String url = UrlUtil.createUrl(host, port, path);
StringBuilder builder = new StringBuilder();
builder.append("[");
builder.append(url);
builder.append("]");
builder.append("(");
builder.append(url);
builder.append(")");
return Stream.of(Renderables.text(builder.toString()), Renderables.lineBreak());
})
.collect(Collectors.toList());
builder.append("[");
builder.append(url);
builder.append("]");
builder.append("(");
builder.append(url);
builder.append(")");
// Remove the last line break
renderableUrls.remove(renderableUrls.size() - 1);
hoverContent.add(Either.forLeft(builder.toString()));
hoverContent.add(Either.forLeft(Renderables.concat(renderableUrls).toMarkdown()));
hoverContent.add(Either.forLeft("Process ID: " + processId));
hoverContent.add(Either.forLeft("Process Name: " + processName));
if (i < mappingMethods.size() - 1) {
if (i < mappingMethods.size() - 1) {
// Three dashes == line separator in Markdown
hoverContent.add(Either.forLeft("---"));
}
}
}
private String getRawMethod(Annotation annotation, JSONObject mappings) {
Iterator<String> keys = mappings.keys();
while (keys.hasNext()) {
String key = keys.next();
if (matchesAnnotation(annotation, key)) {
Object ob = mappings.get(key);
if (ob instanceof JSONObject) {
JSONObject methodMap = (JSONObject) ob;
return methodMap.getString("method");
}
}
}
return null;
}
private String getRawPath(Annotation annotation, JSONObject mappings) {
Iterator<String> keys = mappings.keys();
while (keys.hasNext()) {
String key = keys.next();
if (matchesAnnotation(annotation, key)) {
return key;
}
}
return null;
}
private boolean matchesAnnotation(Annotation annotation, String jsonKey) {
String mappingPath = null;
if (annotation instanceof SingleMemberAnnotation) {
Expression valueContent = ((SingleMemberAnnotation) annotation).getValue();
if (valueContent instanceof StringLiteral) {
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();
if (name != null && name.equals("value")) {
Expression valueContent = ((MemberValuePair) value).getValue();
if (valueContent instanceof StringLiteral) {
mappingPath = ((StringLiteral) valueContent).getLiteralValue();
}
}
}
}
}
return mappingPath != null ? jsonKey.contains(mappingPath) : false;
}
public JSONObject[] getRequestMappingsFromProcesses(SpringBootApp[] runningApps) {
List<JSONObject> result = new ArrayList<>();
try {
for (SpringBootApp app : runningApps) {
String mappings = app.getRequestMappings();
if (mappings != null) {
JSONObject requestMappings = new JSONObject(mappings);
if (requestMappings != null) {
result.add(requestMappings);
}
}
}
}
catch (Exception e) {
Log.log(e);
}
return result.toArray(new JSONObject[result.size()]);
}
static class RequestMappingMethod {
public final SpringBootApp app;
public final String requestMappingPath;
public final JLRMethod requestMappingMethod;
public RequestMappingMethod(String requestMappingPath, JLRMethod requestMappingMethod, SpringBootApp app) {
this.requestMappingPath = requestMappingPath;
this.requestMappingMethod = requestMappingMethod;
this.app = app;
}
}
}

View File

@@ -10,41 +10,8 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.requestmapping;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import java.util.stream.Stream;
public class UrlUtil {
public static Stream<String> processOrPaths(String pathExp) {
if (pathExp.contains("||")) {
String[] paths = pathExp.split(Pattern.quote("||"));
return Stream.of(paths).map(String::trim);
} else {
return Stream.of(pathExp);
}
}
public static String extractPath(String key) {
if (key.startsWith("{[")) { //Case 2 (see above)
//An almost json string. Unfortunately not really json so we can't
//use org.json or jackson Mapper to properly parse this.
int start = 2; //right after first '['
int end = key.indexOf(']');
if (end>=2) {
return key.substring(start, end);
}
}
//Case 1, or some unanticipated stuff.
//Assume the key is the path, which is right for Case 1
// and probably more useful than null for 'unanticipated stuff'.
return key;
}
/**
* Creates http URL string based on host, port and path
* @param host
@@ -67,32 +34,4 @@ public class UrlUtil {
return null;
}
public static String[] splitPath(String path) {
if (path.contains("||")) {
List<String> result = new ArrayList<>();
String basePath = path.substring(0, path.indexOf("||")).trim();
result.add(basePath);
if (basePath.lastIndexOf('/') > 0) {
basePath = basePath.substring(0, basePath.lastIndexOf('/'));
}
String additionalPaths = path.substring(path.indexOf("||"));
StringTokenizer tokenizer = new StringTokenizer(additionalPaths, "||");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken().trim();
if (token.length() > 0) {
result.add(basePath + "/" + token);
}
}
return result.toArray(new String[result.size()]);
}
else {
return new String[] {path};
}
}
}

View File

@@ -0,0 +1,21 @@
/*******************************************************************************
* 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.utils;
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
public class HoverContentUtils {
public static String getProcessInformation(SpringBootApp app) {
return "Process " + app.getProcessID() + ": " + app.getProcessName();
}
}

View File

@@ -74,9 +74,10 @@ public class ConditionalsLiveHoverTest {
harness.intialize(directory);
Editor editor = harness.newEditorFromFileUri(docUri, LanguageId.JAVA);
editor.assertHoverContains("@ConditionalOnBean", "Condition: OnBeanCondition\n" + "\n"
+ "Message: @ConditionalOnBean (types: example.Hello; SearchStrategy: all) found bean 'missing'");
editor.assertHoverContains("@ConditionalOnBean",
"@ConditionalOnBean (types: example.Hello; SearchStrategy: all) found bean 'missing'\n" +
"\n" +
"Process 22022: test-conditionals-live-hover");
}
@Test
@@ -97,8 +98,9 @@ public class ConditionalsLiveHoverTest {
harness.intialize(directory);
Editor editor = harness.newEditorFromFileUri(docUri, LanguageId.JAVA);
editor.assertHoverContains("@ConditionalOnMissingBean", "Condition: OnBeanCondition\n" + "\n"
+ "Message: @ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans");
editor.assertHoverContains("@ConditionalOnMissingBean", "@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\n"+
"\n" +
"Process 22022: test-conditionals-live-hover");
}
@@ -120,20 +122,26 @@ public class ConditionalsLiveHoverTest {
Editor editor = harness.newEditorFromFileUri(docUri, LanguageId.JAVA);
editor.assertHoverContains("@ConditionalOnBean", "Condition: OnBeanCondition\n" + "\n"
+ "Message: @ConditionalOnBean (types: example.Hello; SearchStrategy: all) found beans 'hi', 'missing'");
editor.assertHoverContains("@ConditionalOnBean", "@ConditionalOnBean (types: example.Hello; SearchStrategy: all) found beans 'hi', 'missing'\n" +
"\n" +
"Process 22022: test-conditionals-live-hover");
editor.assertHoverContains("@ConditionalOnWebApplication", "Condition: OnWebApplicationCondition\n" + "\n"
+ "Message: @ConditionalOnWebApplication (required) found StandardServletEnvironment");
editor.assertHoverContains("@ConditionalOnWebApplication", "@ConditionalOnWebApplication (required) found StandardServletEnvironment\n"+
"\n" +
"Process 22022: test-conditionals-live-hover");
editor.assertHoverContains("@ConditionalOnJava(value=ConditionalOnJava.JavaVersion.EIGHT)",
"Condition: OnJavaCondition\n" + "\n" + "Message: @ConditionalOnJava (1.8 or newer) found 1.8");
"@ConditionalOnJava (1.8 or newer) found 1.8\n" +
"\n" +
"Process 22022: test-conditionals-live-hover");
editor.assertHoverContains("@ConditionalOnMissingClass", "Condition: OnClassCondition\n" + "\n"
+ "Message: @ConditionalOnClass found required class; @ConditionalOnMissingClass did not find unwanted class");
editor.assertHoverContains("@ConditionalOnMissingClass", "@ConditionalOnClass found required class; @ConditionalOnMissingClass did not find unwanted class\n" +
"\n" +
"Process 22022: test-conditionals-live-hover");
editor.assertHoverContains("@ConditionalOnExpression", "Condition: OnExpressionCondition\n" + "\n"
+ "Message: @ConditionalOnExpression (#{true}) resulted in true");
editor.assertHoverContains("@ConditionalOnExpression", "@ConditionalOnExpression (#{true}) resulted in true\n" +
"\n" +
"Process 22022: test-conditionals-live-hover");
}
@@ -169,30 +177,21 @@ public class ConditionalsLiveHoverTest {
Editor editor = harness.newEditorFromFileUri(docUri, LanguageId.JAVA);
editor.assertHoverContains("@ConditionalOnMissingBean", "Condition: OnBeanCondition\n" + "\n"
+ "Message: @ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\n" +
editor.assertHoverContains("@ConditionalOnMissingBean", "@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\n" +
"\n" +
"Process ID: 70000\n" +
"\n" +
"Process Name: test-conditionals-live-hover\n" +
"Process 70000: test-conditionals-live-hover\n" +
"\n" +
"---\n" +
"\n" +
"Condition: OnBeanCondition\n" + "\n"
+ "Message: @ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\n" +
"@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\n" +
"\n" +
"Process ID: 80000\n" +
"\n" +
"Process Name: test-conditionals-live-hover\n" +
"Process 80000: test-conditionals-live-hover\n" +
"\n" +
"---\n" +
"\n" +
"Condition: OnBeanCondition\n" + "\n"
+ "Message: @ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\n" +
"@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\n" +
"\n" +
"Process ID: 90000\n" +
"\n" +
"Process Name: test-conditionals-live-hover");
"Process 90000: test-conditionals-live-hover");
}

View File

@@ -62,7 +62,7 @@ public class RequestMappingLiveHoverTest {
harness.intialize(directory);
Editor editor = harness.newEditorFromFileUri(docUri, LanguageId.JAVA);
editor.assertHoverContains("@RequestMapping(\"/hello-world\")", "[http://cfapps.io:1111/hello-world](http://cfapps.io:1111/hello-world)\n" +
editor.assertHoverContains("@RequestMapping(method=RequestMethod.GET)", "[http://cfapps.io:1111/hello-world](http://cfapps.io:1111/hello-world)\n" +
"\n" +
"Process ID: 22022\n" +
"\n" +
@@ -132,6 +132,555 @@ public class RequestMappingLiveHoverTest {
}
@Test
public void testSimpleMethodHoverHintMethod1() 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 a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.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(
"{\"{[/greetings],methods=[DELETE]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public void com.example.RestApi.deleteGreetings()\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.DeleteMapping;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@DeleteMapping(\"/greetings\")\n" +
"public void deleteGreetings() {\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertHoverContains("@DeleteMapping(\"/greetings\")", "[http://cfapps.io:999/greetings](http://cfapps.io:999/greetings)\n" +
"\n" +
"Process ID: 76543\n" +
"\n" +
"Process Name: test-request-mapping-live-hover");
}
@Test
public void testDeleteMappingHoverHintMethod2() 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 a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.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(
"{\"{[/greetings],methods=[DELETE]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public void com.example.RestApi.deleteGreetings()\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.DeleteMapping;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@DeleteMapping(\"/greetings\")\n" +
"public void deleteGreetings(int n) {\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertNoHover("@DeleteMapping(\"/greetings\")");
}
@Test
public void testNoHoverHintMethod() 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 a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.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(
"{\"{[/greetings],methods=[PUT]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.String com.example.RestApi.updateGreetings()\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import java.lang.String;\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.PutMapping;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@PutMapping(\"/greetings\")\n" +
"public String updateGreetings(int n) {\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertNoHover("@PutMapping(\"/greetings\")");
}
@Test
public void testMultiPathMappingHoverHintMethod1() 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 a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.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(
"{\"{[/greetings || /hello],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.String com.example.RestApi.greetings()\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.RequestMapping;\n" +
"import org.springframework.web.bind.annotation.RequestMethod.*;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@RequestMapping(value={\"/greetings\", \"/hello\"}, method=GET)\n" +
"public String greetings() {\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertHoverContains("@RequestMapping(value={\"/greetings\", \"/hello\"}, method=GET)", "[http://cfapps.io:999/greetings](http://cfapps.io:999/greetings) \n" +
"[http://cfapps.io:999/hello](http://cfapps.io:999/hello)\n" +
"\n" +
"Process ID: 76543\n" +
"\n" +
"Process Name: test-request-mapping-live-hover");
}
@Test
public void testMethodMatchingHoverHintMethod1() 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 a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.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(
"{\"{[/find],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public org.springframework.http.ResponseEntity<?> com.example.RestApi.find(java.lang.String,java.util.Date,java.lang.String)\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.http.ResponseEntity;\n" +
"import java.lang.String;\n" +
"import java.util.Date;\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.RequestMapping;\n" +
"import org.springframework.web.bind.annotation.RequestMethod.*;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@RequestMapping(value=\"/find\", method=GET)\n" +
"public ResponseEntity<?> find(String p1, Date p2, String p3) {\n" +
"return null;\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertHoverContains("@RequestMapping(value=\"/find\", method=GET)", "[http://cfapps.io:999/find](http://cfapps.io:999/find)\n" +
"\n" +
"Process ID: 76543\n" +
"\n" +
"Process Name: test-request-mapping-live-hover");
}
@Test
public void testMethodMatchingHoverHintMethod2() 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 a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.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(
"{\"{[/find],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.Object com.example.RestApi.set(java.lang.String,java.util.Map<java.lang.String, java.lang.String>)\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.http.ResponseEntity;\n" +
"import java.lang.String;\n" +
"import java.util.Map;\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.RequestMapping;\n" +
"import org.springframework.web.bind.annotation.RequestMethod.*;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@RequestMapping(value=\"/find\", method=GET)\n" +
"public Object set(String p1, Map<String, String> p2) {\n" +
"return null;\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertHoverContains("@RequestMapping(value=\"/find\", method=GET)", "[http://cfapps.io:999/find](http://cfapps.io:999/find)\n" +
"\n" +
"Process ID: 76543\n" +
"\n" +
"Process Name: test-request-mapping-live-hover");
}
@Test
public void testMethodMatchingHoverHintMethod3() 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 a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.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(
"{\"{[/find],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.Object com.example.RestApi.set(java.lang.String,java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.Integer>>)\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.http.ResponseEntity;\n" +
"import java.lang.String;\n" +
"import java.lang.Integer;\n" +
"import java.util.Map;\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.RequestMapping;\n" +
"import org.springframework.web.bind.annotation.RequestMethod.*;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@RequestMapping(value=\"/find\", method=GET)\n" +
"public Object set(String p1, Map<String, Map<String, Integer>> p2) {\n" +
"return null;\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertHoverContains("@RequestMapping(value=\"/find\", method=GET)", "[http://cfapps.io:999/find](http://cfapps.io:999/find)\n" +
"\n" +
"Process ID: 76543\n" +
"\n" +
"Process Name: test-request-mapping-live-hover");
}
@Test
public void testWildCardMethodMatchingHoverHint() 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 a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.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(
"{\"{[/find],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.Object com.example.RestApi.set(java.lang.String,java.util.Map<java.lang.String, java.util.Map<java.lang.String, ? extends java.lang.Integer>>)\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.http.ResponseEntity;\n" +
"import java.lang.String;\n" +
"import java.lang.Integer;\n" +
"import java.util.Map;\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.RequestMapping;\n" +
"import org.springframework.web.bind.annotation.RequestMethod.*;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@RequestMapping(value=\"/find\", method=GET)\n" +
"public Object set(String p1, Map<String, Map<String, ? extends Integer>> p2) {\n" +
"return null;\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertHoverContains("@RequestMapping(value=\"/find\", method=GET)", "[http://cfapps.io:999/find](http://cfapps.io:999/find)\n" +
"\n" +
"Process ID: 76543\n" +
"\n" +
"Process Name: test-request-mapping-live-hover");
}
@Test
public void testArrayMethodMatchingHoverHint() 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 a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.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(
"{\"{[/find],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.Object com.example.RestApi.set(java.lang.String[])\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.http.ResponseEntity;\n" +
"import java.lang.String;\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.RequestMapping;\n" +
"import org.springframework.web.bind.annotation.RequestMethod.*;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@RequestMapping(value=\"/find\", method=GET)\n" +
"public Object set(String[] p) {\n" +
"return null;\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertHoverContains("@RequestMapping(value=\"/find\", method=GET)", "[http://cfapps.io:999/find](http://cfapps.io:999/find)\n" +
"\n" +
"Process ID: 76543\n" +
"\n" +
"Process Name: test-request-mapping-live-hover");
}
@Test
public void testMultiDimensionalArrayMethodMatchingHoverHint() 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 a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.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(
"{\"{[/find],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.Object com.example.RestApi.set(java.lang.String[][])\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.http.ResponseEntity;\n" +
"import java.lang.String;\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.RequestMapping;\n" +
"import org.springframework.web.bind.annotation.RequestMethod.*;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@RequestMapping(value=\"/find\", method=GET)\n" +
"public Object set(String[][] p) {\n" +
"return null;\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertHoverContains("@RequestMapping(value=\"/find\", method=GET)", "[http://cfapps.io:999/find](http://cfapps.io:999/find)\n" +
"\n" +
"Process ID: 76543\n" +
"\n" +
"Process Name: test-request-mapping-live-hover");
}
@Test
public void testVarArgsMethodMatchingHoverHint() 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 a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.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(
"{\"{[/find],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.Object com.example.RestApi.set(java.lang.String...)\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.http.ResponseEntity;\n" +
"import java.lang.String;\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.RequestMapping;\n" +
"import org.springframework.web.bind.annotation.RequestMethod.*;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@RequestMapping(value=\"/find\", method=GET)\n" +
"public Object set(String... p) {\n" +
"return null;\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertHoverContains("@RequestMapping(value=\"/find\", method=GET)", "[http://cfapps.io:999/find](http://cfapps.io:999/find)\n" +
"\n" +
"Process ID: 76543\n" +
"\n" +
"Process Name: test-request-mapping-live-hover");
}
@Test
public void testMultipleAppsLiveHover() throws Exception {

View File

@@ -19,9 +19,9 @@ import java.util.Collection;
import org.mockito.Mockito;
import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider;
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping;
import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel;
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness.Builder;
import com.google.common.collect.ImmutableList;
@@ -102,7 +102,8 @@ public class MockRunningAppProvider {
}
public MockAppBuilder getRequestMappings(String mappings) throws Exception {
when(app.getRequestMappings()).thenReturn(mappings);
Collection<RequestMapping> requestMappings = SpringBootApp.parseRequestMappingsJson(mappings);
when(app.getRequestMappings()).thenReturn(requestMappings);
return this;
}

View File

@@ -42,6 +42,11 @@
<groupId>org.springframework.ide.vscode</groupId>
<version>${project.version}</version>
</dependency>
<dependency>
<artifactId>commons-java</artifactId>
<groupId>org.springframework.ide.vscode</groupId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -13,7 +13,9 @@ package org.springframework.ide.vscode.commons.boot.app.cli;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -30,6 +32,8 @@ import javax.management.remote.JMXServiceURL;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping;
import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMappingImpl1;
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;
@@ -198,17 +202,29 @@ public class SpringBootApp {
}
}
public String getRequestMappings() throws Exception {
public static Collection<RequestMapping> parseRequestMappingsJson(String json) {
JSONObject obj = new JSONObject(json);
Iterator<String> keys = obj.keys();
List<RequestMapping> result = new ArrayList<>();
while (keys.hasNext()) {
String rawKey = keys.next();
JSONObject value = obj.getJSONObject(rawKey);
result.add(new RequestMappingImpl1(rawKey, value));
}
return result;
}
public Collection<RequestMapping> getRequestMappings() throws Exception {
Object result = getActuatorDataFromAttribute("org.springframework.boot:type=Endpoint,name=requestMappingEndpoint", "Data");
if (result != null) {
String mappings = new ObjectMapper().writeValueAsString(result);
return mappings;
return parseRequestMappingsJson(mappings);
}
result = getActuatorDataFromOperation("org.springframework.boot:type=Endpoint,name=Mappings", "mappings");
if (result != null) {
String mappings = new ObjectMapper().writeValueAsString(result);
return mappings;
return parseRequestMappingsJson(mappings);
}
return null;
@@ -436,4 +452,5 @@ public class SpringBootApp {
return null;
}
}

View File

@@ -0,0 +1,23 @@
/*******************************************************************************
* 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.commons.boot.app.cli.requestmappings;
import java.util.Set;
public interface RequestMapping {
String getPath();
String[] getSplitPath();
String getFullyQualifiedClassName();
String getMethodName();
String[] getMethodParameters();
String getMethodString();
Set<String> getRequestMethods();
}

View File

@@ -0,0 +1,180 @@
/*******************************************************************************
* 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.commons.boot.app.cli.requestmappings;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.json.JSONObject;
import org.springframework.ide.vscode.commons.java.parser.JLRMethodParser;
import org.springframework.ide.vscode.commons.java.parser.JLRMethodParser.JLRMethod;
import org.springframework.ide.vscode.commons.util.Log;
import com.google.common.base.Objects;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
public class RequestMappingImpl1 implements RequestMapping {
private static final Pattern REQUEST_METHODS_PATTERN = Pattern.compile(".*methods=\\[(.*)\\].*");
/*
There are two styles of entries:
1) key is a 'path' String. May contain patters like "**"
"/** /favicon.ico":{
"bean":"faviconHandlerMapping"
}
2) key is a 'almost json' String
"{[/bye],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}":{
"bean":"requestMappingHandlerMapping",
"method":"public java.lang.String demo.MyController.bye()"
}
*/
private JSONObject beanInfo;
private String pathKey;
private Supplier<JLRMethod> methodDataSupplier;
private Supplier<Set<String>> requestMethodsSupplier;
private Supplier<String> requestPathSupplier;
public RequestMappingImpl1(String pathKey, JSONObject beanInfo) {
this.pathKey = pathKey;
this.beanInfo = beanInfo;
this.requestMethodsSupplier = Suppliers.memoize(() -> parseRequestMethods());
this.requestPathSupplier = Suppliers.memoize(() -> parseRequestPath());
this.methodDataSupplier = Suppliers.memoize(() -> JLRMethodParser.parse(getMethodString()));
}
@Override
public String getPath() {
return requestPathSupplier.get();
}
@Override
public String toString() {
return "RequestMapping("+pathKey+")";
}
@Override
public String getFullyQualifiedClassName() {
JLRMethod m = getMethodData();
if (m!=null) {
return m.getFQClassName();
}
return null;
}
@Override
public String getMethodName() {
JLRMethod m = getMethodData();
if (m!=null) {
return m.getMethodName();
}
return null;
}
/**
* Returns the raw string found in the requestmapping info. This is a 'toString' value
* of java.lang.reflect.Method object.
*/
@Override
public String getMethodString() {
try {
if (beanInfo!=null) {
if (beanInfo.has("method")) {
return beanInfo.getString("method");
}
}
} catch (Exception e) {
Log.log(e);
}
return null;
}
private JLRMethod getMethodData() {
return methodDataSupplier.get();
}
@Override
public int hashCode() {
return pathKey.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
RequestMappingImpl1 other = (RequestMappingImpl1) obj;
return Objects.equal(this.pathKey, other.pathKey)
&& Objects.equal(this.getMethodString(), other.getMethodString());
}
protected Set<String> parseRequestMethods() {
Matcher matcher = REQUEST_METHODS_PATTERN.matcher(pathKey);
if (matcher.matches()) {
return Arrays.stream(matcher.group(1).split("\\s*,\\s*")).collect(Collectors.toSet());
}
return Collections.emptySet();
}
protected String parseRequestPath() {
if (pathKey.startsWith("{[")) { //Case 2 (see above)
//An almost json string. Unfortunately not really json so we can't
//use org.json or jackson Mapper to properly parse this.
int start = 2; //right after first '['
int end = pathKey.indexOf(']');
if (end>=2) {
return pathKey.substring(start, end);
}
}
//Case 1, or some unanticipated stuff.
//Assume the key is the path, which is right for Case 1
// and probably more useful than null for 'unanticipated stuff'.
return pathKey;
}
@Override
public Set<String> getRequestMethods() {
return requestMethodsSupplier.get();
}
@Override
public String[] getSplitPath() {
String paths = requestPathSupplier.get();
return Arrays.stream(paths.split("\\|\\|"))
.map(s -> s.trim())
.filter(s -> !s.isEmpty())
.map(s -> {
if (s.charAt(0) != '/') {
return '/' + s;
} else {
return s;
}
})
.toArray(String[]::new);
}
@Override
public String[] getMethodParameters() {
return getMethodData().getParameters();
}
}

View File

@@ -8,51 +8,51 @@
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.requestmapping.test;
package org.springframework.ide.vscode.commons.boot.app.cli;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.springframework.ide.vscode.boot.java.requestmapping.UrlUtil;
import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMappingImpl1;
/**
* @author Martin Lippert
*/
public class UrlUtilTest {
public class RequestMappingImp1Test {
@Test
public void testSplitPathWithoutDuplicate() {
String path = "/superpath";
String[] splitPath = UrlUtil.splitPath(path);
RequestMappingImpl1 rm = new RequestMappingImpl1("/superpath", null);
String[] splitPath = rm.getSplitPath();
assertEquals(1, splitPath.length);
assertEquals("/superpath", splitPath[0]);
}
@Test
public void testSplitPathSimpleCaseWithEmptyOr() {
String path = "/superpath/mypath || ";
String[] splitPath = UrlUtil.splitPath(path);
RequestMappingImpl1 rm = new RequestMappingImpl1("/superpath/mypath || ", null);
String[] splitPath = rm.getSplitPath();
assertEquals(1, splitPath.length);
assertEquals("/superpath/mypath", splitPath[0]);
}
@Test
public void testSplitPathSimpleCase() {
String path = "/superpath/mypath || mypath.json";
String[] splitPath = UrlUtil.splitPath(path);
RequestMappingImpl1 rm = new RequestMappingImpl1("{[/superpath/mypath || mypath.json]}", null);
String[] splitPath = rm.getSplitPath();
assertEquals(2, splitPath.length);
assertEquals("/superpath/mypath", splitPath[0]);
assertEquals("/superpath/mypath.json", splitPath[1]);
assertEquals("/mypath.json", splitPath[1]);
}
@Test
public void testSplitPathMultipleCases() {
String path = "/superpath/mypath || mypath.json || somethingelse.what";
String[] splitPath = UrlUtil.splitPath(path);
RequestMappingImpl1 rm = new RequestMappingImpl1("{[/superpath/mypath || mypath.json || somethingelse.what]}", null);
String[] splitPath = rm.getSplitPath();
assertEquals(3, splitPath.length);
assertEquals("/superpath/mypath", splitPath[0]);
assertEquals("/superpath/mypath.json", splitPath[1]);
assertEquals("/superpath/somethingelse.what", splitPath[2]);
assertEquals("/mypath.json", splitPath[1]);
assertEquals("/somethingelse.what", splitPath[2]);
}
}

View File

@@ -26,11 +26,11 @@ import java.util.stream.Collectors;
import org.json.JSONObject;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel;
import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping;
import org.springframework.ide.vscode.commons.util.AsyncProcess;
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
import org.springframework.ide.vscode.commons.util.ExternalCommand;
@@ -183,9 +183,9 @@ public class SpringBootAppTest {
public void getRequestMappings() throws Exception {
for (SpringBootApp testApp : getTestApps()) {
ACondition.waitFor(TIMEOUT, () -> {
String result = testApp.getRequestMappings();
assertNonEmptyJsonObject(result);
// System.out.println("requestMappings = "+result);
Collection<RequestMapping> result = testApp.getRequestMappings();
assertTrue(result != null && !result.isEmpty());
// System.out.println("requestMappings = "+result);
});
}
}

View File

@@ -10,9 +10,11 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.java.parser;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
@@ -32,6 +34,8 @@ public class JLRMethodParser {
private String fqClass;
private String methodName;
private String returnType;
private String[] parameters;
//TODO: parsing arguments to handle overloading
@@ -49,6 +53,7 @@ public class JLRMethodParser {
while (modifiersEnd<pieces.length && isModifier(pieces[modifiersEnd])) {
modifiersEnd++;
}
returnType = pieces[modifiersEnd];
if (pieces.length>=modifiersEnd+2) {
methodString = pieces[modifiersEnd+1];
int methodNameEnd = methodString.indexOf('(');
@@ -63,6 +68,64 @@ public class JLRMethodParser {
}
}
}
int parametersStart = rawString.indexOf('(');
int parametersEnd = rawString.indexOf(')');
if (parametersStart < parametersEnd) {
String parametersString = rawString.substring(parametersStart + 1, parametersEnd);
this.parameters = parseParameters(parametersString);
} else {
this.parameters = new String[0];
}
}
private String[] parseParameters(String parameterString) {
List<String> parameters = new ArrayList<>();
int openTemplateParameters = 0;
StringBuilder currentParameter = new StringBuilder();
for (int i = 0; i < parameterString.length(); i++) {
char ch = parameterString.charAt(i);
switch (ch) {
case '.':
currentParameter.append(ch);
break;
case '<':
openTemplateParameters++;
currentParameter.append(ch);
break;
case '>':
openTemplateParameters--;
currentParameter.append(ch);
break;
case ',' :
if (openTemplateParameters == 0) {
if (currentParameter.length() != 0) {
parameters.add(currentParameter.toString());
currentParameter = new StringBuilder();
}
} else {
currentParameter.append(ch);
}
break;
default:
currentParameter.append(ch);
}
}
if (currentParameter.length() > 0) {
parameters.add(currentParameter.toString());
}
return parameters.stream()
.map(p -> {
int genericStart = p.indexOf('<');
int genericEnd = p.lastIndexOf('>');
if (genericStart < genericEnd) {
return p.substring(0, genericStart) + p.substring(genericEnd + 1);
} else {
return p;
}
})
.map(p -> p.replaceAll("\\.\\.\\.", "[]"))
.toArray(String[]::new);
}
@Override
@@ -77,6 +140,14 @@ public class JLRMethodParser {
public String getMethodName() {
return methodName;
}
public String getReturnType() {
return returnType;
}
public String[] getParameters() {
return parameters;
}
}