diff --git a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/AbstractSpringIndexElement.java b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/AbstractSpringIndexElement.java new file mode 100644 index 0000000000..2ef72947aa --- /dev/null +++ b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/AbstractSpringIndexElement.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2024 Broadcom + * 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 + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Broadcom - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.protocol.spring; + +public abstract class AbstractSpringIndexElement implements SpringIndexElement { + + public static final SpringIndexElement[] NO_CHILDREN = new SpringIndexElement[0]; + + private final SpringIndexElement[] children; + + public AbstractSpringIndexElement(SpringIndexElement[] children) { + this.children = children != null ? children : NO_CHILDREN; + } + + @Override + public SpringIndexElement[] getChildren() { + return children; + } + +} diff --git a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/Bean.java b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/Bean.java index 74b379469f..0f9c022ba3 100644 --- a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/Bean.java +++ b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/Bean.java @@ -16,7 +16,7 @@ import com.google.gson.Gson; -public class Bean { +public class Bean extends AbstractSpringIndexElement { private final String name; private final String type; @@ -32,7 +32,11 @@ public Bean( Location location, InjectionPoint[] injectionPoints, Set supertypes, - AnnotationMetadata[] annotations, boolean isConfiguration) { + AnnotationMetadata[] annotations, + boolean isConfiguration, + SpringIndexElement[] children) { + + super(children); this.name = name; this.type = type; @@ -64,6 +68,18 @@ else if (supertypes != null && supertypes.size() == 1 && supertypes.contains("ja } } + public Bean( + String name, + String type, + Location location, + InjectionPoint[] injectionPoints, + Set supertypes, + AnnotationMetadata[] annotations, + boolean isConfiguration) { + this(name, type, location, injectionPoints, supertypes, annotations, isConfiguration, AbstractSpringIndexElement.NO_CHILDREN); + } + + public String getName() { return name; } diff --git a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/SpringIndexElement.java b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/SpringIndexElement.java new file mode 100644 index 0000000000..7c153de519 --- /dev/null +++ b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/SpringIndexElement.java @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2024 Broadcom + * 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 + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Broadcom - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.protocol.spring; + +public interface SpringIndexElement { + + SpringIndexElement[] getChildren(); + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndexerConfig.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndexerConfig.java index d010e18b5b..80d66084a2 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndexerConfig.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndexerConfig.java @@ -21,7 +21,6 @@ import org.springframework.ide.vscode.boot.java.data.DataRepositorySymbolProvider; import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingSymbolProvider; -import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxRouterSymbolProvider; import org.springframework.ide.vscode.boot.java.utils.RestrictedDefaultSymbolProvider; @Configuration(proxyBeanMethods = false) @@ -36,7 +35,6 @@ AnnotationHierarchyAwareLookup symbolProviders(IndexCache cache) ComponentSymbolProvider componentSymbolProvider = new ComponentSymbolProvider(); RestrictedDefaultSymbolProvider restrictedDefaultSymbolProvider = new RestrictedDefaultSymbolProvider(); DataRepositorySymbolProvider dataRepositorySymbolProvider = new DataRepositorySymbolProvider(); - WebfluxRouterSymbolProvider webfluxRouterSymbolProvider = new WebfluxRouterSymbolProvider(); providers.put(Annotations.SPRING_REQUEST_MAPPING, requestMappingSymbolProvider); providers.put(Annotations.SPRING_GET_MAPPING, requestMappingSymbolProvider); @@ -72,7 +70,6 @@ AnnotationHierarchyAwareLookup symbolProviders(IndexCache cache) providers.put(Annotations.CONDITIONAL_ON_SINGLE_CANDIDATE, restrictedDefaultSymbolProvider); providers.put(Annotations.REPOSITORY, dataRepositorySymbolProvider); - providers.put("", webfluxRouterSymbolProvider); providers.put(Annotations.FEIGN_CLIENT, new FeignClientSymbolProvider()); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCacheOnDiscDeltaBased.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCacheOnDiscDeltaBased.java index 1896ae6cd5..0a1e909cb6 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCacheOnDiscDeltaBased.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCacheOnDiscDeltaBased.java @@ -46,6 +46,7 @@ import org.springframework.ide.vscode.commons.protocol.spring.Bean; import org.springframework.ide.vscode.commons.protocol.spring.DefaultValues; import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint; +import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; import org.springframework.ide.vscode.commons.util.UriUtil; import com.google.common.collect.ImmutableMultimap; @@ -374,6 +375,7 @@ public static Gson createGson() { .registerTypeAdapter(Bean.class, new BeanJsonAdapter()) .registerTypeAdapter(InjectionPoint.class, new InjectionPointJsonAdapter()) .registerTypeAdapter(IndexCacheStore.class, new IndexCacheStoreAdapter()) + .registerTypeAdapter(SpringIndexElement.class, new SpringIndexElementAdapter()) .create(); } @@ -575,6 +577,7 @@ public IndexCacheStore apply(IndexCacheStore store) { // GSON serialize / deserialize adapters for the various types involved here that have special needs around JSON // // + private static class IndexCacheStoreAdapter implements JsonDeserializer> { @@ -610,9 +613,6 @@ public IndexCacheStore deserialize(JsonElement json, Type typeOfT, JsonDeseri } - /** - * gson adapter to store subtype information for symbol addon informations - */ private static class DeltaStorageAdapter implements JsonSerializer>, JsonDeserializer> { @Override @@ -637,9 +637,6 @@ public DeltaStorage deserialize(JsonElement json, Type type, JsonDeserializat } } - /** - * gson adapter to store subtype information for symbol addon informations - */ private static class SymbolAddOnInformationAdapter implements JsonSerializer, JsonDeserializer { @Override @@ -664,9 +661,6 @@ public SymbolAddOnInformation deserialize(JsonElement json, Type type, JsonDeser } } - /** - * gson adapter to store subtype information for beans - */ private static class BeanJsonAdapter implements JsonDeserializer { @Override @@ -690,8 +684,11 @@ public Bean deserialize(JsonElement json, Type type, JsonDeserializationContext JsonElement isConfigurationObject = parsedObject.get("isConfiguration"); boolean isConfiguration = context.deserialize(isConfigurationObject, boolean.class); + + JsonElement childrenObject = parsedObject.get("children"); + SpringIndexElement[] children = context.deserialize(childrenObject, SpringIndexElement[].class); - return new Bean(beanName, beanType, location, injectionPoints, supertypes, annotations, isConfiguration); + return new Bean(beanName, beanType, location, injectionPoints, supertypes, annotations, isConfiguration, children); } } @@ -714,4 +711,27 @@ public InjectionPoint deserialize(JsonElement json, Type type, JsonDeserializati } } + private static class SpringIndexElementAdapter implements JsonSerializer, JsonDeserializer { + + @Override + public JsonElement serialize(SpringIndexElement element, Type typeOfSrc, JsonSerializationContext context) { + JsonElement elem = context.serialize(element); + elem.getAsJsonObject().addProperty("type", element.getClass().getName()); + return elem; + } + + @Override + public SpringIndexElement deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + String typeName = jsonObject.get("type").getAsString(); + + try { + return context.deserialize(jsonObject, (Class) Class.forName(typeName)); + } catch (ClassNotFoundException e) { + throw new JsonParseException(e); + } + } + } + + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java index b87d7f3749..91d3bd6918 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java @@ -187,7 +187,7 @@ public BootJavaLanguageServerComponents(ApplicationContext appContext) { spelSemanticTokens = appContext.getBean(SpelSemanticTokens.class); - codeLensHandler = createCodeLensEngine(springSymbolIndex, projectFinder, server, spelSemanticTokens); + codeLensHandler = createCodeLensEngine(springIndex, projectFinder, server, spelSemanticTokens); highlightsEngine = createDocumentHighlightEngine(appContext); documents.onDocumentHighlight(highlightsEngine); @@ -328,9 +328,9 @@ protected ReferencesHandler createReferenceHandler(SimpleLanguageServer server, return new BootJavaReferencesHandler(this, cuCache, projectFinder, providers); } - protected BootJavaCodeLensEngine createCodeLensEngine(SpringSymbolIndex index, JavaProjectFinder projectFinder, SimpleLanguageServer server, SpelSemanticTokens spelSemanticTokens) { + protected BootJavaCodeLensEngine createCodeLensEngine(SpringMetamodelIndex springIndex, JavaProjectFinder projectFinder, SimpleLanguageServer server, SpelSemanticTokens spelSemanticTokens) { Collection codeLensProvider = new ArrayList<>(); - codeLensProvider.add(new WebfluxHandlerCodeLensProvider(index)); + codeLensProvider.add(new WebfluxHandlerCodeLensProvider(springIndex)); codeLensProvider.add(new CopilotCodeLensProvider(projectFinder, server, spelSemanticTokens)); return new BootJavaCodeLensEngine(this, codeLensProvider); @@ -338,7 +338,7 @@ protected BootJavaCodeLensEngine createCodeLensEngine(SpringSymbolIndex index, J protected BootJavaDocumentHighlightEngine createDocumentHighlightEngine(ApplicationContext appContext) { Collection highlightProvider = new ArrayList<>(); - highlightProvider.add(new WebfluxRouteHighlightProdivder(appContext.getBean(SpringSymbolIndex.class))); + highlightProvider.add(new WebfluxRouteHighlightProdivder(appContext.getBean(SpringMetamodelIndex.class))); Map astHighlightProviders = appContext.getBeansOfType(JdtAstDocHighlightsProvider.class); if (!astHighlightProviders.isEmpty()) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansSymbolProvider.java index e02c5ec0fa..070247c863 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansSymbolProvider.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -34,13 +35,16 @@ import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider; import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation; +import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxRouterSymbolProvider; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; import org.springframework.ide.vscode.boot.java.utils.CachedSymbol; import org.springframework.ide.vscode.boot.java.utils.FunctionUtils; import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext; +import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJava.SCAN_PASS; import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata; import org.springframework.ide.vscode.commons.protocol.spring.Bean; import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint; +import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.text.DocumentRegion; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -57,13 +61,12 @@ */ public class BeansSymbolProvider extends AbstractSymbolProvider { - private static final Logger log = LoggerFactory.getLogger(BeansSymbolProvider.class); private static final String[] NAME_ATTRIBUTES = {"value", "name"}; @Override - protected void addSymbolsPass1(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { + public void addSymbols(Annotation node, ITypeBinding typeBinding, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { if (node == null) return; ASTNode parent = node.getParent(); @@ -73,7 +76,22 @@ protected void addSymbolsPass1(Annotation node, ITypeBinding annotationType, Col if (isMethodAbstract(method)) return; + boolean isWebfluxRouter = WebfluxRouterSymbolProvider.isWebfluxRouterBean(method); + + // for webflux details, we need full method body ASTs + if (isWebfluxRouter && SCAN_PASS.ONE.equals(context.getPass())) { + context.getNextPassFiles().add(context.getFile()); + return; + } + + List childElements = new ArrayList<>(); + + if (isWebfluxRouter) { + WebfluxRouterSymbolProvider.createWebfluxElements(method, context, doc, childElements); + } + boolean isFunction = isFunctionBean(method); + ITypeBinding beanType = getBeanType(method); String markerString = getAnnotations(method); @@ -97,7 +115,7 @@ protected void addSymbolsPass1(Annotation node, ITypeBinding annotationType, Col Collection annotationsOnMethod = ASTUtils.getAnnotations(method); AnnotationMetadata[] annotations = ASTUtils.getAnnotationsMetadata(annotationsOnMethod, doc); - Bean beanDefinition = new Bean(nameAndRegion.getT1(), beanType.getQualifiedName(), location, injectionPoints, supertypes, annotations, false); + Bean beanDefinition = new Bean(nameAndRegion.getT1(), beanType.getQualifiedName(), location, injectionPoints, supertypes, annotations, false, childElements.toArray(SpringIndexElement[]::new)); context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), enhancedSymbol)); context.getBeans().add(new CachedBean(context.getDocURI(), beanDefinition)); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerCodeLensProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerCodeLensProvider.java index 20fa8c0a94..775b902293 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerCodeLensProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerCodeLensProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018, 2021 Pivotal, Inc. + * Copyright (c) 2018, 2024 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 @@ -9,7 +9,7 @@ * Pivotal, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.vscode.boot.java.requestmapping; - +import java.util.Arrays; import java.util.List; import org.eclipse.jdt.core.dom.ASTVisitor; @@ -19,9 +19,9 @@ import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.jsonrpc.CancelChecker; -import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; +import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; import org.springframework.ide.vscode.boot.java.handlers.CodeLensProvider; -import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation; +import org.springframework.ide.vscode.commons.protocol.spring.Bean; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -30,10 +30,10 @@ */ public class WebfluxHandlerCodeLensProvider implements CodeLensProvider { - private final SpringSymbolIndex springIndexer; + private final SpringMetamodelIndex springIndex; - public WebfluxHandlerCodeLensProvider(SpringSymbolIndex springIndexer) { - this.springIndexer = springIndexer; + public WebfluxHandlerCodeLensProvider(SpringMetamodelIndex springIndex) { + this.springIndex = springIndex; } @Override @@ -59,21 +59,11 @@ protected void provideCodeLens(CancelChecker cancelToken, MethodDeclaration node final String handlerMethod = methodBinding.getMethodDeclaration().toString().trim(); cancelToken.checkCanceled(); - - List handlerInfos = this.springIndexer.getAllAdditionalInformation((addon) -> { - if (addon instanceof WebfluxHandlerInformation) { - WebfluxHandlerInformation handlerInfo = (WebfluxHandlerInformation) addon; - return handlerInfo.getHandlerClass() != null && handlerInfo.getHandlerClass().equals(handlerClass) - && handlerInfo.getHandlerMethod() != null && handlerInfo.getHandlerMethod().equals(handlerMethod); - } - return false; - }); - - if (handlerInfos != null && handlerInfos.size() > 0) { - for (Object object : handlerInfos) { + + List matchingHandlerMethods = findMatchingHandlerMethogs(handlerClass, handlerMethod); + if (matchingHandlerMethods.size() > 0) { + for (WebfluxHandlerMethodIndexElement handlerInfo : matchingHandlerMethods) { try { - WebfluxHandlerInformation handlerInfo = (WebfluxHandlerInformation) object; - CodeLens codeLens = new CodeLens(); codeLens.setRange(document.toRange(node.getName().getStartPosition(), node.getName().getLength())); @@ -101,4 +91,16 @@ protected void provideCodeLens(CancelChecker cancelToken, MethodDeclaration node } } + private List findMatchingHandlerMethogs(String handlerClass, String handlerMethod) { + Bean[] beans = springIndex.getBeans(); + + return Arrays.stream(beans) + .flatMap(bean -> Arrays.stream(bean.getChildren())) + .filter(element -> element instanceof WebfluxHandlerMethodIndexElement) + .map(element -> (WebfluxHandlerMethodIndexElement) element) + .filter(webfluxElement -> webfluxElement.getHandlerClass() != null && webfluxElement.getHandlerClass().equals(handlerClass) + && webfluxElement.getHandlerMethod() != null && webfluxElement.getHandlerMethod().equals(handlerMethod)) + .toList(); + } + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerInformation.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerMethodIndexElement.java similarity index 73% rename from headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerInformation.java rename to headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerMethodIndexElement.java index 99f0c9f36b..53e50d4d99 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerInformation.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerMethodIndexElement.java @@ -1,22 +1,19 @@ /******************************************************************************* - * Copyright (c) 2018 Pivotal, Inc. + * Copyright (c) 2024 Broadcom * 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 * https://www.eclipse.org/legal/epl-v10.html * * Contributors: - * Pivotal, Inc. - initial API and implementation + * Broadcom - initial API and implementation *******************************************************************************/ package org.springframework.ide.vscode.boot.java.requestmapping; -import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation; +import org.springframework.ide.vscode.commons.protocol.spring.AbstractSpringIndexElement; + +public class WebfluxHandlerMethodIndexElement extends AbstractSpringIndexElement { -/** - * @author Martin Lippert - */ -public class WebfluxHandlerInformation implements SymbolAddOnInformation { - private final String handlerClass; private final String handlerMethod; @@ -25,7 +22,9 @@ public class WebfluxHandlerInformation implements SymbolAddOnInformation { private final String[] contentTypes; private final String[] acceptTypes; - public WebfluxHandlerInformation(String handlerClass, String handlerMethod, String path, String[] httpMethods, String[] contentTypes, String[] acceptTypes) { + public WebfluxHandlerMethodIndexElement(String handlerClass, String handlerMethod, String path, String[] httpMethods, String[] contentTypes, String[] acceptTypes) { + super(AbstractSpringIndexElement.NO_CHILDREN); + this.handlerClass = handlerClass; this.handlerMethod = handlerMethod; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxElementsInformation.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouteElementRangesIndexElement.java similarity index 76% rename from headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxElementsInformation.java rename to headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouteElementRangesIndexElement.java index 66259096f5..2ac1bf8512 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxElementsInformation.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouteElementRangesIndexElement.java @@ -1,30 +1,30 @@ /******************************************************************************* - * Copyright (c) 2018 Pivotal, Inc. + * Copyright (c) 2024 Broadcom * 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 * https://www.eclipse.org/legal/epl-v10.html * * Contributors: - * Pivotal, Inc. - initial API and implementation + * Broadcom - initial API and implementation *******************************************************************************/ package org.springframework.ide.vscode.boot.java.requestmapping; import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; -import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation; +import org.springframework.ide.vscode.commons.protocol.spring.AbstractSpringIndexElement; + +public class WebfluxRouteElementRangesIndexElement extends AbstractSpringIndexElement { + + private static final Range[] NO_RANGES = new Range[0]; -/** - * @author Martin Lippert - */ -public class WebfluxElementsInformation implements SymbolAddOnInformation { - private Range[] ranges; - public WebfluxElementsInformation(Range... ranges) { - this.ranges = ranges; + public WebfluxRouteElementRangesIndexElement(Range... ranges) { + super(AbstractSpringIndexElement.NO_CHILDREN); + this.ranges = ranges != null ? ranges : NO_RANGES; } - + public Range[] getRanges() { return ranges; } @@ -35,10 +35,10 @@ public boolean contains(Position position) { return true; } } - + return false; } - + /** * returns true if position1 is the same or before position2 in a document */ diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouteHighlightProdivder.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouteHighlightProdivder.java index cc79094748..a72ce779b9 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouteHighlightProdivder.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouteHighlightProdivder.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018, 2019 Pivotal, Inc. + * Copyright (c) 2018, 2024 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 @@ -18,8 +18,9 @@ import org.eclipse.lsp4j.jsonrpc.CancelChecker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; +import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; import org.springframework.ide.vscode.boot.java.handlers.HighlightProvider; +import org.springframework.ide.vscode.commons.protocol.spring.Bean; import org.springframework.ide.vscode.commons.util.text.TextDocument; /** @@ -29,10 +30,10 @@ public class WebfluxRouteHighlightProdivder implements HighlightProvider { private static final Logger log = LoggerFactory.getLogger(WebfluxRouteHighlightProdivder.class); - private final SpringSymbolIndex springIndexer; + private final SpringMetamodelIndex springIndex; - public WebfluxRouteHighlightProdivder(SpringSymbolIndex indexer) { - this.springIndexer = indexer; + public WebfluxRouteHighlightProdivder(SpringMetamodelIndex springIndex) { + this.springIndex = springIndex; } @Override @@ -41,19 +42,13 @@ public void provideHighlights(CancelChecker cancelToken, TextDocument document, cancelToken.checkCanceled(); - this.springIndexer.getAdditonalInformation(document.getUri()) - .stream() - .filter(addon -> { - if (addon instanceof WebfluxElementsInformation) { - WebfluxElementsInformation handlerInfo = (WebfluxElementsInformation) addon; - - if (handlerInfo.contains(position)) { - return true; - } - } - return false; - }) - .flatMap(addon -> Arrays.asList(((WebfluxElementsInformation) addon).getRanges()).stream()) + Bean[] beans = springIndex.getBeans(); + Arrays.stream(beans) + .flatMap(bean -> Arrays.stream(bean.getChildren())) + .filter(element -> element instanceof WebfluxRouteElementRangesIndexElement) + .map(element -> (WebfluxRouteElementRangesIndexElement) element) + .filter(rangesElement -> rangesElement.contains(position)) + .flatMap(rangesElement -> Arrays.stream(rangesElement.getRanges())) .forEach(range -> resultAccumulator.add(new DocumentHighlight(range))); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouterSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouterSymbolProvider.java index cc0b778952..8b9fd30c51 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouterSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouterSymbolProvider.java @@ -32,24 +32,42 @@ import org.eclipse.lsp4j.Range; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider; import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation; -import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; import org.springframework.ide.vscode.boot.java.utils.CachedSymbol; import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJava.SCAN_PASS; import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext; +import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.text.TextDocument; /** * @author Martin Lippert */ -public class WebfluxRouterSymbolProvider extends AbstractSymbolProvider { +public class WebfluxRouterSymbolProvider { private static final Logger log = LoggerFactory.getLogger(WebfluxRouterSymbolProvider.class); + + public static boolean isWebfluxRouterBean(MethodDeclaration method) { + Type returnType = method.getReturnType2(); + if (returnType != null) { + ITypeBinding resolvedBinding = returnType.resolveBinding(); + if (resolvedBinding != null && WebfluxUtils.ROUTER_FUNCTION_TYPE.equals(resolvedBinding.getBinaryName())) { + return true; + } + } + return false; + } + + public static SpringIndexElement[] createWebfluxElements(MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc, List childElements) { + Block methodBody = methodDeclaration.getBody(); + if (methodBody != null && methodBody.statements() != null && methodBody.statements().size() > 0) { + addSymbolsForRouterFunction(methodBody, context, doc, childElements); + } + + return new SpringIndexElement[0]; + } - @Override public void addSymbols(MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc) { Type returnType = methodDeclaration.getReturnType2(); if (returnType != null) { @@ -60,7 +78,7 @@ public void addSymbols(MethodDeclaration methodDeclaration, SpringIndexerJavaCon Block methodBody = methodDeclaration.getBody(); if (methodBody != null && methodBody.statements() != null && methodBody.statements().size() > 0) { - addSymbolsForRouterFunction(methodBody, context, doc); + addSymbolsForRouterFunction(methodBody, context, doc, new ArrayList<>()); } else if (SCAN_PASS.ONE.equals(context.getPass())) { context.getNextPassFiles().add(context.getFile()); @@ -70,7 +88,7 @@ else if (SCAN_PASS.ONE.equals(context.getPass())) { } } - private void addSymbolsForRouterFunction(Block methodBody, SpringIndexerJavaContext context, TextDocument doc) { + private static void addSymbolsForRouterFunction(Block methodBody, SpringIndexerJavaContext context, TextDocument doc, List indexElementsCollector) { methodBody.accept(new ASTVisitor() { @Override @@ -78,7 +96,7 @@ public boolean visit(MethodInvocation node) { IMethodBinding methodBinding = node.resolveMethodBinding(); if (methodBinding != null && WebfluxUtils.isRouteMethodInvocation(methodBinding)) { - extractMappingSymbol(node, doc, context); + extractMappingSymbol(node, doc, context, indexElementsCollector); } return super.visit(node); @@ -87,7 +105,7 @@ public boolean visit(MethodInvocation node) { }); } - protected void extractMappingSymbol(MethodInvocation node, TextDocument doc, SpringIndexerJavaContext context) { + protected static void extractMappingSymbol(MethodInvocation node, TextDocument doc, SpringIndexerJavaContext context, List indexElementsCollector) { WebfluxRouteElement[] pathElements = extractPath(node, doc); WebfluxRouteElement[] httpMethods = extractMethods(node, doc); WebfluxRouteElement[] contentTypes = extractContentTypes(node, doc); @@ -107,15 +125,14 @@ protected void extractMappingSymbol(MethodInvocation node, TextDocument doc, Spr try { Location location = new Location(doc.getUri(), doc.toRange(methodNameStart, node.getLength() - (methodNameStart - invocationStart))); - WebfluxHandlerInformation handler = extractHandlerInformation(node, path, httpMethods, contentTypes, acceptTypes); - WebfluxElementsInformation elements = extractElementsInformation(pathElements, httpMethods, contentTypes, acceptTypes); + WebfluxHandlerMethodIndexElement handler = extractHandlerInformation(node, path, httpMethods, contentTypes, acceptTypes); + WebfluxRouteElementRangesIndexElement elements = extractElementsInformation(pathElements, httpMethods, contentTypes, acceptTypes); + + if (handler != null) indexElementsCollector.add(handler); + if (elements != null) indexElementsCollector.add(elements); - SymbolAddOnInformation[] addon = handler != null ? - new SymbolAddOnInformation[] {handler, elements} : - new SymbolAddOnInformation[] {elements}; - EnhancedSymbolInformation enhancedSymbol = RouteUtils.createRouteSymbol(location, path, getElementStrings(httpMethods), - getElementStrings(contentTypes), getElementStrings(acceptTypes), addon); + getElementStrings(contentTypes), getElementStrings(acceptTypes), null); context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), enhancedSymbol)); @@ -125,7 +142,7 @@ protected void extractMappingSymbol(MethodInvocation node, TextDocument doc, Spr } } - private WebfluxElementsInformation extractElementsInformation(WebfluxRouteElement[] path, WebfluxRouteElement[] methods, + private static WebfluxRouteElementRangesIndexElement extractElementsInformation(WebfluxRouteElement[] path, WebfluxRouteElement[] methods, WebfluxRouteElement[] contentTypes, WebfluxRouteElement[] acceptTypes) { List allRanges = new ArrayList<>(); @@ -136,10 +153,10 @@ private WebfluxElementsInformation extractElementsInformation(WebfluxRouteElemen } } - return new WebfluxElementsInformation((Range[]) allRanges.toArray(new Range[allRanges.size()])); + return new WebfluxRouteElementRangesIndexElement((Range[]) allRanges.toArray(new Range[allRanges.size()])); } - private WebfluxRouteElement[] extractPath(MethodInvocation routerInvocation, TextDocument doc) { + private static WebfluxRouteElement[] extractPath(MethodInvocation routerInvocation, TextDocument doc) { WebfluxPathFinder pathFinder = new WebfluxPathFinder(routerInvocation, doc); List arguments = routerInvocation.arguments(); for (Object argument : arguments) { @@ -171,7 +188,7 @@ private WebfluxRouteElement[] extractPath(MethodInvocation routerInvocation, Tex return (WebfluxRouteElement[]) path.toArray(new WebfluxRouteElement[path.size()]); } - private WebfluxRouteElement[] extractMethods(MethodInvocation routerInvocation, TextDocument doc) { + private static WebfluxRouteElement[] extractMethods(MethodInvocation routerInvocation, TextDocument doc) { WebfluxMethodFinder methodFinder = new WebfluxMethodFinder(routerInvocation, doc); List arguments = routerInvocation.arguments(); for (Object argument : arguments) { @@ -204,7 +221,7 @@ private WebfluxRouteElement[] extractMethods(MethodInvocation routerInvocation, return (WebfluxRouteElement[]) methods.toArray(new WebfluxRouteElement[methods.size()]); } - private WebfluxRouteElement[] extractAcceptTypes(MethodInvocation routerInvocation, TextDocument doc) { + private static WebfluxRouteElement[] extractAcceptTypes(MethodInvocation routerInvocation, TextDocument doc) { WebfluxAcceptTypeFinder typeFinder = new WebfluxAcceptTypeFinder(doc); List arguments = routerInvocation.arguments(); for (Object argument : arguments) { @@ -237,7 +254,7 @@ private WebfluxRouteElement[] extractAcceptTypes(MethodInvocation routerInvocati return (WebfluxRouteElement[]) acceptTypes.toArray(new WebfluxRouteElement[acceptTypes.size()]); } - private WebfluxRouteElement[] extractContentTypes(MethodInvocation routerInvocation, TextDocument doc) { + private static WebfluxRouteElement[] extractContentTypes(MethodInvocation routerInvocation, TextDocument doc) { WebfluxContentTypeFinder contentTypeFinder = new WebfluxContentTypeFinder(doc); List arguments = routerInvocation.arguments(); for (Object argument : arguments) { @@ -270,7 +287,7 @@ private WebfluxRouteElement[] extractContentTypes(MethodInvocation routerInvocat return (WebfluxRouteElement[]) contentTypes.toArray(new WebfluxRouteElement[contentTypes.size()]); } - private void extractNestedValue(ASTNode node, Collection values, Function extractor) { + private static void extractNestedValue(ASTNode node, Collection values, Function extractor) { if (node == null || node instanceof TypeDeclaration) { return; } @@ -301,7 +318,7 @@ private void extractNestedValue(ASTNode node, Collection va extractNestedValue(node.getParent(), values, extractor); } - private WebfluxHandlerInformation extractHandlerInformation(MethodInvocation node, String path, WebfluxRouteElement[] httpMethods, + private static WebfluxHandlerMethodIndexElement extractHandlerInformation(MethodInvocation node, String path, WebfluxRouteElement[] httpMethods, WebfluxRouteElement[] contentTypes, WebfluxRouteElement[] acceptTypes) { List arguments = node.arguments(); @@ -319,7 +336,7 @@ private WebfluxHandlerInformation extractHandlerInformation(MethodInvocation nod String handlerMethod = methodBinding.getMethodDeclaration().toString(); if (handlerMethod != null) handlerMethod = handlerMethod.trim(); - return new WebfluxHandlerInformation(handlerClass, handlerMethod, path, getElementStrings(httpMethods), getElementStrings(contentTypes), getElementStrings(acceptTypes)); + return new WebfluxHandlerMethodIndexElement(handlerClass, handlerMethod, path, getElementStrings(httpMethods), getElementStrings(contentTypes), getElementStrings(acceptTypes)); } } } @@ -328,7 +345,7 @@ private WebfluxHandlerInformation extractHandlerInformation(MethodInvocation nod return null; } - private String[] getElementStrings(WebfluxRouteElement[] routeElements) { + private static String[] getElementStrings(WebfluxRouteElement[] routeElements) { List result = new ArrayList<>(); for (int i = 0; i < routeElements.length; i++) { diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/cache/test/IndexCacheOnDiscDeltaBasedTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/cache/test/IndexCacheOnDiscDeltaBasedTest.java index d23183fe15..dae0519d23 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/cache/test/IndexCacheOnDiscDeltaBasedTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/cache/test/IndexCacheOnDiscDeltaBasedTest.java @@ -40,8 +40,6 @@ import org.springframework.ide.vscode.boot.index.cache.IndexCacheKey; import org.springframework.ide.vscode.boot.index.cache.IndexCacheOnDiscDeltaBased; import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation; -import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation; -import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxElementsInformation; import org.springframework.ide.vscode.boot.java.utils.CachedSymbol; import org.springframework.ide.vscode.commons.util.UriUtil; @@ -257,44 +255,6 @@ void testDoNotDeleteCacheFileFromOtherCategory() throws Exception { assertTrue(Files.exists(tempDir.resolve(Paths.get(key1.toString() + STORAGE_FILE_EXTENSION)))); } - @Test - void testEnhancedInformationSubclasses() throws Exception { - Path file1 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile1"); - Files.createFile(file1); - - FileTime timeFile1 = Files.getLastModifiedTime(file1); - String[] files = {file1.toString()}; - String doc1URI = UriUtil.toUri(file1.toFile()).toString(); - - List generatedSymbols = new ArrayList<>(); - - WorkspaceSymbol symbol = new WorkspaceSymbol("symbol1", SymbolKind.Field, Either.forLeft(new Location(doc1URI, new Range(new Position(3, 10), new Position(3, 20))))); - WebfluxElementsInformation addon = new WebfluxElementsInformation(new Range(new Position(4, 4), new Position(5, 5)), new Range(new Position(6, 6), new Position(7, 7))); - EnhancedSymbolInformation enhancedSymbol = new EnhancedSymbolInformation(symbol, new SymbolAddOnInformation[]{addon}); - - generatedSymbols.add(new CachedSymbol(doc1URI, timeFile1.toMillis(), enhancedSymbol)); - - cache.store(CACHE_KEY_VERSION_1, files, generatedSymbols, null, CachedSymbol.class); - - CachedSymbol[] cachedSymbols = cache.retrieveSymbols(CACHE_KEY_VERSION_1, files, CachedSymbol.class); - assertNotNull(cachedSymbols); - assertEquals(1, cachedSymbols.length); - - assertEquals("symbol1", cachedSymbols[0].getEnhancedSymbol().getSymbol().getName()); - assertEquals(SymbolKind.Field, cachedSymbols[0].getEnhancedSymbol().getSymbol().getKind()); - assertEquals(new Location(doc1URI, new Range(new Position(3, 10), new Position(3, 20))), cachedSymbols[0].getEnhancedSymbol().getSymbol().getLocation().getLeft()); - - SymbolAddOnInformation[] retrievedAddOns = cachedSymbols[0].getEnhancedSymbol().getAdditionalInformation(); - assertNotNull(retrievedAddOns); - assertEquals(1, retrievedAddOns.length); - assertTrue(retrievedAddOns[0] instanceof WebfluxElementsInformation); - - Range[] ranges = ((WebfluxElementsInformation) retrievedAddOns[0]).getRanges(); - assertEquals(2, ranges.length); - assertEquals(new Range(new Position(4, 4), new Position(5, 5)), ranges[0]); - assertEquals(new Range(new Position(6, 6), new Position(7, 7)), ranges[1]); - } - @Test void testSymbolAddedToExistingFile() throws Exception { Path file1 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile1"); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/cache/test/IndexCacheOnDiscTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/cache/test/IndexCacheOnDiscTest.java index 85818b23a0..ea693c8345 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/cache/test/IndexCacheOnDiscTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/cache/test/IndexCacheOnDiscTest.java @@ -40,8 +40,6 @@ import org.springframework.ide.vscode.boot.index.cache.IndexCacheKey; import org.springframework.ide.vscode.boot.index.cache.IndexCacheOnDisc; import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation; -import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation; -import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxElementsInformation; import org.springframework.ide.vscode.boot.java.utils.CachedSymbol; import org.springframework.ide.vscode.commons.util.UriUtil; @@ -255,44 +253,6 @@ void testDoNotDeleteCacheFileFromOtherCategory() throws Exception { assertTrue(Files.exists(tempDir.resolve(Paths.get(key1.toString() + ".json")))); } - @Test - void testEnhancedInformationSubclasses() throws Exception { - Path file1 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile1"); - Files.createFile(file1); - - FileTime timeFile1 = Files.getLastModifiedTime(file1); - String[] files = {file1.toString()}; - String doc1URI = UriUtil.toUri(file1.toFile()).toString(); - - List generatedSymbols = new ArrayList<>(); - - WorkspaceSymbol symbol = new WorkspaceSymbol("symbol1", SymbolKind.Field, Either.forLeft(new Location(doc1URI, new Range(new Position(3, 10), new Position(3, 20))))); - WebfluxElementsInformation addon = new WebfluxElementsInformation(new Range(new Position(4, 4), new Position(5, 5)), new Range(new Position(6, 6), new Position(7, 7))); - EnhancedSymbolInformation enhancedSymbol = new EnhancedSymbolInformation(symbol, new SymbolAddOnInformation[]{addon}); - - generatedSymbols.add(new CachedSymbol(doc1URI, timeFile1.toMillis(), enhancedSymbol)); - - cache.store(CACHE_KEY_VERSION_1, files, generatedSymbols, null, CachedSymbol.class); - - CachedSymbol[] cachedSymbols = cache.retrieveSymbols(CACHE_KEY_VERSION_1, files, CachedSymbol.class); - assertNotNull(cachedSymbols); - assertEquals(1, cachedSymbols.length); - - assertEquals("symbol1", cachedSymbols[0].getEnhancedSymbol().getSymbol().getName()); - assertEquals(SymbolKind.Field, cachedSymbols[0].getEnhancedSymbol().getSymbol().getKind()); - assertEquals(new Location(doc1URI, new Range(new Position(3, 10), new Position(3, 20))), cachedSymbols[0].getEnhancedSymbol().getSymbol().getLocation().getLeft()); - - SymbolAddOnInformation[] retrievedAddOns = cachedSymbols[0].getEnhancedSymbol().getAdditionalInformation(); - assertNotNull(retrievedAddOns); - assertEquals(1, retrievedAddOns.length); - assertTrue(retrievedAddOns[0] instanceof WebfluxElementsInformation); - - Range[] ranges = ((WebfluxElementsInformation) retrievedAddOns[0]).getRanges(); - assertEquals(2, ranges.length); - assertEquals(new Range(new Position(4, 4), new Position(5, 5)), ranges[0]); - assertEquals(new Range(new Position(6, 6), new Position(7, 7)), ranges[1]); - } - @Test void testSymbolAddedToExistingFile() throws Exception { Path file1 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile1"); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexTest.java index 070933f5df..a6d3c6bc6e 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexTest.java @@ -29,12 +29,15 @@ import org.eclipse.lsp4j.Range; import org.junit.jupiter.api.Test; import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; -import org.springframework.ide.vscode.boot.index.cache.IndexCacheOnDisc; +import org.springframework.ide.vscode.boot.index.cache.IndexCacheOnDiscDeltaBased; +import org.springframework.ide.vscode.commons.protocol.spring.AbstractSpringIndexElement; import org.springframework.ide.vscode.commons.protocol.spring.AnnotationAttributeValue; import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata; import org.springframework.ide.vscode.commons.protocol.spring.Bean; import org.springframework.ide.vscode.commons.protocol.spring.DefaultValues; import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint; +import org.springframework.ide.vscode.commons.protocol.spring.RequestMapping; +import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; import com.google.gson.Gson; @@ -250,7 +253,7 @@ void testOverallSerializeDeserializeBeans() { Bean bean1 = new Bean("beanName1", "beanType", locationForDoc1, new InjectionPoint[] {point1, point2}, Set.of("supertype1", "supertype2"), emptyAnnotations, true); String serialized = bean1.toString(); - Gson gson = IndexCacheOnDisc.createGson(); + Gson gson = IndexCacheOnDiscDeltaBased.createGson(); Bean deserializedBean = gson.fromJson(serialized, Bean.class); assertEquals("beanName1", deserializedBean.getName()); @@ -301,7 +304,7 @@ void testEmptyInjectionPointsOptimizationWithSerializeDeserializeBeans() { Bean bean1 = new Bean("beanName1", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes, emptyAnnotations, false); String serialized = bean1.toString(); - Gson gson = IndexCacheOnDisc.createGson(); + Gson gson = IndexCacheOnDiscDeltaBased.createGson(); Bean deserializedBean = gson.fromJson(serialized, Bean.class); assertEquals("beanName1", deserializedBean.getName()); @@ -395,5 +398,90 @@ void testFindMatchingBeansWithMultipleProjects() { matchingBeans = index.getMatchingBeans("otherProject", "supertype1"); } + + @Test + void testBasicSpringIndexStructure() { + SubType1 child1 = new SubType1(AbstractSpringIndexElement.NO_CHILDREN); + SpringIndexElement[] children = new SpringIndexElement[] {child1}; + Bean bean1 = new Bean("beanName1", "beanType1", locationForDoc1, emptyInjectionPoints, Set.of("supertype1", "supertype2"), emptyAnnotations, false, children); + + SpringIndexElement[] children2 = bean1.getChildren(); + assertEquals(1, children2.length); + assertSame(child1, children2[0]); + } + + @Test + void testSpringIndexStructurePolymorphicSerialization() { + Gson gson = IndexCacheOnDiscDeltaBased.createGson(); + + SubType2 subNode = new SubType2(null); + + SubType1 node1 = new SubType1(new SpringIndexElement[] {subNode}); + SubType2 node2 = new SubType2(null); + + Root root = new Root(new SpringIndexElement[] {node1, node2}); + + String json = gson.toJson(root); + Root deserializedRoot = gson.fromJson(json, Root.class); + + SpringIndexElement[] children = deserializedRoot.getChildren(); + assertEquals(2, children.length); + + SubType1 deserializedNode1 = (SubType1) java.util.Arrays.stream(children).filter(node -> node instanceof SubType1).findAny().get(); + SubType2 deserializedNode2 = (SubType2) java.util.Arrays.stream(children).filter(node -> node instanceof SubType2).findAny().get(); + + assertNotNull(deserializedNode1); + assertNotNull(deserializedNode2); + + SpringIndexElement[] deserializedChild2 = deserializedNode1.getChildren(); + assertEquals(1, deserializedChild2.length); + assertTrue(deserializedChild2[0] instanceof SubType2); + } + + @Test + void testSerializeDeserializeBeansWithChildElements() { + + Gson gson = IndexCacheOnDiscDeltaBased.createGson(); + + SubType2 childOfChild = new SubType2(null); + SubType1 child1 = new SubType1(new SpringIndexElement[] {childOfChild}); + SubType2 child2 = new SubType2(null); + Bean bean1 = new Bean("beanName1", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes, emptyAnnotations, true, new SpringIndexElement[] {child1, child2}); + + String serialized = gson.toJson(bean1); + Bean deserializedBean = gson.fromJson(serialized, Bean.class); + + SpringIndexElement[] children = deserializedBean.getChildren(); + assertEquals(2, children.length); + + SpringIndexElement deserializedChild1 = java.util.Arrays.stream(children).filter(element -> element instanceof SubType1).findAny().get(); + assertNotNull(deserializedChild1); + + SpringIndexElement[] childrenOfChild = deserializedChild1.getChildren(); + assertEquals(1, childrenOfChild.length); + assertTrue(childrenOfChild[0] instanceof SubType2); + + SpringIndexElement deserializedChild2 = java.util.Arrays.stream(children).filter(element -> element instanceof SubType2).findAny().get(); + assertNotNull(deserializedChild2); + assertEquals(0, deserializedChild2.getChildren().length); + } + + static class SubType1 extends AbstractSpringIndexElement { + public SubType1(SpringIndexElement[] children) { + super(children); + } + } + + static class SubType2 extends AbstractSpringIndexElement { + public SubType2(SpringIndexElement[] children) { + super(children); + } + } + + static class Root extends AbstractSpringIndexElement { + public Root(SpringIndexElement[] children) { + super(children); + } + } } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxMappingSymbolProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxMappingSymbolProviderTest.java index 3cf9a7087c..c094e2a308 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxMappingSymbolProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxMappingSymbolProviderTest.java @@ -32,10 +32,11 @@ import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; -import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation; -import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxHandlerInformation; +import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxHandlerMethodIndexElement; +import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.protocol.spring.Bean; +import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; import org.springframework.ide.vscode.project.harness.ProjectsHarness; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -54,6 +55,7 @@ public class WebFluxMappingSymbolProviderTest { @Autowired private SpringMetamodelIndex springIndex; private File directory; + private IJavaProject project; @BeforeEach public void setup() throws Exception { @@ -62,7 +64,7 @@ public void setup() throws Exception { String projectDir = directory.toURI().toString(); // trigger project creation - projectFinder.find(new TextDocumentIdentifier(projectDir)).get(); + project = projectFinder.find(new TextDocumentIdentifier(projectDir)).get(); CompletableFuture initProject = indexer.waitOperation(); initProject.get(5, TimeUnit.SECONDS); @@ -91,41 +93,45 @@ void testRoutesMappingSymbols() throws Exception { assertTrue(containsSymbol(symbols, "@/echo -- POST - Accept: text/plain - Content-Type: text/plain", docUri, 23, 5, 23, 101)); assertTrue(containsSymbol(symbols, "@/quotes -- GET - Accept: application/json", docUri, 24, 5, 24, 86)); assertTrue(containsSymbol(symbols, "@/quotes -- GET - Accept: application/stream+json", docUri, 25, 5, 25, 94)); - - List addons = indexer.getAdditonalInformation(docUri); - assertEquals(8, addons.size()); - - WebfluxHandlerInformation handlerInfo1 = getWebfluxHandler(addons, "/hello", "GET").get(0); - assertEquals("/hello", handlerInfo1.getPath()); - assertEquals("[GET]", Arrays.toString(handlerInfo1.getHttpMethods())); - assertEquals(0, handlerInfo1.getContentTypes().length); - assertEquals("[TEXT_PLAIN]", Arrays.toString(handlerInfo1.getAcceptTypes())); - assertEquals("org.test.QuoteHandler", handlerInfo1.getHandlerClass()); - assertEquals("public Mono hello(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo1.getHandlerMethod()); - - WebfluxHandlerInformation handlerInfo2 = getWebfluxHandler(addons, "/echo", "POST").get(0); - assertEquals("/echo", handlerInfo2.getPath()); - assertEquals("[POST]", Arrays.toString(handlerInfo2.getHttpMethods())); - assertEquals("[TEXT_PLAIN]", Arrays.toString(handlerInfo2.getContentTypes())); - assertEquals("[TEXT_PLAIN]", Arrays.toString(handlerInfo2.getAcceptTypes())); - assertEquals("org.test.QuoteHandler", handlerInfo2.getHandlerClass()); - assertEquals("public Mono echo(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo2.getHandlerMethod()); - - WebfluxHandlerInformation handlerInfo3 = getWebfluxHandler(addons, "/quotes", "GET").get(0); - assertEquals("/quotes", handlerInfo3.getPath()); - assertEquals("[GET]", Arrays.toString(handlerInfo3.getHttpMethods())); - assertEquals(0, handlerInfo3.getContentTypes().length); - assertEquals("[APPLICATION_STREAM_JSON]", Arrays.toString(handlerInfo3.getAcceptTypes())); - assertEquals("org.test.QuoteHandler", handlerInfo3.getHandlerClass()); - assertEquals("public Mono streamQuotes(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo3.getHandlerMethod()); - - WebfluxHandlerInformation handlerInfo4 = getWebfluxHandler(addons, "/quotes", "GET").get(1); - assertEquals("/quotes", handlerInfo4.getPath()); - assertEquals("[GET]", Arrays.toString(handlerInfo4.getHttpMethods())); - assertEquals(0, handlerInfo4.getContentTypes().length); - assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerInfo4.getAcceptTypes())); - assertEquals("org.test.QuoteHandler", handlerInfo4.getHandlerClass()); - assertEquals("public Mono fetchQuotes(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo4.getHandlerMethod()); + + Bean[] routeBeans = springIndex.getBeansWithName(project.getElementName(), "route"); + assertEquals(1, routeBeans.length); + assertEquals("route", routeBeans[0].getName()); + + SpringIndexElement[] children = routeBeans[0].getChildren(); + assertEquals(8, children.length); + + WebfluxHandlerMethodIndexElement handlerElement1 = getWebfluxIndexElements(children, "/hello", "GET").get(0); + assertEquals("/hello", handlerElement1.getPath()); + assertEquals("[GET]", Arrays.toString(handlerElement1.getHttpMethods())); + assertEquals(0, handlerElement1.getContentTypes().length); + assertEquals("[TEXT_PLAIN]", Arrays.toString(handlerElement1.getAcceptTypes())); + assertEquals("org.test.QuoteHandler", handlerElement1.getHandlerClass()); + assertEquals("public Mono hello(org.springframework.web.reactive.function.server.ServerRequest)", handlerElement1.getHandlerMethod()); + + WebfluxHandlerMethodIndexElement handlerElement2 = getWebfluxIndexElements(children, "/echo", "POST").get(0); + assertEquals("/echo", handlerElement2.getPath()); + assertEquals("[POST]", Arrays.toString(handlerElement2.getHttpMethods())); + assertEquals("[TEXT_PLAIN]", Arrays.toString(handlerElement2.getContentTypes())); + assertEquals("[TEXT_PLAIN]", Arrays.toString(handlerElement2.getAcceptTypes())); + assertEquals("org.test.QuoteHandler", handlerElement2.getHandlerClass()); + assertEquals("public Mono echo(org.springframework.web.reactive.function.server.ServerRequest)", handlerElement2.getHandlerMethod()); + + WebfluxHandlerMethodIndexElement handlerElement3 = getWebfluxIndexElements(children, "/quotes", "GET").get(0); + assertEquals("/quotes", handlerElement3.getPath()); + assertEquals("[GET]", Arrays.toString(handlerElement3.getHttpMethods())); + assertEquals(0, handlerElement3.getContentTypes().length); + assertEquals("[APPLICATION_STREAM_JSON]", Arrays.toString(handlerElement3.getAcceptTypes())); + assertEquals("org.test.QuoteHandler", handlerElement3.getHandlerClass()); + assertEquals("public Mono streamQuotes(org.springframework.web.reactive.function.server.ServerRequest)", handlerElement3.getHandlerMethod()); + + WebfluxHandlerMethodIndexElement handlerElement4 = getWebfluxIndexElements(children, "/quotes", "GET").get(1); + assertEquals("/quotes", handlerElement4.getPath()); + assertEquals("[GET]", Arrays.toString(handlerElement4.getHttpMethods())); + assertEquals(0, handlerElement4.getContentTypes().length); + assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerElement4.getAcceptTypes())); + assertEquals("org.test.QuoteHandler", handlerElement4.getHandlerClass()); + assertEquals("public Mono fetchQuotes(org.springframework.web.reactive.function.server.ServerRequest)", handlerElement4.getHandlerMethod()); } @Test @@ -136,33 +142,37 @@ void testNestedRoutesMappingSymbols1() throws Exception { assertTrue(containsSymbol(symbols, "@/person/{id} -- GET - Accept: application/json", docUri, 27, 6, 27, 45)); assertTrue(containsSymbol(symbols, "@/person/ -- POST - Content-Type: application/json", docUri, 29, 6, 29, 83)); assertTrue(containsSymbol(symbols, "@/person -- GET - Accept: application/json", docUri, 28, 7, 28, 60)); - - List addons = indexer.getAdditonalInformation(docUri); - assertEquals(6, addons.size()); - - WebfluxHandlerInformation handlerInfo1 = getWebfluxHandler(addons, "/person/{id}", "GET").get(0); - assertEquals("/person/{id}", handlerInfo1.getPath()); - assertEquals("[GET]", Arrays.toString(handlerInfo1.getHttpMethods())); - assertEquals(0, handlerInfo1.getContentTypes().length); - assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerInfo1.getAcceptTypes())); - assertEquals("org.test.PersonHandler1", handlerInfo1.getHandlerClass()); - assertEquals("public Mono getPerson(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo1.getHandlerMethod()); - - WebfluxHandlerInformation handlerInfo2 = getWebfluxHandler(addons, "/person/", "POST").get(0); - assertEquals("/person/", handlerInfo2.getPath()); - assertEquals("[POST]", Arrays.toString(handlerInfo2.getHttpMethods())); - assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerInfo2.getContentTypes())); - assertEquals(0, handlerInfo2.getAcceptTypes().length); - assertEquals("org.test.PersonHandler1", handlerInfo2.getHandlerClass()); - assertEquals("public Mono createPerson(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo2.getHandlerMethod()); - - WebfluxHandlerInformation handlerInfo3 = getWebfluxHandler(addons, "/person", "GET").get(0); - assertEquals("/person", handlerInfo3.getPath()); - assertEquals("[GET]", Arrays.toString(handlerInfo3.getHttpMethods())); - assertEquals(0, handlerInfo3.getContentTypes().length); - assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerInfo3.getAcceptTypes())); - assertEquals("org.test.PersonHandler1", handlerInfo3.getHandlerClass()); - assertEquals("public Mono listPeople(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo3.getHandlerMethod()); + + Bean[] routeBeans = springIndex.getBeansWithName(project.getElementName(), "routingFunction1"); + assertEquals(1, routeBeans.length); + assertEquals("routingFunction1", routeBeans[0].getName()); + + SpringIndexElement[] children = routeBeans[0].getChildren(); + assertEquals(6, children.length); + + WebfluxHandlerMethodIndexElement handlerElement1 = getWebfluxIndexElements(children, "/person/{id}", "GET").get(0); + assertEquals("/person/{id}", handlerElement1.getPath()); + assertEquals("[GET]", Arrays.toString(handlerElement1.getHttpMethods())); + assertEquals(0, handlerElement1.getContentTypes().length); + assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerElement1.getAcceptTypes())); + assertEquals("org.test.PersonHandler1", handlerElement1.getHandlerClass()); + assertEquals("public Mono getPerson(org.springframework.web.reactive.function.server.ServerRequest)", handlerElement1.getHandlerMethod()); + + WebfluxHandlerMethodIndexElement handlerElement2 = getWebfluxIndexElements(children, "/person/", "POST").get(0); + assertEquals("/person/", handlerElement2.getPath()); + assertEquals("[POST]", Arrays.toString(handlerElement2.getHttpMethods())); + assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerElement2.getContentTypes())); + assertEquals(0, handlerElement2.getAcceptTypes().length); + assertEquals("org.test.PersonHandler1", handlerElement2.getHandlerClass()); + assertEquals("public Mono createPerson(org.springframework.web.reactive.function.server.ServerRequest)", handlerElement2.getHandlerMethod()); + + WebfluxHandlerMethodIndexElement handlerElement3 = getWebfluxIndexElements(children, "/person", "GET").get(0); + assertEquals("/person", handlerElement3.getPath()); + assertEquals("[GET]", Arrays.toString(handlerElement3.getHttpMethods())); + assertEquals(0, handlerElement3.getContentTypes().length); + assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerElement3.getAcceptTypes())); + assertEquals("org.test.PersonHandler1", handlerElement3.getHandlerClass()); + assertEquals("public Mono listPeople(org.springframework.web.reactive.function.server.ServerRequest)", handlerElement3.getHandlerMethod()); } @Test @@ -173,33 +183,37 @@ void testNestedRoutesMappingSymbols2() throws Exception { assertTrue(containsSymbol(symbols, "@/person/{id} -- GET - Accept: application/json", docUri, 29, 6, 29, 45)); assertTrue(containsSymbol(symbols, "@/ -- POST - Accept: application/json - Content-Type: application/json,application/pdf", docUri, 31, 6, 31, 117)); assertTrue(containsSymbol(symbols, "@/person -- GET,HEAD - Accept: text/plain,application/json", docUri, 30, 7, 30, 113)); - - List addons = indexer.getAdditonalInformation(docUri); - assertEquals(6, addons.size()); - - WebfluxHandlerInformation handlerInfo1 = getWebfluxHandler(addons, "/person/{id}", "GET").get(0); - assertEquals("/person/{id}", handlerInfo1.getPath()); - assertEquals("[GET]", Arrays.toString(handlerInfo1.getHttpMethods())); - assertEquals(0, handlerInfo1.getContentTypes().length); - assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerInfo1.getAcceptTypes())); - assertEquals("org.test.PersonHandler2", handlerInfo1.getHandlerClass()); - assertEquals("public Mono getPerson(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo1.getHandlerMethod()); - - WebfluxHandlerInformation handlerInfo2 = getWebfluxHandler(addons, "/", "POST").get(0); - assertEquals("/", handlerInfo2.getPath()); - assertEquals("[POST]", Arrays.toString(handlerInfo2.getHttpMethods())); - assertEquals("[APPLICATION_JSON, APPLICATION_PDF]", Arrays.toString(handlerInfo2.getContentTypes())); - assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerInfo2.getAcceptTypes())); - assertEquals("org.test.PersonHandler2", handlerInfo2.getHandlerClass()); - assertEquals("public Mono createPerson(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo2.getHandlerMethod()); - - WebfluxHandlerInformation handlerInfo3 = getWebfluxHandler(addons, "/person", "HEAD").get(0); - assertEquals("/person", handlerInfo3.getPath()); - assertEquals("[GET, HEAD]", Arrays.toString(handlerInfo3.getHttpMethods())); - assertEquals(0, handlerInfo3.getContentTypes().length); - assertEquals("[TEXT_PLAIN, APPLICATION_JSON]", Arrays.toString(handlerInfo3.getAcceptTypes())); - assertEquals("org.test.PersonHandler2", handlerInfo3.getHandlerClass()); - assertEquals("public Mono listPeople(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo3.getHandlerMethod()); + + Bean[] routeBeans = springIndex.getBeansWithName(project.getElementName(), "routingFunction2"); + assertEquals(1, routeBeans.length); + assertEquals("routingFunction2", routeBeans[0].getName()); + + SpringIndexElement[] children = routeBeans[0].getChildren(); + assertEquals(6, children.length); + + WebfluxHandlerMethodIndexElement handlerelement1 = getWebfluxIndexElements(children, "/person/{id}", "GET").get(0); + assertEquals("/person/{id}", handlerelement1.getPath()); + assertEquals("[GET]", Arrays.toString(handlerelement1.getHttpMethods())); + assertEquals(0, handlerelement1.getContentTypes().length); + assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerelement1.getAcceptTypes())); + assertEquals("org.test.PersonHandler2", handlerelement1.getHandlerClass()); + assertEquals("public Mono getPerson(org.springframework.web.reactive.function.server.ServerRequest)", handlerelement1.getHandlerMethod()); + + WebfluxHandlerMethodIndexElement handlerElement2 = getWebfluxIndexElements(children, "/", "POST").get(0); + assertEquals("/", handlerElement2.getPath()); + assertEquals("[POST]", Arrays.toString(handlerElement2.getHttpMethods())); + assertEquals("[APPLICATION_JSON, APPLICATION_PDF]", Arrays.toString(handlerElement2.getContentTypes())); + assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerElement2.getAcceptTypes())); + assertEquals("org.test.PersonHandler2", handlerElement2.getHandlerClass()); + assertEquals("public Mono createPerson(org.springframework.web.reactive.function.server.ServerRequest)", handlerElement2.getHandlerMethod()); + + WebfluxHandlerMethodIndexElement handlerElement3 = getWebfluxIndexElements(children, "/person", "HEAD").get(0); + assertEquals("/person", handlerElement3.getPath()); + assertEquals("[GET, HEAD]", Arrays.toString(handlerElement3.getHttpMethods())); + assertEquals(0, handlerElement3.getContentTypes().length); + assertEquals("[TEXT_PLAIN, APPLICATION_JSON]", Arrays.toString(handlerElement3.getAcceptTypes())); + assertEquals("org.test.PersonHandler2", handlerElement3.getHandlerClass()); + assertEquals("public Mono listPeople(org.springframework.web.reactive.function.server.ServerRequest)", handlerElement3.getHandlerMethod()); } @Test @@ -214,57 +228,61 @@ void testNestedRoutesMappingSymbols3() throws Exception { assertTrue(containsSymbol(symbols, "@/person/sub1/andNestPath/andNestPathGET -- GET", docUri, 33, 5, 33, 54)); assertTrue(containsSymbol(symbols, "@/person/ -- POST - Content-Type: application/json", docUri, 34, 5, 34, 82)); assertTrue(containsSymbol(symbols, "@/nestedDelete -- DELETE", docUri, 35, 42, 35, 93)); - - List addons = indexer.getAdditonalInformation(docUri); - assertEquals(12, addons.size()); - - WebfluxHandlerInformation handlerInfo1 = getWebfluxHandler(addons, "/person/sub1/sub2/{id}", "GET").get(0); - assertEquals("/person/sub1/sub2/{id}", handlerInfo1.getPath()); - assertEquals("[GET]", Arrays.toString(handlerInfo1.getHttpMethods())); - assertEquals(0, handlerInfo1.getContentTypes().length); - assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerInfo1.getAcceptTypes())); - assertEquals("org.test.PersonHandler3", handlerInfo1.getHandlerClass()); - assertEquals("public Mono getPerson(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo1.getHandlerMethod()); - - WebfluxHandlerInformation handlerInfo2 = getWebfluxHandler(addons, "/person/sub1/sub2", "GET").get(0); - assertEquals("/person/sub1/sub2", handlerInfo2.getPath()); - assertEquals("[GET]", Arrays.toString(handlerInfo2.getHttpMethods())); - assertEquals(0, handlerInfo2.getContentTypes().length); - assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerInfo2.getAcceptTypes())); - assertEquals("org.test.PersonHandler3", handlerInfo1.getHandlerClass()); - assertEquals("public Mono listPeople(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo2.getHandlerMethod()); - - WebfluxHandlerInformation handlerInfo3 = getWebfluxHandler(addons, "/person/sub1/sub2/nestedGet", "GET").get(0); - assertEquals("/person/sub1/sub2/nestedGet", handlerInfo3.getPath()); - assertEquals("[GET]", Arrays.toString(handlerInfo3.getHttpMethods())); - assertEquals(0, handlerInfo3.getContentTypes().length); - assertEquals(0, handlerInfo3.getAcceptTypes().length); - assertEquals("org.test.PersonHandler3", handlerInfo1.getHandlerClass()); - assertEquals("public Mono getPerson(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo3.getHandlerMethod()); - - WebfluxHandlerInformation handlerInfo4 = getWebfluxHandler(addons, "/person/sub1/andNestPath/andNestPathGET", "GET").get(0); - assertEquals("/person/sub1/andNestPath/andNestPathGET", handlerInfo4.getPath()); - assertEquals("[GET]", Arrays.toString(handlerInfo4.getHttpMethods())); - assertEquals(0, handlerInfo4.getContentTypes().length); - assertEquals(0, handlerInfo4.getAcceptTypes().length); - assertEquals("org.test.PersonHandler3", handlerInfo4.getHandlerClass()); - assertEquals("public Mono getPerson(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo4.getHandlerMethod()); - - WebfluxHandlerInformation handlerInfo5 = getWebfluxHandler(addons, "/person/", "POST").get(0); - assertEquals("/person/", handlerInfo5.getPath()); - assertEquals("[POST]", Arrays.toString(handlerInfo5.getHttpMethods())); - assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerInfo5.getContentTypes())); - assertEquals(0, handlerInfo5.getAcceptTypes().length); - assertEquals("org.test.PersonHandler3", handlerInfo5.getHandlerClass()); - assertEquals("public Mono createPerson(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo5.getHandlerMethod()); - - WebfluxHandlerInformation handlerInfo6 = getWebfluxHandler(addons, "/nestedDelete", "DELETE").get(0); - assertEquals("/nestedDelete", handlerInfo6.getPath()); - assertEquals("[DELETE]", Arrays.toString(handlerInfo6.getHttpMethods())); - assertEquals(0, handlerInfo6.getContentTypes().length); - assertEquals(0, handlerInfo6.getAcceptTypes().length); - assertEquals("org.test.PersonHandler3", handlerInfo6.getHandlerClass()); - assertEquals("public Mono deletePerson(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo6.getHandlerMethod()); + + Bean[] routeBeans = springIndex.getBeansWithName(project.getElementName(), "routingFunction"); + assertEquals(1, routeBeans.length); + assertEquals("routingFunction", routeBeans[0].getName()); + + SpringIndexElement[] children = routeBeans[0].getChildren(); + assertEquals(12, children.length); + + WebfluxHandlerMethodIndexElement handlerElement1 = getWebfluxIndexElements(children, "/person/sub1/sub2/{id}", "GET").get(0); + assertEquals("/person/sub1/sub2/{id}", handlerElement1.getPath()); + assertEquals("[GET]", Arrays.toString(handlerElement1.getHttpMethods())); + assertEquals(0, handlerElement1.getContentTypes().length); + assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerElement1.getAcceptTypes())); + assertEquals("org.test.PersonHandler3", handlerElement1.getHandlerClass()); + assertEquals("public Mono getPerson(org.springframework.web.reactive.function.server.ServerRequest)", handlerElement1.getHandlerMethod()); + + WebfluxHandlerMethodIndexElement handlerElement2 = getWebfluxIndexElements(children, "/person/sub1/sub2", "GET").get(0); + assertEquals("/person/sub1/sub2", handlerElement2.getPath()); + assertEquals("[GET]", Arrays.toString(handlerElement2.getHttpMethods())); + assertEquals(0, handlerElement2.getContentTypes().length); + assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerElement2.getAcceptTypes())); + assertEquals("org.test.PersonHandler3", handlerElement1.getHandlerClass()); + assertEquals("public Mono listPeople(org.springframework.web.reactive.function.server.ServerRequest)", handlerElement2.getHandlerMethod()); + + WebfluxHandlerMethodIndexElement handlerElement3 = getWebfluxIndexElements(children, "/person/sub1/sub2/nestedGet", "GET").get(0); + assertEquals("/person/sub1/sub2/nestedGet", handlerElement3.getPath()); + assertEquals("[GET]", Arrays.toString(handlerElement3.getHttpMethods())); + assertEquals(0, handlerElement3.getContentTypes().length); + assertEquals(0, handlerElement3.getAcceptTypes().length); + assertEquals("org.test.PersonHandler3", handlerElement1.getHandlerClass()); + assertEquals("public Mono getPerson(org.springframework.web.reactive.function.server.ServerRequest)", handlerElement3.getHandlerMethod()); + + WebfluxHandlerMethodIndexElement handlerElement4 = getWebfluxIndexElements(children, "/person/sub1/andNestPath/andNestPathGET", "GET").get(0); + assertEquals("/person/sub1/andNestPath/andNestPathGET", handlerElement4.getPath()); + assertEquals("[GET]", Arrays.toString(handlerElement4.getHttpMethods())); + assertEquals(0, handlerElement4.getContentTypes().length); + assertEquals(0, handlerElement4.getAcceptTypes().length); + assertEquals("org.test.PersonHandler3", handlerElement4.getHandlerClass()); + assertEquals("public Mono getPerson(org.springframework.web.reactive.function.server.ServerRequest)", handlerElement4.getHandlerMethod()); + + WebfluxHandlerMethodIndexElement handlerElement5 = getWebfluxIndexElements(children, "/person/", "POST").get(0); + assertEquals("/person/", handlerElement5.getPath()); + assertEquals("[POST]", Arrays.toString(handlerElement5.getHttpMethods())); + assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerElement5.getContentTypes())); + assertEquals(0, handlerElement5.getAcceptTypes().length); + assertEquals("org.test.PersonHandler3", handlerElement5.getHandlerClass()); + assertEquals("public Mono createPerson(org.springframework.web.reactive.function.server.ServerRequest)", handlerElement5.getHandlerMethod()); + + WebfluxHandlerMethodIndexElement handlerElement6 = getWebfluxIndexElements(children, "/nestedDelete", "DELETE").get(0); + assertEquals("/nestedDelete", handlerElement6.getPath()); + assertEquals("[DELETE]", Arrays.toString(handlerElement6.getHttpMethods())); + assertEquals(0, handlerElement6.getContentTypes().length); + assertEquals(0, handlerElement6.getAcceptTypes().length); + assertEquals("org.test.PersonHandler3", handlerElement6.getHandlerClass()); + assertEquals("public Mono deletePerson(org.springframework.web.reactive.function.server.ServerRequest)", handlerElement6.getHandlerMethod()); } private boolean containsSymbol(List symbols, String name, String uri, int startLine, int startCHaracter, int endLine, int endCharacter) { @@ -284,10 +302,10 @@ private boolean containsSymbol(List symbols, String n return false; } - private List getWebfluxHandler(List addons, String path, String httpMethod) { - return addons.stream() - .filter((obj) -> obj instanceof WebfluxHandlerInformation) - .map((obj -> (WebfluxHandlerInformation) obj)) + private List getWebfluxIndexElements(SpringIndexElement[] indexElements, String path, String httpMethod) { + return Arrays.stream(indexElements) + .filter((obj) -> obj instanceof WebfluxHandlerMethodIndexElement) + .map((obj -> (WebfluxHandlerMethodIndexElement) obj)) .filter((addon) -> addon.getPath().equals(path) && Arrays.asList(addon.getHttpMethods()).contains(httpMethod)) .collect(Collectors.toList()); } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebfluxElementsInformationTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebfluxRouteElementRangesIndexElementTest.java similarity index 86% rename from headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebfluxElementsInformationTest.java rename to headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebfluxRouteElementRangesIndexElementTest.java index 5e7c1a491e..016350c89b 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebfluxElementsInformationTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebfluxRouteElementRangesIndexElementTest.java @@ -1,12 +1,12 @@ /******************************************************************************* - * Copyright (c) 2018 Pivotal, Inc. + * Copyright (c) 2024 Broadcom * 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 * https://www.eclipse.org/legal/epl-v10.html * * Contributors: - * Pivotal, Inc. - initial API and implementation + * Broadcom - initial API and implementation *******************************************************************************/ package org.springframework.ide.vscode.boot.java.requestmapping.test; @@ -16,20 +16,17 @@ import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; import org.junit.jupiter.api.Test; -import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxElementsInformation; - -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.assertFalse; +import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxRouteElementRangesIndexElement; /** * @author Martin Lippert */ -public class WebfluxElementsInformationTest { +public class WebfluxRouteElementRangesIndexElementTest { @Test void testContainsSingleCharacterRange() { Range range = new Range(new Position(3, 10), new Position(3, 10)); - WebfluxElementsInformation information = new WebfluxElementsInformation(new Range[]{range}); + WebfluxRouteElementRangesIndexElement information = new WebfluxRouteElementRangesIndexElement(new Range[]{range}); assertFalse(information.contains(new Position(3, 9))); assertTrue(information.contains(new Position(3, 10))); @@ -39,7 +36,7 @@ void testContainsSingleCharacterRange() { @Test void testContainsSingleLineRange() { Range range = new Range(new Position(3, 10), new Position(3, 20)); - WebfluxElementsInformation information = new WebfluxElementsInformation(new Range[]{range}); + WebfluxRouteElementRangesIndexElement information = new WebfluxRouteElementRangesIndexElement(new Range[]{range}); assertFalse(information.contains(new Position(3, 5))); assertTrue(information.contains(new Position(3, 11))); @@ -53,7 +50,7 @@ void testContainsSingleLineRange() { @Test void testContainsMultipleLineRange() { Range range = new Range(new Position(2, 10), new Position(4, 5)); - WebfluxElementsInformation information = new WebfluxElementsInformation(new Range[]{range}); + WebfluxRouteElementRangesIndexElement information = new WebfluxRouteElementRangesIndexElement(new Range[]{range}); assertFalse(information.contains(new Position(1, 1))); assertFalse(information.contains(new Position(1, 11))); @@ -82,7 +79,7 @@ void testContainsMultipleRanges() { Range range3 = new Range(new Position(10, 10), new Position(20, 20)); Range range4 = new Range(new Position(4, 40), new Position(6, 3)); - WebfluxElementsInformation information = new WebfluxElementsInformation(new Range[]{range1, range2, range3, range4}); + WebfluxRouteElementRangesIndexElement information = new WebfluxRouteElementRangesIndexElement(new Range[]{range1, range2, range3, range4}); assertFalse(information.contains(new Position(2, 9))); assertTrue(information.contains(new Position(2, 10)));