finished first version of symbol support for webflux route definitions

This commit is contained in:
Martin Lippert
2018-02-22 17:02:28 +01:00
parent f381aa72d5
commit 6800c83c0d
5 changed files with 234 additions and 14 deletions

View File

@@ -0,0 +1,64 @@
/*******************************************************************************
* Copyright (c) 2018 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.requestmapping;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.MethodInvocation;
/**
* @author Martin Lippert
*/
public class WebfluxPathFinder extends ASTVisitor {
private String path;
private ASTNode root;
public WebfluxPathFinder(ASTNode root) {
this.root = root;
}
public String getPath() {
return path;
}
@Override
public boolean visit(MethodInvocation node) {
boolean visitChildren = true;
if (node != this.root) {
IMethodBinding methodBinding = node.resolveMethodBinding();
if (WebfluxRouterSymbolProvider.REQUEST_PREDICATES_TYPE.equals(methodBinding.getDeclaringClass().getBinaryName())) {
String name = methodBinding.getName();
if (name != null && WebfluxRouterSymbolProvider.REQUEST_PREDICATE_PATH_METHODS.contains(name)) {
path = WebfluxRouterSymbolProvider.extractPath(node);
}
}
if (WebfluxRouterSymbolProvider.ROUTER_FUNCTIONS_TYPE.equals(methodBinding.getDeclaringClass().getBinaryName())) {
String name = methodBinding.getName();
if ("route".equals(name)) {
visitChildren = false;
}
}
else if (WebfluxRouterSymbolProvider.ROUTER_FUNCTION_TYPE.equals(methodBinding.getDeclaringClass().getBinaryName())) {
String name = methodBinding.getName();
if ("andRoute".equals(name)) {
visitChildren = false;
}
}
}
return visitChildren;
}
}

View File

