finished first version of symbol support for webflux route definitions
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user