From 85afe5ceefead544ce984b0b5aa0ec1f6169c83a Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Fri, 24 Jan 2025 16:41:48 +0100 Subject: [PATCH] GH-1425: groundwork to allow nested beans in internal index structure --- .../spring/AbstractSpringIndexElement.java | 32 ++- .../vscode/commons/protocol/spring/Bean.java | 17 +- .../protocol/spring/DocumentElement.java | 25 +++ .../protocol/spring/ProjectElement.java | 39 ++++ .../protocol/spring/SpringIndexElement.java | 4 +- .../boot/index/SpringMetamodelIndex.java | 204 ++++++++++++------ .../cache/IndexCacheOnDiscDeltaBased.java | 10 +- .../boot/java/beans/BeansSymbolProvider.java | 7 +- .../WebfluxHandlerCodeLensProvider.java | 2 +- .../WebfluxHandlerMethodIndexElement.java | 2 - ...WebfluxRouteElementRangesIndexElement.java | 1 - .../WebfluxRouteHighlightProdivder.java | 2 +- .../index/test/SpringMetamodelIndexTest.java | 105 +++++---- .../test/SpringMetamodelIndexingTest.java | 2 +- .../WebFluxMappingSymbolProviderTest.java | 25 ++- 15 files changed, 328 insertions(+), 149 deletions(-) create mode 100644 headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/DocumentElement.java create mode 100644 headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/ProjectElement.java 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 index 2ef72947aa..7e1f90bf66 100644 --- 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 @@ -10,19 +10,39 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.protocol.spring; +import java.util.ArrayList; +import java.util.List; + public abstract class AbstractSpringIndexElement implements SpringIndexElement { - public static final SpringIndexElement[] NO_CHILDREN = new SpringIndexElement[0]; - - private final SpringIndexElement[] children; + public static final List NO_CHILDREN = List.of(); + private List children; - public AbstractSpringIndexElement(SpringIndexElement[] children) { - this.children = children != null ? children : NO_CHILDREN; + public AbstractSpringIndexElement() { + this.children = NO_CHILDREN; } @Override - public SpringIndexElement[] getChildren() { + public List getChildren() { return children; } + + public void addChild(SpringIndexElement child) { + if (children == NO_CHILDREN) { + children = new ArrayList<>(); + } + + this.children.add(child); + } + + public void removeChild(SpringIndexElement doc) { + boolean removed = this.children.remove(doc); + + if (removed && this.children.size() == 0) { + this.children = NO_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 0f9c022ba3..790c06ca3c 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 @@ -33,11 +33,8 @@ public Bean( InjectionPoint[] injectionPoints, Set supertypes, AnnotationMetadata[] annotations, - boolean isConfiguration, - SpringIndexElement[] children) { + boolean isConfiguration) { - super(children); - this.name = name; this.type = type; this.location = location; @@ -68,18 +65,6 @@ 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/DocumentElement.java b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/DocumentElement.java new file mode 100644 index 0000000000..6382c77e81 --- /dev/null +++ b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/DocumentElement.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * 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 class DocumentElement extends AbstractSpringIndexElement { + + private final String docURI; + + public DocumentElement(String docURI) { + this.docURI = docURI; + } + + public String getDocURI() { + return docURI; + } + +} diff --git a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/ProjectElement.java b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/ProjectElement.java new file mode 100644 index 0000000000..0f4a26879c --- /dev/null +++ b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/ProjectElement.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2025 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; + +import java.util.Iterator; +import java.util.List; + +public class ProjectElement extends AbstractSpringIndexElement { + + private String projectName; + + public ProjectElement(String projectName) { + this.projectName = projectName; + } + + public String getProjectName() { + return projectName; + } + + public void removeDocument(String docURI) { + List children = this.getChildren(); + + for (Iterator iterator = children.iterator(); iterator.hasNext();) { + SpringIndexElement springIndexElement = (SpringIndexElement) iterator.next(); + if (springIndexElement instanceof DocumentElement doc && doc.getDocURI().equals(docURI)) { + iterator.remove(); + } + } + } + +} 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 index 7c153de519..ca2cbd7c10 100644 --- 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 @@ -10,8 +10,10 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.protocol.spring; +import java.util.List; + public interface SpringIndexElement { - SpringIndexElement[] getChildren(); + List getChildren(); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringMetamodelIndex.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringMetamodelIndex.java index 6a18b5698b..256f86ae69 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringMetamodelIndex.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringMetamodelIndex.java @@ -10,125 +10,207 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.index; +import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.stream.Collectors; import org.springframework.ide.vscode.commons.protocol.spring.Bean; +import org.springframework.ide.vscode.commons.protocol.spring.DocumentElement; +import org.springframework.ide.vscode.commons.protocol.spring.ProjectElement; +import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; public class SpringMetamodelIndex { - private final ConcurrentMap beansPerProject; + private final ConcurrentMap projectRootElements; public SpringMetamodelIndex() { - beansPerProject = new ConcurrentHashMap<>(); + projectRootElements = new ConcurrentHashMap<>(); } public void updateBeans(String projectName, Bean[] beanDefinitions) { - beansPerProject.put(projectName, beanDefinitions); - } - - public void updateBeans(String projectName, String docURI, Bean[] beanDefinitions) { - Bean[] existingBeans = beansPerProject.putIfAbsent(projectName, beanDefinitions); - - if (existingBeans != null) { - - List beans = new ArrayList<>(); + ProjectElement projectRoot = new ProjectElement(projectName); + + Map documents = new HashMap<>(); + for (Bean bean : beanDefinitions) { + String docURI = bean.getLocation() != null ? bean.getLocation().getUri() : null; - // add old, unrelated beans - for (Bean bean : existingBeans) { - if (!bean.getLocation().getUri().equals(docURI)) { - beans.add(bean); - } + if (docURI != null) { + + DocumentElement document = documents.computeIfAbsent(docURI, uri -> { + DocumentElement newDocument = new DocumentElement(uri); + projectRoot.addChild(newDocument); + return newDocument; + }); + + document.addChild(bean); } + else { + projectRoot.addChild(bean); + } + } + + projectRootElements.put(projectName, projectRoot); + } - // add new beans for doc URI - beans.addAll(Arrays.asList(beanDefinitions)); - - // set new beans set - beansPerProject.put(projectName, (Bean[]) beans.toArray(new Bean[beans.size()])); + public void updateBeans(String projectName, String docURI, Bean[] beanDefinitions) { + ProjectElement project = this.projectRootElements.computeIfAbsent(projectName, name -> new ProjectElement(name)); + project.removeDocument(docURI); + + DocumentElement document = new DocumentElement(docURI); + for (Bean bean : beanDefinitions) { + document.addChild(bean); } + + project.addChild(document); } public void removeBeans(String projectName) { - beansPerProject.remove(projectName); + projectRootElements.remove(projectName); } public void removeBeans(String projectName, String docURI) { - Bean[] oldBeans = beansPerProject.get(projectName); - if (oldBeans != null) { - List newBeans = Arrays.stream(oldBeans) - .filter(bean -> !bean.getLocation().getUri().equals(docURI)) - .collect(Collectors.toList()); - - beansPerProject.put(projectName, (Bean[]) newBeans.toArray(new Bean[newBeans.size()])); + ProjectElement project = projectRootElements.get(projectName); + if (project != null) { + project.removeDocument(docURI); } } public Bean[] getBeans() { List result = new ArrayList<>(); - for (Bean[] beans : beansPerProject.values()) { - for (Bean bean : beans) { + ArrayDeque elementsToVisit = new ArrayDeque<>(); + elementsToVisit.addAll(this.projectRootElements.values()); + + while (!elementsToVisit.isEmpty()) { + SpringIndexElement element = elementsToVisit.pop(); + + if (element instanceof Bean bean) { result.add(bean); } + + elementsToVisit.addAll(element.getChildren()); } return (Bean[]) result.toArray(new Bean[result.size()]); } public Bean[] getBeansOfProject(String projectName) { - return beansPerProject.get(projectName); - } - - public Bean[] getBeansOfDocument(String docURI) { List result = new ArrayList<>(); - for (Bean[] beans : beansPerProject.values()) { - for (Bean bean : beans) { - if (bean.getLocation().getUri().equals(docURI)) { + ProjectElement project = this.projectRootElements.get(projectName); + if (project != null) { + ArrayDeque elementsToVisit = new ArrayDeque<>(); + elementsToVisit.push(project); + + while (!elementsToVisit.isEmpty()) { + SpringIndexElement element = elementsToVisit.pop(); + + if (element instanceof Bean bean) { result.add(bean); } + + elementsToVisit.addAll(element.getChildren()); } } return (Bean[]) result.toArray(new Bean[result.size()]); } - public Bean[] getBeansWithName(String project, String name) { - Bean[] allBeans = this.beansPerProject.get(project); + public Bean[] getBeansOfDocument(String docURI) { + List result = new ArrayList<>(); - if (allBeans != null) { - return Arrays.stream(allBeans).filter(bean -> bean.getName().equals(name)).toArray(Bean[]::new); - } - else { - return null; + ArrayDeque elementsToVisit = new ArrayDeque<>(); + elementsToVisit.addAll(this.projectRootElements.values()); + + while (!elementsToVisit.isEmpty()) { + SpringIndexElement element = elementsToVisit.pop(); + + if (element instanceof Bean bean) { + result.add(bean); + } + + if (element instanceof DocumentElement doc) { + if (doc.getDocURI().equals(docURI)) { + elementsToVisit.addAll(doc.getChildren()); + } + // else do not look into other document structures + } + else { + elementsToVisit.addAll(element.getChildren()); + } } + + return (Bean[]) result.toArray(new Bean[result.size()]); } + + public Bean[] getBeansWithName(String projectName, String name) { + List result = new ArrayList<>(); - public Bean[] getBeansWithType(String project, String type) { - Bean[] allBeans = this.beansPerProject.get(project); - - if (allBeans != null) { - return Arrays.stream(allBeans).filter(bean -> bean.getType().equals(type)).toArray(Bean[]::new); + ProjectElement project = this.projectRootElements.get(projectName); + if (project != null) { + ArrayDeque elementsToVisit = new ArrayDeque<>(); + elementsToVisit.push(project); + + while (!elementsToVisit.isEmpty()) { + SpringIndexElement element = elementsToVisit.pop(); + + if (element instanceof Bean bean && bean.getName().equals(name)) { + result.add(bean); + } + + elementsToVisit.addAll(element.getChildren()); + } } - else { - return null; + + return (Bean[]) result.toArray(new Bean[result.size()]); + } + + public Bean[] getBeansWithType(String projectName, String type) { + List result = new ArrayList<>(); + + ProjectElement project = this.projectRootElements.get(projectName); + if (project != null) { + ArrayDeque elementsToVisit = new ArrayDeque<>(); + elementsToVisit.push(project); + + while (!elementsToVisit.isEmpty()) { + SpringIndexElement element = elementsToVisit.pop(); + + if (element instanceof Bean bean && bean.getType().equals(type)) { + result.add(bean); + } + + elementsToVisit.addAll(element.getChildren()); + } } + + return (Bean[]) result.toArray(new Bean[result.size()]); } public Bean[] getMatchingBeans(String projectName, String matchType) { - Bean[] allBeans = this.beansPerProject.get(projectName); - - if (allBeans != null) { - return Arrays.stream(allBeans).filter(bean -> bean.isTypeCompatibleWith(matchType)).collect(Collectors.toList()).toArray(new Bean[0]); - } - else { - return null; + List result = new ArrayList<>(); + + ProjectElement project = this.projectRootElements.get(projectName); + if (project != null) { + ArrayDeque elementsToVisit = new ArrayDeque<>(); + elementsToVisit.push(project); + + while (!elementsToVisit.isEmpty()) { + SpringIndexElement element = elementsToVisit.pop(); + + if (element instanceof Bean bean && bean.isTypeCompatibleWith(matchType)) { + result.add(bean); + } + + elementsToVisit.addAll(element.getChildren()); + } } + + return (Bean[]) result.toArray(new Bean[result.size()]); } } 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 d83032101a..2fb47574d5 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 @@ -660,9 +660,15 @@ public Bean deserialize(JsonElement json, Type type, JsonDeserializationContext boolean isConfiguration = context.deserialize(isConfigurationObject, boolean.class); JsonElement childrenObject = parsedObject.get("children"); - SpringIndexElement[] children = context.deserialize(childrenObject, SpringIndexElement[].class); + Type childrenListType = TypeToken.getParameterized(List.class, SpringIndexElement.class).getType(); + List children = context.deserialize(childrenObject, childrenListType); - return new Bean(beanName, beanType, location, injectionPoints, supertypes, annotations, isConfiguration, children); + Bean bean = new Bean(beanName, beanType, location, injectionPoints, supertypes, annotations, isConfiguration); + for (SpringIndexElement springIndexElement : children) { + bean.addChild(springIndexElement); + } + + return bean; } } 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 e43b596444..9ffdeb21d7 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 @@ -115,7 +115,12 @@ public void addSymbols(Annotation node, ITypeBinding typeBinding, Collection annotationsOnMethod = ASTUtils.getAnnotations(method); AnnotationMetadata[] annotations = ASTUtils.getAnnotationsMetadata(annotationsOnMethod, doc); - Bean beanDefinition = new Bean(nameAndRegion.getT1(), beanType.getQualifiedName(), location, injectionPoints, supertypes, annotations, false, childElements.toArray(SpringIndexElement[]::new)); + Bean beanDefinition = new Bean(nameAndRegion.getT1(), beanType.getQualifiedName(), location, injectionPoints, supertypes, annotations, false); + if (childElements.size() > 0) { + for (SpringIndexElement springIndexElement : childElements) { + beanDefinition.addChild(springIndexElement); + } + } 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 a7a6575dc4..c04213907b 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 @@ -95,7 +95,7 @@ private List findMatchingHandlerMethogs(String Bean[] beans = springIndex.getBeans(); return Arrays.stream(beans) - .flatMap(bean -> Arrays.stream(bean.getChildren())) + .flatMap(bean -> bean.getChildren().stream()) .filter(element -> element instanceof WebfluxHandlerMethodIndexElement) .map(element -> (WebfluxHandlerMethodIndexElement) element) .filter(webfluxElement -> webfluxElement.getHandlerClass() != null && webfluxElement.getHandlerClass().equals(handlerClass) diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerMethodIndexElement.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerMethodIndexElement.java index 37d45693f2..5fac5da19b 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerMethodIndexElement.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerMethodIndexElement.java @@ -23,8 +23,6 @@ public class WebfluxHandlerMethodIndexElement extends AbstractSpringIndexElement private final 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/WebfluxRouteElementRangesIndexElement.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouteElementRangesIndexElement.java index d6e8d4d74e..8eeb3c21f8 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouteElementRangesIndexElement.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouteElementRangesIndexElement.java @@ -21,7 +21,6 @@ public class WebfluxRouteElementRangesIndexElement extends AbstractSpringIndexEl private Range[] ranges; public WebfluxRouteElementRangesIndexElement(Range... ranges) { - super(AbstractSpringIndexElement.NO_CHILDREN); this.ranges = ranges != null ? ranges : NO_RANGES; } 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 4a2e3f7ae6..d34b5c4242 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 @@ -44,7 +44,7 @@ public void provideHighlights(CancelChecker cancelToken, TextDocument document, Bean[] beans = springIndex.getBeans(); Arrays.stream(beans) - .flatMap(bean -> Arrays.stream(bean.getChildren())) + .flatMap(bean -> bean.getChildren().stream()) .filter(element -> element instanceof WebfluxRouteElementRangesIndexElement) .map(element -> (WebfluxRouteElementRangesIndexElement) element) .filter(rangesElement -> rangesElement.contains(position)) 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 43db61322b..5c7e79aaf6 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 @@ -54,8 +54,8 @@ public class SpringMetamodelIndexTest { @Test void testEmptyIndex() { SpringMetamodelIndex index = new SpringMetamodelIndex(); - assertNull(index.getBeansOfProject("someProject")); - assertNull(index.getBeansWithName("someProject", "someBeanName")); + assertEquals(0, index.getBeansOfProject("someProject").length); + assertEquals(0, index.getBeansWithName("someProject", "someBeanName").length); } @Test @@ -126,7 +126,7 @@ void testSimpleProjectWithBeansPerName() { assertTrue(beansList.contains(bean2)); assertFalse(beansList.contains(bean3)); - assertNull(index.getBeansWithName("nonExistingProject", "beanName1")); + assertEquals(0, index.getBeansWithName("nonExistingProject", "beanName1").length); } @Test @@ -203,7 +203,7 @@ void testRemoveAllBeansForSpecificProject() { assertFalse(beansList.contains(bean2)); assertTrue(beansList.contains(bean3)); - assertNull(index.getBeansOfProject("someProject1")); + assertEquals(0, index.getBeansOfProject("someProject1").length); } @Test @@ -364,7 +364,7 @@ void testFindMatchingBeansWithOneProject() { assertEquals(0, matchingBeans.length); matchingBeans = index.getMatchingBeans("otherProject", "supertype1"); - assertNull(matchingBeans); + assertEquals(0, matchingBeans.length); } @Test @@ -400,41 +400,46 @@ void testFindMatchingBeansWithMultipleProjects() { @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); + Bean bean1 = new Bean("beanName1", "beanType1", locationForDoc1, emptyInjectionPoints, Set.of("supertype1", "supertype2"), emptyAnnotations, false); + + SubType1 child1 = new SubType1(); + bean1.addChild(child1); - SpringIndexElement[] children2 = bean1.getChildren(); - assertEquals(1, children2.length); - assertSame(child1, children2[0]); + List children2 = bean1.getChildren(); + assertEquals(1, children2.size()); + assertSame(child1, children2.get(0)); } @Test void testSpringIndexStructurePolymorphicSerialization() { Gson gson = IndexCacheOnDiscDeltaBased.createGson(); - SubType2 subNode = new SubType2(null); + SubType2 subNode = new SubType2(); + + SubType1 node1 = new SubType1(); + node1.addChild(subNode); - SubType1 node1 = new SubType1(new SpringIndexElement[] {subNode}); - SubType2 node2 = new SubType2(null); + SubType2 node2 = new SubType2(); - Root root = new Root(new SpringIndexElement[] {node1, node2}); + Root root = new Root(); + root.addChild(node1); + root.addChild(node2); String json = gson.toJson(root); Root deserializedRoot = gson.fromJson(json, Root.class); - SpringIndexElement[] children = deserializedRoot.getChildren(); - assertEquals(2, children.length); + List children = deserializedRoot.getChildren(); + assertEquals(2, children.size()); - 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(); + SubType1 deserializedNode1 = (SubType1) children.stream().filter(node -> node instanceof SubType1).findAny().get(); + SubType2 deserializedNode2 = (SubType2) children.stream().filter(node -> node instanceof SubType2).findAny().get(); assertNotNull(deserializedNode1); assertNotNull(deserializedNode2); - SpringIndexElement[] deserializedChild2 = deserializedNode1.getChildren(); - assertEquals(1, deserializedChild2.length); - assertTrue(deserializedChild2[0] instanceof SubType2); + List deserializedChild2 = deserializedNode1.getChildren(); + assertEquals(1, deserializedChild2.size()); + assertTrue(deserializedChild2.get(0) instanceof SubType2); } @Test @@ -442,45 +447,59 @@ 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}); + SubType2 childOfChild = new SubType2(); + SubType1 child1 = new SubType1(); + child1.addChild(childOfChild); + + SubType2 child2 = new SubType2(); + Bean bean1 = new Bean("beanName1", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes, emptyAnnotations, true); + bean1.addChild(child1); + bean1.addChild(child2); String serialized = gson.toJson(bean1); Bean deserializedBean = gson.fromJson(serialized, Bean.class); - SpringIndexElement[] children = deserializedBean.getChildren(); - assertEquals(2, children.length); + List children = deserializedBean.getChildren(); + assertEquals(2, children.size()); - SpringIndexElement deserializedChild1 = java.util.Arrays.stream(children).filter(element -> element instanceof SubType1).findAny().get(); + SpringIndexElement deserializedChild1 = children.stream().filter(element -> element instanceof SubType1).findAny().get(); assertNotNull(deserializedChild1); - SpringIndexElement[] childrenOfChild = deserializedChild1.getChildren(); - assertEquals(1, childrenOfChild.length); - assertTrue(childrenOfChild[0] instanceof SubType2); + List childrenOfChild = deserializedChild1.getChildren(); + assertEquals(1, childrenOfChild.size()); + assertTrue(childrenOfChild.get(0) instanceof SubType2); - SpringIndexElement deserializedChild2 = java.util.Arrays.stream(children).filter(element -> element instanceof SubType2).findAny().get(); + SpringIndexElement deserializedChild2 = children.stream().filter(element -> element instanceof SubType2).findAny().get(); assertNotNull(deserializedChild2); - assertEquals(0, deserializedChild2.getChildren().length); + assertEquals(0, deserializedChild2.getChildren().size()); + } + + @Test + void testAddChildAfterDeserialize() { + + Gson gson = IndexCacheOnDiscDeltaBased.createGson(); + + SubType1 child1 = new SubType1(); + Bean bean1 = new Bean("beanName1", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes, emptyAnnotations, true); + bean1.addChild(child1); + + String serialized = gson.toJson(bean1); + Bean deserializedBean = gson.fromJson(serialized, Bean.class); + + SubType2 newChild = new SubType2(); + deserializedBean.addChild(newChild); + + List childrenAfterNewChildAdded = deserializedBean.getChildren(); + assertEquals(2, childrenAfterNewChildAdded.size()); } 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/index/test/SpringMetamodelIndexingTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexingTest.java index 3c196d2351..002e17d4bd 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexingTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexingTest.java @@ -83,7 +83,7 @@ void testDeleteProject() throws Exception { deleteProject.get(5, TimeUnit.SECONDS); Bean[] noBeansAnymore = springIndex.getBeansOfProject("test-spring-indexing"); - assertNull(noBeansAnymore); + assertEquals(0, noBeansAnymore.length); assertEquals(2, harness.getIndexUpdatedCount()); // 1x project created, 1x project deleted } 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 30afb4c440..c9dd6359fe 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 @@ -39,7 +39,6 @@ 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.InjectionPoint; 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; @@ -102,8 +101,8 @@ void testRoutesMappingSymbols() throws Exception { assertEquals(1, routeBeans.length); assertEquals("route", routeBeans[0].getName()); - SpringIndexElement[] children = routeBeans[0].getChildren(); - assertEquals(8, children.length); + List children = routeBeans[0].getChildren(); + assertEquals(8, children.size()); WebfluxHandlerMethodIndexElement handlerElement1 = getWebfluxIndexElements(children, "/hello", "GET").get(0); assertEquals("/hello", handlerElement1.getPath()); @@ -151,8 +150,8 @@ void testNestedRoutesMappingSymbols1() throws Exception { assertEquals(1, routeBeans.length); assertEquals("routingFunction1", routeBeans[0].getName()); - SpringIndexElement[] children = routeBeans[0].getChildren(); - assertEquals(6, children.length); + List children = routeBeans[0].getChildren(); + assertEquals(6, children.size()); WebfluxHandlerMethodIndexElement handlerElement1 = getWebfluxIndexElements(children, "/person/{id}", "GET").get(0); assertEquals("/person/{id}", handlerElement1.getPath()); @@ -192,8 +191,8 @@ void testNestedRoutesMappingSymbols2() throws Exception { assertEquals(1, routeBeans.length); assertEquals("routingFunction2", routeBeans[0].getName()); - SpringIndexElement[] children = routeBeans[0].getChildren(); - assertEquals(6, children.length); + List children = routeBeans[0].getChildren(); + assertEquals(6, children.size()); WebfluxHandlerMethodIndexElement handlerelement1 = getWebfluxIndexElements(children, "/person/{id}", "GET").get(0); assertEquals("/person/{id}", handlerelement1.getPath()); @@ -237,8 +236,8 @@ void testNestedRoutesMappingSymbols3() throws Exception { assertEquals(1, routeBeans.length); assertEquals("routingFunction", routeBeans[0].getName()); - SpringIndexElement[] children = routeBeans[0].getChildren(); - assertEquals(12, children.length); + List children = routeBeans[0].getChildren(); + assertEquals(12, children.size()); WebfluxHandlerMethodIndexElement handlerElement1 = getWebfluxIndexElements(children, "/person/sub1/sub2/{id}", "GET").get(0); assertEquals("/person/sub1/sub2/{id}", handlerElement1.getPath()); @@ -302,8 +301,8 @@ void testUpdatedRouteInChangedDocument() throws Exception { assertEquals(1, routeBeans.length); assertEquals("route", routeBeans[0].getName()); - SpringIndexElement[] children = routeBeans[0].getChildren(); - assertEquals(8, children.length); + List children = routeBeans[0].getChildren(); + assertEquals(8, children.size()); WebfluxHandlerMethodIndexElement handlerElement1 = getWebfluxIndexElements(children, "/hello-updated", "GET").get(0); assertEquals("/hello-updated", handlerElement1.getPath()); @@ -334,8 +333,8 @@ private boolean containsSymbol(List symbols, String n return false; } - private List getWebfluxIndexElements(SpringIndexElement[] indexElements, String path, String httpMethod) { - return Arrays.stream(indexElements) + private List getWebfluxIndexElements(List children, String path, String httpMethod) { + return children.stream() .filter((obj) -> obj instanceof WebfluxHandlerMethodIndexElement) .map((obj -> (WebfluxHandlerMethodIndexElement) obj)) .filter((addon) -> addon.getPath().equals(path) && Arrays.asList(addon.getHttpMethods()).contains(httpMethod))