@@ -11,9 +11,13 @@
package org.springframework.ide.vscode.boot.java.requestmapping;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.Block;
@@ -21,10 +25,14 @@ import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.SymbolKind;
import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
/**
@@ -32,8 +40,11 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
*/
public class WebfluxRouterSymbolProvider implements SymbolProvider {
private static final String ROUTER_FUNCTION_TYPE = "org.springframework.web.reactive.function.server.RouterFunction";
private static final String ROUTER_FUNCTIONS_TYPE = "org.springframework.web.reactive.function.server.RouterFunctions";
public static final String ROUTER_FUNCTION_TYPE = "org.springframework.web.reactive.function.server.RouterFunction";
public static final String ROUTER_FUNCTIONS_TYPE = "org.springframework.web.reactive.function.server.RouterFunctions";
public static final String REQUEST_PREDICATES_TYPE = "org.springframework.web.reactive.function.server.RequestPredicates";
public static final Set<String> REQUEST_PREDICATE_PATH_METHODS = new HashSet<>(Arrays.asList("path", "GET", "POST", "DELETE", "PUT", "PATCH", "HEAD", "OPTIONS"));
@Override
public Collection<SymbolInformation> getSymbols(Annotation node, ITypeBinding typeBinding,
@@ -71,13 +82,17 @@ public class WebfluxRouterSymbolProvider implements SymbolProvider {
public boolean visit(MethodInvocation node) {
IMethodBinding methodBinding = node.resolveMethodBinding();
if (ROUTER_FUNCTIONS_TYPE.equals(methodBinding.getDeclaringClass().getBinaryName())
&& "route".equals(node.getName().toString())) {
extractMappingSymbol(node, doc, result);
if (ROUTER_FUNCTIONS_TYPE.equals(methodBinding.getDeclaringClass().getBinaryName())) {
String name = methodBinding.getName();
if ("route".equals(name)) {
extractMappingSymbol(node, doc, result);
}
}
else if (ROUTER_FUNCTION_TYPE.equals(methodBinding.getDeclaringClass().getBinaryName())
&& "andRoute".equals(node.getName().toString())) {
extractMappingSymbol(node, doc, result);
else if (ROUTER_FUNCTION_TYPE.equals(methodBinding.getDeclaringClass().getBinaryName())) {
String name = methodBinding.getName();
if ("andRoute".equals(name)) {
extractMappingSymbol(node, doc, result);
}
}
return super.visit(node);
@@ -89,12 +104,83 @@ public class WebfluxRouterSymbolProvider implements SymbolProvider {
}
protected void extractMappingSymbol(MethodInvocation node, TextDocument doc, List<SymbolInformation> result) {
List<?> arguments = node.arguments();
if (arguments != null) {
for (Object argument : arguments) {
System.out.println(argument);
String foundPath = extractPathFromRouterFunction(node);
String path = extractPath(node, foundPath);
String httpMethod = extractMethod(node);
int methodNameStart = node.getName().getStartPosition();
int invocationStart = node.getStartPosition();
if (path != null && path.length() > 0) {
try {
Location location = new Location(doc.getUri(), doc.toRange(methodNameStart, node.getLength() - (methodNameStart - invocationStart)));
String label = "@" + (path.startsWith("/") ? path : ("/" + path)) + (httpMethod == null || httpMethod.isEmpty() ? "" : " -- " + httpMethod);
result.add(new SymbolInformation(label, SymbolKind.Interface, location));
} catch (BadLocationException e) {
e.printStackTrace();
}
}
}
private String extractPathFromRouterFunction(MethodInvocation routerInvocation) {
WebfluxPathFinder pathFinder = new WebfluxPathFinder(routerInvocation);
routerInvocation.accept(pathFinder);
String path = pathFinder.getPath();
if (path == null) path = "";
return path;
}
private String extractPath(ASTNode node, String path) {
if (node == null || node instanceof TypeDeclaration) {
return path;
}
if (node instanceof MethodInvocation) {
MethodInvocation methodInvocation = (MethodInvocation) node;
IMethodBinding methodBinding = methodInvocation.resolveMethodBinding();
if (ROUTER_FUNCTIONS_TYPE.equals(methodBinding.getDeclaringClass().getBinaryName())) {
String name = methodBinding.getName();
if ("nest".equals(name)) {
List<?> arguments = methodInvocation.arguments();
for (Object argument : arguments) {
if (argument instanceof MethodInvocation) {
MethodInvocation nestedMethod = (MethodInvocation) argument;
IMethodBinding nestedMethodBinding = nestedMethod.resolveMethodBinding();
String nestedMethodName = nestedMethodBinding.getName();
if ("path".equals(nestedMethodName)) {
String additionalPath = extractPath(nestedMethod);
if (additionalPath != null && additionalPath.length() > 0) {
path = additionalPath + path;
}
}
}
}
}
}
}
return extractPath(node.getParent(), path);
}
protected static String extractPath(MethodInvocation node) {
List<?> arguments = node.arguments();
if (arguments != null && arguments.size() > 0) {
Object object = arguments.get(0);
if (object instanceof StringLiteral) {
String path = ((StringLiteral) object).getLiteralValue();
return path;
}
}
return null;
}
private String extractMethod(MethodInvocation node) {
return null;
}
}

View File

@@ -55,8 +55,23 @@ public class WebFluxMappingSymbolProviderTest {
String docUri = directory.toPath().resolve("src/main/java/org/test/QuoteRouter.java").toUri().toString();
List<? extends SymbolInformation> symbols = getSymbols(docUri);
assertEquals(6, symbols.size());
assertTrue(containsSymbol(symbols, "@/hello", docUri, 22, 23, 22, 81));
assertTrue(containsSymbol(symbols, "@/echo", docUri, 23, 1, 23, 85));
assertTrue(containsSymbol(symbols, "@/hello", docUri, 22, 5, 22, 70));
assertTrue(containsSymbol(symbols, "@/echo", docUri, 23, 5, 23, 101));
assertTrue(containsSymbol(symbols, "@/quotes", docUri, 24, 5, 24, 86));
assertTrue(containsSymbol(symbols, "@/quotes", docUri, 25, 5, 25, 94));
}
@Test
public void testNestedRoutesMappingSymbols() throws Exception {
harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/test-webflux-project/").toURI()));
File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-webflux-project/").toURI());
String docUri = directory.toPath().resolve("src/main/java/org/test/NestedRouter.java").toUri().toString();
List<? extends SymbolInformation> symbols = getSymbols(docUri);
assertEquals(5, symbols.size());
assertTrue(containsSymbol(symbols, "@/person/{id}", docUri, 27, 6, 27, 45));
assertTrue(containsSymbol(symbols, "@/person/", docUri, 29, 6, 29, 83));
assertTrue(containsSymbol(symbols, "@/person", docUri, 28, 7, 28, 60));
}
private boolean containsSymbol(List<? extends SymbolInformation> symbols, String name, String uri, int startLine, int startCHaracter, int endLine, int endCharacter) {

View File

@@ -0,0 +1,33 @@
package org.test;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RequestPredicates.contentType;
import static org.springframework.web.reactive.function.server.RequestPredicates.method;
import static org.springframework.web.reactive.function.server.RequestPredicates.path;
import static org.springframework.web.reactive.function.server.RouterFunctions.nest;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
@Configuration
public class NestedRouter {
@Bean
public RouterFunction<ServerResponse> routingFunction() {
PersonHandler handler = new PersonHandler();
return nest(path("/person"),
nest(accept(APPLICATION_JSON),
route(GET("/{id}"), handler::getPerson)
.andRoute(method(HttpMethod.GET), handler::listPeople)
).andRoute(POST("/").and(contentType(APPLICATION_JSON)), handler::createPerson));
}
}

View File

@@ -0,0 +1,22 @@
package org.test;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
public class PersonHandler {
public Mono<ServerResponse> getPerson(ServerRequest request) {
return ServerResponse.notFound().build();
}
public Mono<ServerResponse> createPerson(ServerRequest request) {
return ServerResponse.notFound().build();
}
public Mono<ServerResponse> listPeople(ServerRequest request) {
return ServerResponse.notFound().build();
}
}