From 4f007de1bd710463d9bef8c89456d73b4d4fcb5f Mon Sep 17 00:00:00 2001 From: BoykoAlex Date: Wed, 12 May 2021 23:21:55 -0400 Subject: [PATCH] Wait for document symbols in sync fashion --- .../java/FutureProjectFinder.java | 82 +++++++++++++++++++ .../ide/vscode/commons/util/FileUtils.java | 2 +- .../boot/app/BootLanguageServerBootApp.java | 7 ++ .../vscode/boot/app/SpringSymbolIndex.java | 58 +++++++++---- .../utils/test/AdvancedSourceLinksTest.java | 16 +++- .../test/SpringIndexerXMLProjectTest.java | 16 +++- .../utils/test/XmlBeansHyperlinkTest.java | 16 +++- .../test/XMLSpelExpressionValidationTest.java | 16 +++- 8 files changed, 185 insertions(+), 28 deletions(-) create mode 100644 headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/FutureProjectFinder.java diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/FutureProjectFinder.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/FutureProjectFinder.java new file mode 100644 index 000000000..3a52275cf --- /dev/null +++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/FutureProjectFinder.java @@ -0,0 +1,82 @@ +package org.springframework.ide.vscode.commons.languageserver.java; + +import java.net.URI; +import java.util.Map; +import java.util.Optional; +import java.util.WeakHashMap; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver.Listener; + +public class FutureProjectFinder implements DisposableBean { + + private JavaProjectFinder projectFinder; + private ProjectObserver projectObserver; + + private WeakHashMap> pendingFindProjectRequests = new WeakHashMap<>(); + + private final Listener LISTENER = new Listener() { + + @Override + public void deleted(IJavaProject project) { + } + + @Override + public void created(IJavaProject project) { + resolvePendingRequests(project); + } + + @Override + public void changed(IJavaProject project) { + } + }; + + + public FutureProjectFinder(JavaProjectFinder projectFinder, Optional projectObserver) { + this.projectFinder = projectFinder; + this.projectObserver = projectObserver.orElse(null); + if (this.projectObserver != null) { + this.projectObserver.addListener(LISTENER); + } + } + + synchronized private void resolvePendingRequests(IJavaProject project) { + for (Map.Entry> e : pendingFindProjectRequests.entrySet()) { + Optional jp = projectFinder.find(new TextDocumentIdentifier(e.getKey().toString())); + if (jp.isPresent()) { + e.getValue().complete(jp.get()); + pendingFindProjectRequests.remove(e.getKey()); + } + } + } + + @Override + public void destroy() throws Exception { + if (projectObserver != null) { + projectObserver.removeListener(LISTENER); + } + } + + synchronized public CompletableFuture findFuture(URI uri) { + TextDocumentIdentifier id = new TextDocumentIdentifier(uri.toString()); + Optional jp = projectFinder.find(id); + if (jp.isPresent()) { + return CompletableFuture.completedFuture(jp.get()); + } else { + if (projectObserver == null) { + throw new IllegalStateException("Future project lookup not supported without ProjectObserver bean present"); + } + CompletableFuture cf = pendingFindProjectRequests.get(uri); + if (cf == null) { + cf = new CompletableFuture(); + pendingFindProjectRequests.put(uri, cf); + } + return cf; + } + } + + +} diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/FileUtils.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/FileUtils.java index 4735deb85..20d108759 100644 --- a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/FileUtils.java +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/FileUtils.java @@ -37,7 +37,7 @@ public class FileUtils { } public static File findFile(File folder, String fileNameToFind, boolean recursiveUp) { - if (folder != null && folder.exists()) { + if (folder != null) { File file = new File(folder, fileNameToFind); if (file.isFile()) { return file; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java index f8267182f..211b8dc1b 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java @@ -11,6 +11,7 @@ package org.springframework.ide.vscode.boot.app; import java.io.File; +import java.util.Optional; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Qualifier; @@ -54,7 +55,9 @@ import org.springframework.ide.vscode.boot.xml.SpringXMLCompletionEngine; import org.springframework.ide.vscode.boot.yaml.completions.ApplicationYamlAssistContext; import org.springframework.ide.vscode.boot.yaml.completions.SpringYamlCompletionEngine; import org.springframework.ide.vscode.commons.languageserver.LanguageServerRunner; +import org.springframework.ide.vscode.commons.languageserver.java.FutureProjectFinder; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; import org.springframework.ide.vscode.commons.languageserver.util.DocumentEventListenerManager; import org.springframework.ide.vscode.commons.languageserver.util.LspClient; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; @@ -225,4 +228,8 @@ public class BootLanguageServerBootApp { new SampleProjectsProvider() )); } + + @Bean FutureProjectFinder futureProjectFinder(JavaProjectFinder projectFinder, Optional projectObserver) { + return new FutureProjectFinder(projectFinder, projectObserver); + } } 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 6c656aa11..b1cb33769 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 @@ -28,6 +28,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -54,6 +55,7 @@ import org.springframework.ide.vscode.boot.java.utils.SymbolHandler; import org.springframework.ide.vscode.boot.java.utils.SymbolIndexConfig; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.SpringProjectUtil; +import org.springframework.ide.vscode.commons.languageserver.java.FutureProjectFinder; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver.Listener; @@ -78,6 +80,7 @@ public class SpringSymbolIndex implements InitializingBean { @Autowired BootLanguageServerParams params; @Autowired AnnotationHierarchyAwareLookup specificProviders; @Autowired SymbolCache cache; + @Autowired FutureProjectFinder futureProjectFinder; private static final String QUERY_PARAM_LOCATION_PREFIX = "locationPrefix:"; private static final int MAX_NUMBER_OF_SYMBOLS_IN_RESPONSE = 50; @@ -120,7 +123,10 @@ public class SpringSymbolIndex implements InitializingBean { private String watchXMLDeleteRegistration; private String watchXMLCreatedRegistration; private String watchXMLChangedRegistration; - + + // Futures resolved when project is initialized/indexed + private Map> initializedProjects = new HashMap<>(); + private SimpleWorkspaceService getWorkspaceService() { return server.getServer().getWorkspaceService(); @@ -273,8 +279,16 @@ public class SpringSymbolIndex implements InitializingBean { log.error("{}", e); } } - + public CompletableFuture initializeProject(IJavaProject project) { + CompletableFuture cf = _initializeProject(project); + cf.thenAccept( f -> { + projectInitializedFuture(project).complete(null); + }); + return cf; + } + + private CompletableFuture _initializeProject(IJavaProject project) { try { if (SpringProjectUtil.isBootProject(project) || SpringProjectUtil.isSpringProject(project)) { if (project.getElementName() == null) { @@ -291,7 +305,7 @@ public class SpringSymbolIndex implements InitializingBean { InitializeProject initializeItem = new InitializeProject(project, this.indexers[i]); futures[i] = CompletableFuture.runAsync(initializeItem, this.updateQueue); } - + return CompletableFuture.allOf(futures); } } else { @@ -520,21 +534,35 @@ public class SpringSymbolIndex implements InitializingBean { .filter(filter) .map(enhanced -> enhanced.getSymbol()); } - - public List getSymbols(String docURI) { - List docSymbols = this.symbolsByDoc.get(docURI); - if (docSymbols != null) { - synchronized(docSymbols) { - ImmutableList.Builder builder = ImmutableList.builder(); - for (EnhancedSymbolInformation enhanced : docSymbols) { - builder.add(enhanced.getSymbol()); - } - return builder.build(); - } + + synchronized private CompletableFuture projectInitializedFuture(IJavaProject project) { + if (project == null) { + return CompletableFuture.completedFuture(null); + } else { + URI uri = project.getLocationUri(); + return initializedProjects.computeIfAbsent(uri, u -> new CompletableFuture()); } - else { + } + + public List getSymbols(String docURI) { + try { + CompletableFuture projectInitialized = futureProjectFinder.findFuture(URI.create(docURI)).thenCompose(project -> projectInitializedFuture(project)); + projectInitialized.get(60, TimeUnit.SECONDS); + List docSymbols = this.symbolsByDoc.get(docURI); + if (docSymbols != null) { + synchronized (docSymbols) { + ImmutableList.Builder builder = ImmutableList.builder(); + for (EnhancedSymbolInformation enhanced : docSymbols) { + builder.add(enhanced.getSymbol()); + } + return builder.build(); + } + } + } catch (Exception e) { + log.warn("", e); return Collections.emptyList(); } + return Collections.emptyList(); } public List getAllAdditionalInformation(Predicate filter) { diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/AdvancedSourceLinksTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/AdvancedSourceLinksTest.java index 3edfd4b77..f0e11af55 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/AdvancedSourceLinksTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/AdvancedSourceLinksTest.java @@ -20,20 +20,30 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Import; -import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; +import org.springframework.ide.vscode.boot.app.BootLanguageServerBootApp; import org.springframework.ide.vscode.boot.bootiful.SourceLinksTestConf; import org.springframework.ide.vscode.boot.java.links.SourceLinks; import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject; +import org.springframework.ide.vscode.languageserver.starter.LanguageServerAutoConf; import org.springframework.ide.vscode.project.harness.ProjectsHarness; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.junit4.SpringRunner; /** * @author Alex Boyko */ @RunWith(SpringRunner.class) -@BootLanguageServerTest -@Import(SourceLinksTestConf.class) +//@BootLanguageServerTest +@OverrideAutoConfiguration(enabled=false) +@Import({LanguageServerAutoConf.class, SourceLinksTestConf.class}) +@SpringBootTest(classes={ + BootLanguageServerBootApp.class +}) +@DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD) public class AdvancedSourceLinksTest { private ProjectsHarness projects = ProjectsHarness.INSTANCE; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerXMLProjectTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerXMLProjectTest.java index 69e20fe07..ea24025d9 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerXMLProjectTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerXMLProjectTest.java @@ -25,24 +25,34 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Import; +import org.springframework.ide.vscode.boot.app.BootLanguageServerBootApp; import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; -import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; import org.springframework.ide.vscode.boot.bootiful.XmlBeansTestConf; import org.springframework.ide.vscode.boot.java.beans.BeansSymbolAddOnInformation; import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation; import org.springframework.ide.vscode.boot.java.utils.SymbolIndexConfig; import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.languageserver.starter.LanguageServerAutoConf; import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; import org.springframework.ide.vscode.project.harness.ProjectsHarness; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.junit4.SpringRunner; /** * @author Martin Lippert */ @RunWith(SpringRunner.class) -@BootLanguageServerTest -@Import(XmlBeansTestConf.class) +//@BootLanguageServerTest +@OverrideAutoConfiguration(enabled=false) +@Import({LanguageServerAutoConf.class, XmlBeansTestConf.class}) +@SpringBootTest(classes={ + BootLanguageServerBootApp.class +}) +@DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD) public class SpringIndexerXMLProjectTest { @Autowired private BootLanguageServerHarness harness; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/XmlBeansHyperlinkTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/XmlBeansHyperlinkTest.java index 63dee5c66..bc0c3dc0d 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/XmlBeansHyperlinkTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/XmlBeansHyperlinkTest.java @@ -27,17 +27,22 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Import; +import org.springframework.ide.vscode.boot.app.BootLanguageServerBootApp; import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; -import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; import org.springframework.ide.vscode.boot.bootiful.XmlBeansTestConf; import org.springframework.ide.vscode.boot.test.DefinitionLinkAsserts; import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject; import org.springframework.ide.vscode.commons.util.UriUtil; import org.springframework.ide.vscode.commons.util.text.LanguageId; +import org.springframework.ide.vscode.languageserver.starter.LanguageServerAutoConf; import org.springframework.ide.vscode.languageserver.testharness.Editor; import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; import org.springframework.ide.vscode.project.harness.ProjectsHarness; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.junit4.SpringRunner; import com.google.gson.Gson; @@ -46,8 +51,13 @@ import com.google.gson.Gson; * @author Alex Boyko */ @RunWith(SpringRunner.class) -@BootLanguageServerTest -@Import(XmlBeansTestConf.class) +//@BootLanguageServerTest +@OverrideAutoConfiguration(enabled=false) +@Import({LanguageServerAutoConf.class, XmlBeansTestConf.class}) +@SpringBootTest(classes={ + BootLanguageServerBootApp.class +}) +@DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD) public class XmlBeansHyperlinkTest { @Autowired private BootLanguageServerHarness harness; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/XMLSpelExpressionValidationTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/XMLSpelExpressionValidationTest.java index c1121a4a0..4850d3250 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/XMLSpelExpressionValidationTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/XMLSpelExpressionValidationTest.java @@ -31,8 +31,10 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Import; -import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; +import org.springframework.ide.vscode.boot.app.BootLanguageServerBootApp; import org.springframework.ide.vscode.boot.bootiful.XmlBeansTestConf; import org.springframework.ide.vscode.boot.xml.SpringXMLReconcileEngine; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; @@ -42,8 +44,11 @@ import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguage import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject; import org.springframework.ide.vscode.commons.util.text.LanguageId; import org.springframework.ide.vscode.commons.util.text.TextDocument; +import org.springframework.ide.vscode.languageserver.starter.LanguageServerAutoConf; import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; import org.springframework.ide.vscode.project.harness.ProjectsHarness; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.junit4.SpringRunner; import com.google.gson.Gson; @@ -52,8 +57,13 @@ import com.google.gson.Gson; * @author Martin Lippert */ @RunWith(SpringRunner.class) -@BootLanguageServerTest -@Import(XmlBeansTestConf.class) +//@BootLanguageServerTest +@OverrideAutoConfiguration(enabled=false) +@Import({LanguageServerAutoConf.class, XmlBeansTestConf.class}) +@SpringBootTest(classes={ + BootLanguageServerBootApp.class +}) +@DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD) public class XMLSpelExpressionValidationTest { @Autowired private BootLanguageServerHarness harness;