From 4ea0313ea72796899d85954d6bf94d098769d159 Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Fri, 14 Apr 2023 10:16:19 +0200 Subject: [PATCH] moved the spring bean index updates out of the indexers into the handler --- .../vscode/boot/app/SpringSymbolIndex.java | 81 +++++-- .../boot/index/SpringMetamodelIndex.java | 83 +++++-- .../java/utils/SpringFactoriesIndexer.java | 15 +- .../boot/java/utils/SpringIndexerJava.java | 31 +-- .../boot/java/utils/SpringIndexerXML.java | 26 +-- .../vscode/boot/java/utils/SymbolHandler.java | 4 +- .../test/SpringMetamodelIndexTest.java | 213 ++++++++++++++++++ .../test/SpringMetamodelIndexerBeansTest.java | 24 +- 8 files changed, 400 insertions(+), 77 deletions(-) create mode 100644 headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/metamodel/test/SpringMetamodelIndexTest.java diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java index e21d3a39f..c3cea213a 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java @@ -152,20 +152,60 @@ public class SpringSymbolIndex implements InitializingBean { SymbolHandler handler = new SymbolHandler() { @Override - public void addSymbol(IJavaProject project, String docURI, EnhancedSymbolInformation enhancedSymbol, Bean beanDefinition) { - SpringSymbolIndex.this.addSymbol(project, docURI, enhancedSymbol); + public void addSymbols(IJavaProject project, String docURI, EnhancedSymbolInformation[] enhancedSymbols, Bean[] beanDefinitions) { + if (enhancedSymbols != null) { + SpringSymbolIndex.this.addSymbolsByDoc(project, docURI, enhancedSymbols); + } - if (beanDefinition != null) { - springIndex.registerBean(beanDefinition); + if (beanDefinitions != null) { + springIndex.updateBeans(project.getElementName(), docURI, beanDefinitions); } } @Override - public void removeSymbols(IJavaProject project, String docURI) { - SpringSymbolIndex.this.removeSymbolsByDoc(project, docURI); - springIndex.removeBeans(project, docURI); + public void addSymbols(IJavaProject project, EnhancedSymbolInformation[] enhancedSymbols, + Bean[] beanDefinitions) { + + // organize symbols by doc URI + Map> symbolsPerDoc = new HashMap<>(); + for (EnhancedSymbolInformation symbol : enhancedSymbols) { + Either location = symbol.getSymbol().getLocation(); + String docURI = location.isLeft() ? location.getLeft().getUri() : location.getRight().getUri(); + + symbolsPerDoc.computeIfAbsent(docURI, k -> new ArrayList<>()).add(symbol); + } + + // add symbols per doc + for (Map.Entry> entry : symbolsPerDoc.entrySet()) { + String docURI = entry.getKey(); + List symbols = entry.getValue(); + + SpringSymbolIndex.this.addSymbolsByDoc(project, docURI, (EnhancedSymbolInformation[]) symbols.toArray(new EnhancedSymbolInformation[symbols.size()])); + } + + // organize beans per doc URI + Map> beansPerDoc = new HashMap<>(); + for (Bean bean : beanDefinitions) { + String docURI = bean.getLocation().getUri(); + beansPerDoc.computeIfAbsent(docURI, k -> new ArrayList<>()).add(bean); + } + + // add beans per doc URI + for (Map.Entry> entry : beansPerDoc.entrySet()) { + String docURI = entry.getKey(); + List beans = entry.getValue(); + + springIndex.updateBeans(project.getElementName(), docURI, (Bean[]) beans.toArray(new Bean[beans.size()])); + } + } + @Override + public void removeSymbols(IJavaProject project, String docURI) { + SpringSymbolIndex.this.removeSymbolsByDoc(project, docURI); + springIndex.removeBeans(project.getElementName(), docURI); + } + }; Map namespaceHandler = new HashMap<>(); @@ -821,20 +861,27 @@ public class SpringSymbolIndex implements InitializingBean { } - private void addSymbol(IJavaProject project, String docURI, EnhancedSymbolInformation enhancedSymbol) { - synchronized(this.symbols) { - symbols.add(enhancedSymbol); - } + private void addSymbolsByDoc(IJavaProject project, String docURI, EnhancedSymbolInformation[] enhancedSymbols) { List docSymbols = symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList()); - synchronized(docSymbols) { - docSymbols.add(enhancedSymbol); - } - List projectSymbols = symbolsByProject.computeIfAbsent(project.getElementName(), s -> new ArrayList()); - synchronized(projectSymbols) { - projectSymbols.add(enhancedSymbol); + + for (EnhancedSymbolInformation enhancedSymbol : enhancedSymbols) { + + synchronized(this.symbols) { + symbols.add(enhancedSymbol); + } + + synchronized(docSymbols) { + docSymbols.add(enhancedSymbol); + } + + synchronized(projectSymbols) { + projectSymbols.add(enhancedSymbol); + } + } + } private void removeSymbolsByDoc(IJavaProject project, String docURI) { 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 eb4899ea5..d2823afe4 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 @@ -11,42 +11,97 @@ package org.springframework.ide.vscode.boot.index; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.stream.Collectors; -import org.eclipse.lsp4j.Location; -import org.springframework.ide.vscode.commons.java.IJavaProject; 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.SpringModelService; public class SpringMetamodelIndex implements SpringModelService { - - private List beans; + private final ConcurrentMap beansPerProject; + public SpringMetamodelIndex() { - this.beans = new ArrayList<>(); + beansPerProject = new ConcurrentHashMap<>(); } @Override public CompletableFuture> beans(String project) { - return CompletableFuture.completedFuture(beans); + return CompletableFuture.completedFuture(null); } - public Bean[] getBeans(String name) { - return this.beans.stream().filter(bean -> bean.getName().equals(name)).collect(Collectors.toList()).toArray(new Bean[0]); + public void updateBeans(String projectName, Bean[] beanDefinitions) { + beansPerProject.put(projectName, beanDefinitions); } - public void registerBean(Bean beanDefinition) { - this.beans.add(beanDefinition); + public void updateBeans(String projectName, String docURI, Bean[] beanDefinitions) { + Bean[] existingBeans = beansPerProject.putIfAbsent(projectName, beanDefinitions); + + if (existingBeans != null) { + + List beans = new ArrayList<>(); + + // add old, unrelated beans + for (Bean bean : existingBeans) { + if (!bean.getLocation().getUri().equals(docURI)) { + beans.add(bean); + } + } + + // 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 registerBean(String name, String type, Location location, InjectionPoint[] injectionPoints, String[] supertypes) { - registerBean(new Bean(name, type, location, injectionPoints, supertypes)); + public void removeBeans(String projectName) { + beansPerProject.remove(projectName); } - public void removeBeans(IJavaProject project, String docURI) { + 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()])); + } + } + + 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)) { + result.add(bean); + } + } + } + + return (Bean[]) result.toArray(new Bean[result.size()]); + } + + public Bean[] getBeansWithName(String project, String name) { + Bean[] allBeans = this.beansPerProject.get(project); + + if (allBeans != null) { + return Arrays.stream(allBeans).filter(bean -> bean.getName().equals(name)).collect(Collectors.toList()).toArray(new Bean[0]); + } + else { + return null; + } } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringFactoriesIndexer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringFactoriesIndexer.java index 706afafdd..afe6aeb60 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringFactoriesIndexer.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringFactoriesIndexer.java @@ -19,6 +19,7 @@ import java.nio.file.Path; import java.nio.file.PathMatcher; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; @@ -42,6 +43,7 @@ import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation; import org.springframework.ide.vscode.commons.java.IClasspathUtil; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.protocol.java.Classpath; +import org.springframework.ide.vscode.commons.protocol.spring.Bean; import org.springframework.ide.vscode.commons.util.text.LanguageId; import org.springframework.ide.vscode.commons.util.text.Region; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -183,10 +185,9 @@ public class SpringFactoriesIndexer implements SpringIndexer { } if (symbols != null) { - for (int i = 0; i < symbols.length; i++) { - CachedSymbol symbol = symbols[i]; - symbolHandler.addSymbol(project, symbol.getDocURI(), symbol.getEnhancedSymbol(), symbol.getBean()); - } + EnhancedSymbolInformation[] enhancedSymbols = Arrays.stream(symbols).map(cachedSymbol -> cachedSymbol.getEnhancedSymbol()).toArray(EnhancedSymbolInformation[]::new); + Bean[] beans = Arrays.stream(symbols).filter(cachedSymbol -> cachedSymbol.getBean() != null).map(cachedSymbol -> cachedSymbol.getBean()).toArray(Bean[]::new); + symbolHandler.addSymbols(project, enhancedSymbols, beans); } long endTime = System.currentTimeMillis(); @@ -255,9 +256,9 @@ public class SpringFactoriesIndexer implements SpringIndexer { String file = new File(new URI(docURI)).getAbsolutePath(); this.cache.update(cacheKey, file, updatedDoc.getLastModified(), generatedSymbols, null); - for (CachedSymbol symbol : generatedSymbols) { - symbolHandler.addSymbol(project, symbol.getDocURI(), symbol.getEnhancedSymbol(), symbol.getBean()); - } + EnhancedSymbolInformation[] symbols = generatedSymbols.stream().map(cachedSymbol -> cachedSymbol.getEnhancedSymbol()).toArray(EnhancedSymbolInformation[]::new); + Bean[] beans = generatedSymbols.stream().filter(cachedSymbol -> cachedSymbol.getBean() != null).map(cachedSymbol -> cachedSymbol.getBean()).toArray(Bean[]::new); + symbolHandler.addSymbols(project, docURI, symbols, beans); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java index 8b1ecdb83..692176bb9 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java @@ -56,6 +56,7 @@ import org.springframework.ide.vscode.commons.java.IClasspath; import org.springframework.ide.vscode.commons.java.IClasspathUtil; 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.util.UriUtil; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -73,6 +74,10 @@ public class SpringIndexerJava implements SpringIndexer { private static final Logger log = LoggerFactory.getLogger(SpringIndexerJava.class); + // whenever the implementation of the indexer changes in a way that the stored data in the cache is no longer valid, + // we need to change the generation - this will result in a re-indexing due to no up-to-date cache data being found + private static final String GENERATION = "GEN-2"; + private final SymbolHandler symbolHandler; private final AnnotationHierarchyAwareLookup symbolProviders; private final SymbolCache cache; @@ -222,9 +227,10 @@ public class SpringIndexerJava implements SpringIndexer { this.cache.update(cacheKey, file, lastModified, generatedSymbols, context.getDependencies()); // dependencyTracker.dump(); - for (CachedSymbol symbol : generatedSymbols) { - symbolHandler.addSymbol(project, symbol.getDocURI(), symbol.getEnhancedSymbol(), symbol.getBean()); - } + EnhancedSymbolInformation[] symbols = generatedSymbols.stream().map(cachedSymbol -> cachedSymbol.getEnhancedSymbol()).toArray(EnhancedSymbolInformation[]::new); + Bean[] beans = generatedSymbols.stream().filter(cachedSymbol -> cachedSymbol.getBean() != null).map(cachedSymbol -> cachedSymbol.getBean()).toArray(Bean[]::new); + symbolHandler.addSymbols(project, docURI, symbols, beans); + Set scannedFiles = new HashSet<>(); scannedFiles.add(file); fileScannedEvent(file); @@ -308,12 +314,12 @@ public class SpringIndexerJava implements SpringIndexer { parser.createASTs(javaFiles, null, new String[0], requestor, null); - for (CachedSymbol symbol : generatedSymbols) { - symbolHandler.addSymbol(project, symbol.getDocURI(), symbol.getEnhancedSymbol(), symbol.getBean()); - } - + EnhancedSymbolInformation[] symbols = generatedSymbols.stream().map(cachedSymbol -> cachedSymbol.getEnhancedSymbol()).toArray(EnhancedSymbolInformation[]::new); + Bean[] beans = generatedSymbols.stream().filter(cachedSymbol -> cachedSymbol.getBean() != null).map(cachedSymbol -> cachedSymbol.getBean()).toArray(Bean[]::new); + symbolHandler.addSymbols(project, symbols, beans); + SymbolCacheKey cacheKey = getCacheKey(project); - SpringIndexerJava.this.cache.update(cacheKey, javaFiles, lastModified, generatedSymbols, dependencies); + this.cache.update(cacheKey, javaFiles, lastModified, generatedSymbols, dependencies); return scannedTypes; } @@ -382,10 +388,9 @@ public class SpringIndexerJava implements SpringIndexer { } if (symbols != null) { - for (int i = 0; i < symbols.length; i++) { - CachedSymbol symbol = symbols[i]; - symbolHandler.addSymbol(project, symbol.getDocURI(), symbol.getEnhancedSymbol(), symbol.getBean()); - } + EnhancedSymbolInformation[] enhancedSymbols = Arrays.stream(symbols).map(cachedSymbol -> cachedSymbol.getEnhancedSymbol()).toArray(EnhancedSymbolInformation[]::new); + Bean[] beans = Arrays.stream(symbols).filter(cachedSymbol -> cachedSymbol.getBean() != null).map(cachedSymbol -> cachedSymbol.getBean()).toArray(Bean[]::new); + symbolHandler.addSymbols(project, enhancedSymbols, beans); } } @@ -610,7 +615,7 @@ public class SpringIndexerJava implements SpringIndexer { .map(file -> file.getAbsolutePath() + "#" + file.lastModified()) .collect(Collectors.joining(",")); - return new SymbolCacheKey(project.getElementName() + "-java-", DigestUtils.md5Hex(classpathIdentifier).toUpperCase()); + return new SymbolCacheKey(project.getElementName() + "-java-", DigestUtils.md5Hex(GENERATION + "-" + classpathIdentifier).toUpperCase()); } public void setScanTestJavaSources(boolean scanTestJavaSources) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerXML.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerXML.java index 41ab96241..0b389e177 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerXML.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerXML.java @@ -36,6 +36,7 @@ import org.springframework.ide.vscode.commons.java.IClasspath; import org.springframework.ide.vscode.commons.java.IClasspathUtil; 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.util.UriUtil; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -122,10 +123,9 @@ public class SpringIndexerXML implements SpringIndexer { } if (symbols != null) { - for (int i = 0; i < symbols.length; i++) { - CachedSymbol symbol = symbols[i]; - symbolHandler.addSymbol(project, symbol.getDocURI(), symbol.getEnhancedSymbol(), symbol.getBean()); - } + EnhancedSymbolInformation[] enhancedSymbols = Arrays.stream(symbols).map(cachedSymbol -> cachedSymbol.getEnhancedSymbol()).toArray(EnhancedSymbolInformation[]::new); + Bean[] beans = Arrays.stream(symbols).filter(cachedSymbol -> cachedSymbol.getBean() != null).map(cachedSymbol -> cachedSymbol.getBean()).toArray(Bean[]::new); + symbolHandler.addSymbols(project, enhancedSymbols, beans); } long endTime = System.currentTimeMillis(); @@ -153,16 +153,14 @@ public class SpringIndexerXML implements SpringIndexer { String file = new File(new URI(docURI)).getAbsolutePath(); this.cache.update(cacheKey, file, updatedDoc.getLastModified(), generatedSymbols, null); - for (CachedSymbol symbol : generatedSymbols) { - symbolHandler.addSymbol(project, symbol.getDocURI(), symbol.getEnhancedSymbol(), symbol.getBean()); - } + EnhancedSymbolInformation[] symbols = generatedSymbols.stream().map(cachedSymbol -> cachedSymbol.getEnhancedSymbol()).toArray(EnhancedSymbolInformation[]::new); + Bean[] beans = generatedSymbols.stream().filter(cachedSymbol -> cachedSymbol.getBean() != null).map(cachedSymbol -> cachedSymbol.getBean()).toArray(Bean[]::new); + symbolHandler.addSymbols(project, docURI, symbols, beans); } @Override public void updateFiles(IJavaProject project, DocumentDescriptor[] updatedDocs) throws Exception { - List generatedSymbols = new ArrayList(); - for (DocumentDescriptor updatedDoc : updatedDocs) { String docURI = updatedDoc.getDocURI(); @@ -170,15 +168,17 @@ public class SpringIndexerXML implements SpringIndexer { Path path = new File(new URI(docURI)).toPath(); String content = new String(Files.readAllBytes(path)); + + List generatedSymbols = new ArrayList(); scanFile(project, content, docURI, updatedDoc.getLastModified(), generatedSymbols); SymbolCacheKey cacheKey = getCacheKey(project); String file = new File(new URI(docURI)).getAbsolutePath(); this.cache.update(cacheKey, file, updatedDoc.getLastModified(), generatedSymbols, null); - } - - for (CachedSymbol symbol : generatedSymbols) { - symbolHandler.addSymbol(project, symbol.getDocURI(), symbol.getEnhancedSymbol(), symbol.getBean()); + + EnhancedSymbolInformation[] symbols = generatedSymbols.stream().map(cachedSymbol -> cachedSymbol.getEnhancedSymbol()).toArray(EnhancedSymbolInformation[]::new); + Bean[] beans = generatedSymbols.stream().filter(cachedSymbol -> cachedSymbol.getBean() != null).map(cachedSymbol -> cachedSymbol.getBean()).toArray(Bean[]::new); + symbolHandler.addSymbols(project, docURI, symbols, beans); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SymbolHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SymbolHandler.java index a12523eab..cb61b3e6f 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SymbolHandler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SymbolHandler.java @@ -19,7 +19,9 @@ import org.springframework.ide.vscode.commons.protocol.spring.Bean; */ public interface SymbolHandler { - void addSymbol(IJavaProject project, String docURI, EnhancedSymbolInformation enhancedSymbol, Bean beanDefinition); + void addSymbols(IJavaProject project, String docURI, EnhancedSymbolInformation[] enhancedSymbols, Bean[] beanDefinitions); + void addSymbols(IJavaProject project, EnhancedSymbolInformation[] enhancedSymbols, Bean[] beanDefinitions); + void removeSymbols(IJavaProject project, String docURI); } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/metamodel/test/SpringMetamodelIndexTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/metamodel/test/SpringMetamodelIndexTest.java new file mode 100644 index 000000000..0b8b761a2 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/metamodel/test/SpringMetamodelIndexTest.java @@ -0,0 +1,213 @@ +/******************************************************************************* + * Copyright (c) 2023 VMware, 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 + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VMware, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.metamodel.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.assertj.core.util.Arrays; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.junit.jupiter.api.Test; +import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; +import org.springframework.ide.vscode.commons.protocol.spring.Bean; +import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint; + +public class SpringMetamodelIndexTest { + + private InjectionPoint[] emptyInjectionPoints = new InjectionPoint[0]; + private String[] emptySupertypes = new String[0]; + + private Location locationForDoc1 = new Location("docURI1", new Range(new Position(1, 1), new Position(1, 10))); + private Location locationForDoc2 = new Location("docURI2", new Range(new Position(2, 1), new Position(2, 10))); + + @Test + void testEmptyIndex() { + SpringMetamodelIndex index = new SpringMetamodelIndex(); + assertNull(index.getBeansOfProject("someProject")); + assertNull(index.getBeansWithName("someProject", "someBeanName")); + } + + @Test + void testSimpleProjectWithBeansPerProject() { + SpringMetamodelIndex index = new SpringMetamodelIndex(); + Bean bean1 = new Bean("beanName1", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes); + Bean bean2 = new Bean("beanName2", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes); + Bean bean3 = new Bean("beanName3", "beanType", locationForDoc2, emptyInjectionPoints, emptySupertypes); + + index.updateBeans("someProject", new Bean[] {bean1, bean2, bean3}); + + Bean[] beans = index.getBeansOfProject("someProject"); + assertNotNull(beans); + assertEquals(3, beans.length); + + List beansList = Arrays.asList(beans); + assertTrue(beansList.contains(bean1)); + assertTrue(beansList.contains(bean2)); + assertTrue(beansList.contains(bean3)); + + Bean anotherBean = new Bean("anotherBean", "beanType", null, emptyInjectionPoints, emptySupertypes); + + assertFalse(beansList.contains(anotherBean)); + } + + @Test + void testSimpleProjectWithBeansPerDocument() { + SpringMetamodelIndex index = new SpringMetamodelIndex(); + Bean bean1 = new Bean("beanName1", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes); + Bean bean2 = new Bean("beanName1", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes); + Bean bean3 = new Bean("beanWithDifferentName", "beanType", locationForDoc2, emptyInjectionPoints, emptySupertypes); + + index.updateBeans("someProject", new Bean[] {bean1, bean2, bean3}); + + Bean[] beansByLocation1 = index.getBeansOfDocument(locationForDoc1.getUri()); + assertNotNull(beansByLocation1); + assertEquals(2, beansByLocation1.length); + + List beansList = Arrays.asList(beansByLocation1); + assertTrue(beansList.contains(bean1)); + assertTrue(beansList.contains(bean2)); + assertFalse(beansList.contains(bean3)); + + Bean[] beansByLocation2 = index.getBeansOfDocument(locationForDoc2.getUri()); + assertNotNull(beansByLocation2); + assertEquals(1, beansByLocation2.length); + assertEquals(bean3, beansByLocation2[0]); + + Bean[] beansOfNonExistingLocation = index.getBeansOfDocument("otherDocURI"); + assertEquals(0, beansOfNonExistingLocation.length); + } + + @Test + void testSimpleProjectWithBeansPerName() { + SpringMetamodelIndex index = new SpringMetamodelIndex(); + Bean bean1 = new Bean("beanName1", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes); + Bean bean2 = new Bean("beanName1", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes); + Bean bean3 = new Bean("beanWithDifferentName", "beanType", locationForDoc2, emptyInjectionPoints, emptySupertypes); + + index.updateBeans("someProject", new Bean[] {bean1, bean2, bean3}); + + Bean[] beansByName = index.getBeansWithName("someProject", "beanName1"); + assertNotNull(beansByName); + assertEquals(2, beansByName.length); + + List beansList = Arrays.asList(beansByName); + assertTrue(beansList.contains(bean1)); + assertTrue(beansList.contains(bean2)); + assertFalse(beansList.contains(bean3)); + + assertNull(index.getBeansWithName("nonExistingProject", "beanName1")); + } + + @Test + void testUpdateBeansForSpecificDoc() { + SpringMetamodelIndex index = new SpringMetamodelIndex(); + Bean bean1 = new Bean("beanName1", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes); + Bean bean2 = new Bean("beanName2", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes); + Bean bean3 = new Bean("beanName3", "beanType", locationForDoc2, emptyInjectionPoints, emptySupertypes); + + index.updateBeans("someProject", locationForDoc1.getUri(), new Bean[] {bean1, bean2}); + index.updateBeans("someProject", locationForDoc2.getUri(), new Bean[] {bean3}); + + Bean updatedBean1 = new Bean("updated1", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes); + Bean updatedBean2 = new Bean("updated2", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes); + + index.updateBeans("someProject", locationForDoc1.getUri(), new Bean[] {updatedBean1, updatedBean2}); + + Bean[] beans = index.getBeansOfProject("someProject"); + assertNotNull(beans); + assertEquals(3, beans.length); + + List beansList = Arrays.asList(beans); + assertTrue(beansList.contains(updatedBean1)); + assertTrue(beansList.contains(updatedBean2)); + assertTrue(beansList.contains(bean3)); + + assertFalse(beansList.contains(bean1)); + assertFalse(beansList.contains(bean2)); + + Bean anotherBean = new Bean("anotherBean", "beanType", null, emptyInjectionPoints, emptySupertypes); + assertFalse(beansList.contains(anotherBean)); + } + + @Test + void testUpdateAllBeansForSpecificProject() { + SpringMetamodelIndex index = new SpringMetamodelIndex(); + Bean bean1 = new Bean("beanName1", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes); + Bean bean2 = new Bean("beanName2", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes); + + index.updateBeans("someProject", new Bean[] {bean1, bean2}); + + Bean bean3 = new Bean("beanName3", "beanType", locationForDoc2, emptyInjectionPoints, emptySupertypes); + + index.updateBeans("someProject", new Bean[] {bean3}); + + Bean[] beans = index.getBeansOfProject("someProject"); + assertNotNull(beans); + assertEquals(1, beans.length); + + List beansList = Arrays.asList(beans); + assertFalse(beansList.contains(bean1)); + assertFalse(beansList.contains(bean2)); + assertTrue(beansList.contains(bean3)); + } + + @Test + void testRemoveAllBeansForSpecificProject() { + SpringMetamodelIndex index = new SpringMetamodelIndex(); + Bean bean1 = new Bean("beanName1", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes); + Bean bean2 = new Bean("beanName2", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes); + Bean bean3 = new Bean("beanName3", "beanType", locationForDoc2, emptyInjectionPoints, emptySupertypes); + + index.updateBeans("someProject1", new Bean[] {bean1, bean2}); + index.updateBeans("someProject2", new Bean[] {bean3}); + + index.removeBeans("someProject1"); + + Bean[] beans = index.getBeansOfProject("someProject2"); + assertNotNull(beans); + assertEquals(1, beans.length); + + List beansList = Arrays.asList(beans); + assertFalse(beansList.contains(bean1)); + assertFalse(beansList.contains(bean2)); + assertTrue(beansList.contains(bean3)); + + assertNull(index.getBeansOfProject("someProject1")); + } + + @Test + void testRemoveAllBeansForSpecificDocument() { + SpringMetamodelIndex index = new SpringMetamodelIndex(); + Bean bean1 = new Bean("beanName1", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes); + Bean bean2 = new Bean("beanName2", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes); + Bean bean3 = new Bean("beanName3", "beanType", locationForDoc2, emptyInjectionPoints, emptySupertypes); + + index.updateBeans("someProject", new Bean[] {bean1, bean2, bean3}); + index.removeBeans("someProject", locationForDoc1.getUri()); + + Bean[] beans = index.getBeansOfProject("someProject"); + assertNotNull(beans); + assertEquals(1, beans.length); + + List beansList = Arrays.asList(beans); + assertFalse(beansList.contains(bean1)); + assertFalse(beansList.contains(bean2)); + assertTrue(beansList.contains(bean3)); + } + +} diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/metamodel/test/SpringMetamodelIndexerBeansTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/metamodel/test/SpringMetamodelIndexerBeansTest.java index 137e4200f..c53d6da68 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/metamodel/test/SpringMetamodelIndexerBeansTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/metamodel/test/SpringMetamodelIndexerBeansTest.java @@ -83,7 +83,7 @@ public class SpringMetamodelIndexerBeansTest { @Test void testBeansNameAndTypeFromBeanAnnotatedMethod() { - Bean[] beans = springIndex.getBeans("bean1"); + Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "bean1"); assertEquals(1, beans.length); assertEquals("bean1", beans[0].getName()); @@ -92,7 +92,7 @@ public class SpringMetamodelIndexerBeansTest { @Test void testBeansDefintionLocationFromBeanAnnotatedMethod() { - Bean[] beans = springIndex.getBeans("bean1"); + Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "bean1"); String docUri = directory.toPath().resolve("src/main/java/org/test/MainClass.java").toUri().toString(); Location location = new Location(docUri, new Range(new Position(13, 1), new Position(13, 6))); @@ -101,7 +101,7 @@ public class SpringMetamodelIndexerBeansTest { @Test void testBeansNameAndTypeFromComponentAnnotatedClassExists() { - Bean[] beans = springIndex.getBeans("constructorInjectionService"); + Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "constructorInjectionService"); assertEquals(1, beans.length); assertEquals("constructorInjectionService", beans[0].getName()); @@ -110,7 +110,7 @@ public class SpringMetamodelIndexerBeansTest { @Test void testBeansDefintionLocationFromComponentAnnotatedClass() { - Bean[] beans = springIndex.getBeans("constructorInjectionService"); + Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "constructorInjectionService"); String docUri = directory.toPath().resolve("src/main/java/org/test/injections/ConstructorInjectionService.java").toUri().toString(); Location location = new Location(docUri, new Range(new Position(6, 0), new Position(6, 8))); @@ -119,7 +119,7 @@ public class SpringMetamodelIndexerBeansTest { @Test void testBeansNameAndTypeFromConfigurationAnnotatedClassExists() { - Bean[] beans = springIndex.getBeans("configurationWithoutInjection"); + Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "configurationWithoutInjection"); assertEquals(1, beans.length); assertEquals("configurationWithoutInjection", beans[0].getName()); @@ -128,7 +128,7 @@ public class SpringMetamodelIndexerBeansTest { @Test void testBeansDefinitionLocationFromConfigurationAnnotatedClass() { - Bean[] beans = springIndex.getBeans("configurationWithoutInjection"); + Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "configurationWithoutInjection"); assertEquals(1, beans.length); String docUri = directory.toPath().resolve("src/main/java/org/test/injections/ConfigurationWithoutInjection.java").toUri().toString(); @@ -137,7 +137,7 @@ public class SpringMetamodelIndexerBeansTest { @Test void testBeanNoInjectionPointsFromBeanAnnotatedMethod() { - Bean[] beans = springIndex.getBeans("beanWithoutInjections"); + Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "beanWithoutInjections"); assertEquals(1, beans.length); InjectionPoint[] injectionPoints = beans[0].getInjectionPoints(); @@ -146,7 +146,7 @@ public class SpringMetamodelIndexerBeansTest { @Test void testBeanInjectionPointsFromBeanAnnotatedMethod() { - Bean[] beans = springIndex.getBeans("manualBeanWithConstructor"); + Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "manualBeanWithConstructor"); assertEquals(1, beans.length); String docUri = directory.toPath().resolve("src/main/java/org/test/injections/ConfigurationWithInjections.java").toUri().toString(); @@ -167,7 +167,7 @@ public class SpringMetamodelIndexerBeansTest { @Test void testBeanInjectionPointsFromConstructor() { - Bean[] beans = springIndex.getBeans("constructorInjectionService"); + Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "constructorInjectionService"); assertEquals(1, beans.length); String docUri = directory.toPath().resolve("src/main/java/org/test/injections/ConstructorInjectionService.java").toUri().toString(); @@ -188,7 +188,7 @@ public class SpringMetamodelIndexerBeansTest { @Test void testBeanInjectionPointsFromAutowiredFields() { - Bean[] beans = springIndex.getBeans("autowiredInjectionService"); + Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "autowiredInjectionService"); assertEquals(1, beans.length); String docUri = directory.toPath().resolve("src/main/java/org/test/injections/AutowiredInjectionService.java").toUri().toString(); @@ -209,7 +209,7 @@ public class SpringMetamodelIndexerBeansTest { @Test void testBeanFromSpringDataRepository() { - Bean[] beans = springIndex.getBeans("customerRepository"); + Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "customerRepository"); assertEquals(1, beans.length); assertEquals("customerRepository", beans[0].getName()); @@ -221,7 +221,7 @@ public class SpringMetamodelIndexerBeansTest { @Test void testBeansWithSupertypes() { - Bean[] beans = springIndex.getBeans("beanWithSupertypes"); + Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "beanWithSupertypes"); assertEquals(1, beans.length); assertTrue(beans[0].isTypeCompatibleWith("java.lang.Object"));