Wait for document symbols in sync fashion

This commit is contained in:
BoykoAlex
2021-05-12 23:21:55 -04:00
parent 5a4eea4d2b
commit 4f007de1bd
8 changed files with 185 additions and 28 deletions

View File

@@ -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<URI, CompletableFuture<IJavaProject>> 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> 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<URI, CompletableFuture<IJavaProject>> e : pendingFindProjectRequests.entrySet()) {
Optional<IJavaProject> 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<IJavaProject> findFuture(URI uri) {
TextDocumentIdentifier id = new TextDocumentIdentifier(uri.toString());
Optional<IJavaProject> 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<IJavaProject> cf = pendingFindProjectRequests.get(uri);
if (cf == null) {
cf = new CompletableFuture<IJavaProject>();
pendingFindProjectRequests.put(uri, cf);
}
return cf;
}
}
}

View File

@@ -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;

View File

@@ -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> projectObserver) {
return new FutureProjectFinder(projectFinder, projectObserver);
}
}

View File

@@ -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<SymbolProvider> 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<URI, CompletableFuture<Void>> initializedProjects = new HashMap<>();
private SimpleWorkspaceService getWorkspaceService() {
return server.getServer().getWorkspaceService();
@@ -273,8 +279,16 @@ public class SpringSymbolIndex implements InitializingBean {
log.error("{}", e);
}
}
public CompletableFuture<Void> initializeProject(IJavaProject project) {
CompletableFuture<Void> cf = _initializeProject(project);
cf.thenAccept( f -> {
projectInitializedFuture(project).complete(null);
});
return cf;
}
private CompletableFuture<Void> _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<? extends SymbolInformation> getSymbols(String docURI) {
List<EnhancedSymbolInformation> docSymbols = this.symbolsByDoc.get(docURI);
if (docSymbols != null) {
synchronized(docSymbols) {
ImmutableList.Builder<SymbolInformation> builder = ImmutableList.builder();
for (EnhancedSymbolInformation enhanced : docSymbols) {
builder.add(enhanced.getSymbol());
}
return builder.build();
}
synchronized private CompletableFuture<Void> projectInitializedFuture(IJavaProject project) {
if (project == null) {
return CompletableFuture.completedFuture(null);
} else {
URI uri = project.getLocationUri();
return initializedProjects.computeIfAbsent(uri, u -> new CompletableFuture<Void>());
}
else {
}
public List<? extends SymbolInformation> getSymbols(String docURI) {
try {
CompletableFuture<Void> projectInitialized = futureProjectFinder.findFuture(URI.create(docURI)).thenCompose(project -> projectInitializedFuture(project));
projectInitialized.get(60, TimeUnit.SECONDS);
List<EnhancedSymbolInformation> docSymbols = this.symbolsByDoc.get(docURI);
if (docSymbols != null) {
synchronized (docSymbols) {
ImmutableList.Builder<SymbolInformation> 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<SymbolAddOnInformation> getAllAdditionalInformation(Predicate<SymbolAddOnInformation> filter) {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;