Re-work project reconcile for optimization purposes
This commit is contained in:
@@ -10,16 +10,9 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.app;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
@@ -27,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.ide.vscode.boot.common.IJavaProjectReconcileEngine;
|
||||
import org.springframework.ide.vscode.boot.common.ProjectReconcileScheduler;
|
||||
import org.springframework.ide.vscode.boot.factories.SpringFactoriesLanguageServerComponents;
|
||||
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
|
||||
import org.springframework.ide.vscode.boot.java.links.JavaElementLocationProvider;
|
||||
@@ -39,16 +33,15 @@ import org.springframework.ide.vscode.boot.metadata.ProjectBasedPropertyIndexPro
|
||||
import org.springframework.ide.vscode.boot.properties.BootPropertiesLanguageServerComponents;
|
||||
import org.springframework.ide.vscode.boot.validation.BootVersionValidationEngine;
|
||||
import org.springframework.ide.vscode.boot.xml.SpringXMLLanguageServerComponents;
|
||||
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.CompositeCompletionEngine;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter;
|
||||
import org.springframework.ide.vscode.commons.languageserver.composable.CompositeLanguageServerComponents;
|
||||
import org.springframework.ide.vscode.commons.languageserver.composable.LanguageServerComponents;
|
||||
import org.springframework.ide.vscode.commons.languageserver.config.LanguageServerProperties;
|
||||
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.reconcile.IReconcileEngine;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.HoverHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
|
||||
@@ -59,18 +52,9 @@ import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureProvid
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import reactor.core.Disposable;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
@Component
|
||||
public class BootLanguageServerInitializer implements InitializingBean {
|
||||
|
||||
private static final long DEBOUNCE_PERIOD_PROJECT_RECONCILE = 500;
|
||||
|
||||
private static final List<String> FILES_TO_WATCH_GLOB = List.of("**/*.java");
|
||||
|
||||
@Autowired SimpleLanguageServer server;
|
||||
@Autowired BootLanguageServerParams params;
|
||||
@Autowired SourceLinks sourceLinks;
|
||||
@@ -93,9 +77,6 @@ public class BootLanguageServerInitializer implements InitializingBean {
|
||||
|
||||
private CompositeLanguageServerComponents components;
|
||||
private VscodeCompletionEngineAdapter completionEngineAdapter;
|
||||
private List<IJavaProjectReconcileEngine> projectReconcilers;
|
||||
private Scheduler projectReconcileScheduler = Schedulers.newBoundedElastic(1, Integer.MAX_VALUE, "Project-Reconciler", 10);
|
||||
private Map<URI, Disposable> projectReconcileRequests = new ConcurrentHashMap<>();
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(BootLanguageServerInitializer.class);
|
||||
|
||||
@@ -104,7 +85,6 @@ public class BootLanguageServerInitializer implements InitializingBean {
|
||||
|
||||
@Override
|
||||
public void deleted(IJavaProject project) {
|
||||
doNotValidateProject(project, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -126,8 +106,6 @@ public class BootLanguageServerInitializer implements InitializingBean {
|
||||
s.validateWith(doc.getId(), reconciler);
|
||||
}
|
||||
}
|
||||
validateProject(project, reconciler);
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -138,26 +116,39 @@ public class BootLanguageServerInitializer implements InitializingBean {
|
||||
//TODO: CompositeLanguageServerComponents object instance serves no purpose anymore. The constructor really just contains
|
||||
// some server intialization code. Migrate that code and get rid of the ComposableLanguageServer class
|
||||
CompositeLanguageServerComponents.Builder builder = new CompositeLanguageServerComponents.Builder();
|
||||
builder.add(new BootPropertiesLanguageServerComponents(server, params, javaElementLocationProvider, parser, yamlStructureProvider, yamlAssistContextProvider, sourceLinks));
|
||||
BootJavaLanguageServerComponents bootJavaLanguageServerComponent = new BootJavaLanguageServerComponents(appContext);
|
||||
builder.add(bootJavaLanguageServerComponent);
|
||||
builder.add(new SpringXMLLanguageServerComponents(server, springIndexer, params, config));
|
||||
builder.add(new SpringFactoriesLanguageServerComponents(projectFinder, springIndexer, config));
|
||||
components = builder.build(server);
|
||||
|
||||
projectReconcilers = List.of(
|
||||
(IJavaProjectReconcileEngine) bootJavaLanguageServerComponent.getReconcileEngine().get(),
|
||||
new BootVersionValidationEngine(server, config)
|
||||
List<LanguageServerComponents> componentsList = List.of(
|
||||
new BootPropertiesLanguageServerComponents(server, params, javaElementLocationProvider, parser, yamlStructureProvider, yamlAssistContextProvider, sourceLinks),
|
||||
new BootJavaLanguageServerComponents(appContext),
|
||||
new SpringXMLLanguageServerComponents(server, springIndexer, params, config),
|
||||
new SpringFactoriesLanguageServerComponents(projectFinder, springIndexer, config)
|
||||
);
|
||||
|
||||
SimpleTextDocumentService documents = server.getTextDocumentService();
|
||||
List<ProjectReconcileScheduler> reconcileSchedulers = new ArrayList<>(componentsList.size() + 1);
|
||||
for (LanguageServerComponents c : componentsList) {
|
||||
builder.add(c);
|
||||
|
||||
if (!configProps.isReconcileOnlyOpenedDocs()) {
|
||||
c.getReconcileEngine()
|
||||
.filter(IJavaProjectReconcileEngine.class::isInstance)
|
||||
.map(IJavaProjectReconcileEngine.class::cast)
|
||||
.map(r -> r.getScheduler())
|
||||
.ifPresent(reconcileSchedulers::add);
|
||||
}
|
||||
}
|
||||
|
||||
// Version reconciler is for Maven/Gradle build files which are not part of
|
||||
// server components because they come from docs we don't support at the moment
|
||||
reconcileSchedulers.add(new BootVersionValidationEngine(server, config, params.projectObserver, projectFinder).getScheduler());
|
||||
|
||||
components.getReconcileEngine().ifPresent(reconcileEngine -> {
|
||||
documents.onDidChangeContent(params -> {
|
||||
TextDocument doc = params.getDocument();
|
||||
server.validateWith(doc.getId(), reconcileEngine);
|
||||
});
|
||||
});
|
||||
// Kick off project reconcile schedulers
|
||||
for (ProjectReconcileScheduler scheduler : reconcileSchedulers) {
|
||||
scheduler.start();
|
||||
server.onShutdown(() -> scheduler.stop());
|
||||
}
|
||||
|
||||
components = builder.build(server);
|
||||
|
||||
SimpleTextDocumentService documents = server.getTextDocumentService();
|
||||
|
||||
if (!completionEngines.isEmpty()) {
|
||||
CompositeCompletionEngine compositeCompletionEngine = new CompositeCompletionEngine();
|
||||
@@ -175,6 +166,7 @@ public class BootLanguageServerInitializer implements InitializingBean {
|
||||
|
||||
components.getDocumentSymbolProvider().ifPresent(documents::onDocumentSymbol);
|
||||
|
||||
|
||||
if (recipesRepo != null) {
|
||||
recipesRepo.onRecipesLoaded(v -> {
|
||||
// Recipes will start loading only after config has been received. Therefore safe to start listening to config changes now
|
||||
@@ -183,37 +175,34 @@ public class BootLanguageServerInitializer implements InitializingBean {
|
||||
reconcile();
|
||||
});
|
||||
} else {
|
||||
// Reconcile would occur as listeners will be receiving events
|
||||
startListeningToPerformReconcile();
|
||||
reconcile();
|
||||
}
|
||||
|
||||
server.onShutdown(() -> {
|
||||
for (IJavaProject p : projectFinder.all()) {
|
||||
doNotValidateProject(p, false);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void startListeningToPerformReconcile() {
|
||||
components.getReconcileEngine().ifPresent(reconcileEngine -> {
|
||||
server.getTextDocumentService().onDidChangeContent(params -> {
|
||||
TextDocument doc = params.getDocument();
|
||||
server.validateWith(doc.getId(), reconcileEngine);
|
||||
});
|
||||
});
|
||||
config.addListener(evt -> reconcile());
|
||||
|
||||
params.projectObserver.addListener(reconcileDocumentsForProjectChange(server, components, params.projectFinder));
|
||||
|
||||
server.getWorkspaceService().getFileObserver().onFilesChanged(FILES_TO_WATCH_GLOB, this::handleFiles);
|
||||
server.getWorkspaceService().getFileObserver().onFilesCreated(FILES_TO_WATCH_GLOB, this::handleFiles);
|
||||
|
||||
// TODO: index update even happens on every file save. Very expensive to blindly reconcile all projects.
|
||||
// Need to figure out a check if spring index has any changes
|
||||
// springIndexer.onUpdate(v -> reconcile());
|
||||
// // TODO: index update even happens on every file save. Very expensive to blindly reconcile all projects.
|
||||
// // Need to figure out a check if spring index has any changes
|
||||
//// springIndexer.onUpdate(v -> reconcile());
|
||||
}
|
||||
|
||||
private void reconcile() {
|
||||
components.getReconcileEngine().ifPresent(reconciler -> {
|
||||
log.info("A configuration changed, triggering reconcile on all open documents");
|
||||
log.info("Triggering reconcile on all open documents");
|
||||
for (TextDocument doc : server.getTextDocumentService().getAll()) {
|
||||
server.validateWith(doc.getId(), reconciler);
|
||||
}
|
||||
params.projectFinder.all().forEach(p -> validateProject(p, reconciler));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -228,79 +217,4 @@ public class BootLanguageServerInitializer implements InitializingBean {
|
||||
}
|
||||
}
|
||||
|
||||
private void validateProject(IJavaProject project, IReconcileEngine reconcileEngine) {
|
||||
if (configProps.isReconcileOnlyOpenedDocs()) {
|
||||
return;
|
||||
}
|
||||
|
||||
URI uri = project.getLocationUri();
|
||||
|
||||
Disposable previousRequest = projectReconcileRequests.put(uri, Mono.delay(Duration.ofMillis(DEBOUNCE_PERIOD_PROJECT_RECONCILE))
|
||||
.publishOn(projectReconcileScheduler)
|
||||
.doOnSuccess(l -> {
|
||||
if (projectReconcileRequests.remove(uri) != null) {
|
||||
projectFinder.find(new TextDocumentIdentifier(uri.toASCIIString())).ifPresent(p -> {
|
||||
for (IJavaProjectReconcileEngine projectReconciler : projectReconcilers) {
|
||||
projectReconciler.clear(project);
|
||||
projectReconciler.reconcile(p, doc -> server.createProblemCollector(doc));
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.subscribe());
|
||||
// Dispose previous request to debounce project reconcile requests.
|
||||
if (previousRequest != null) {
|
||||
previousRequest.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void doNotValidateProject(IJavaProject project, boolean asyncClear) {
|
||||
if (configProps.isReconcileOnlyOpenedDocs()) {
|
||||
return;
|
||||
}
|
||||
|
||||
URI uri = project.getLocationUri();
|
||||
Disposable request = projectReconcileRequests.remove(uri);
|
||||
if (request != null) {
|
||||
request.dispose();
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: Look at LanguageServerHarness to fix the deadlock that occurs every 2 second time maven build is ran
|
||||
* If #clear(IJavaProject) is synchronous then the locked LanguageServerHarness instance is attempted to call publishDiagnostic()
|
||||
* which is caused by the #clear(...) call. In the LS reality this will never happen as #publishDiagnsotics() is always a future
|
||||
*/
|
||||
if (asyncClear) {
|
||||
Mono.fromFuture(CompletableFuture.runAsync(() -> projectReconcilers.forEach(projectReconciler -> projectReconciler.clear(project))))
|
||||
.publishOn(projectReconcileScheduler);
|
||||
} else {
|
||||
for (IJavaProjectReconcileEngine projectReconciler : projectReconcilers) {
|
||||
projectReconciler.clear(project);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleFiles(String[] files) {
|
||||
if (configProps.isReconcileOnlyOpenedDocs()) {
|
||||
return;
|
||||
}
|
||||
|
||||
components.getReconcileEngine().ifPresent(reconcileEngine -> {
|
||||
for (String f : files) {
|
||||
URI uri = URI.create(f);
|
||||
TextDocumentIdentifier docId = new TextDocumentIdentifier(uri.toASCIIString());
|
||||
TextDocument doc = server.getTextDocumentService().getLatestSnapshot(docId.getUri());
|
||||
if (doc == null) {
|
||||
projectFinder.find(docId).ifPresent(project -> {
|
||||
Path p = Paths.get(uri);
|
||||
if (IClasspathUtil.getSourceFolders(project.getClasspath()).filter(folder -> p.startsWith(folder.toPath())).findFirst().isPresent()) {
|
||||
validateProject(project, reconcileEngine);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,16 +10,14 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.common;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public interface IJavaProjectReconcileEngine {
|
||||
|
||||
void reconcile(IJavaProject project, Function<TextDocument, IProblemCollector> problemCollectorFactory);
|
||||
void reconcile(IJavaProject project);
|
||||
|
||||
void clear(IJavaProject project);
|
||||
|
||||
|
||||
ProjectReconcileScheduler getScheduler();
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/*******************************************************************************
|
||||
* 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.common;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
|
||||
import reactor.core.Disposable;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
public abstract class ProjectReconcileScheduler {
|
||||
|
||||
private IJavaProjectReconcileEngine reconciler;
|
||||
private JavaProjectFinder projectFinder;
|
||||
private long debounce;
|
||||
|
||||
private Scheduler projectReconcileScheduler;
|
||||
private Map<URI, Disposable> projectReconcileRequests = new ConcurrentHashMap<>();
|
||||
private boolean started;
|
||||
|
||||
public ProjectReconcileScheduler(IJavaProjectReconcileEngine reconciler, JavaProjectFinder projectFinder, long debounce, int numberOFthreads) {
|
||||
this.reconciler = reconciler;
|
||||
this.debounce = debounce;
|
||||
this.projectFinder = projectFinder;
|
||||
|
||||
projectReconcileScheduler = Schedulers.newBoundedElastic(numberOFthreads, Integer.MAX_VALUE, "Project-Reconciler", 10);
|
||||
}
|
||||
|
||||
public ProjectReconcileScheduler(IJavaProjectReconcileEngine reconciler, JavaProjectFinder projectFinder) {
|
||||
this(reconciler, projectFinder, 500, 1);
|
||||
}
|
||||
|
||||
public final synchronized void start() {
|
||||
if (!started) {
|
||||
init();
|
||||
started = true;
|
||||
}
|
||||
}
|
||||
|
||||
public final synchronized void stop() {
|
||||
if (started) {
|
||||
dispose();
|
||||
started = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected void init() {
|
||||
|
||||
}
|
||||
|
||||
protected void dispose() {
|
||||
for (IJavaProject p : projectFinder.all()) {
|
||||
unscheduleValidation(p);
|
||||
reconciler.clear(p);
|
||||
}
|
||||
}
|
||||
|
||||
protected final void scheduleValidationForAllProjects() {
|
||||
for (IJavaProject p : projectFinder.all()) {
|
||||
scheduleValidation(p);
|
||||
}
|
||||
}
|
||||
|
||||
protected final void scheduleValidation(IJavaProject project) {
|
||||
URI uri = project.getLocationUri();
|
||||
|
||||
Disposable previousRequest = projectReconcileRequests.put(uri, Mono.delay(Duration.ofMillis(debounce))
|
||||
.publishOn(projectReconcileScheduler)
|
||||
.doOnSuccess(l -> {
|
||||
if (projectReconcileRequests.remove(uri) != null) {
|
||||
projectFinder.find(new TextDocumentIdentifier(uri.toASCIIString())).ifPresent(p -> {
|
||||
reconciler.clear(project);
|
||||
reconciler.reconcile(p);
|
||||
});
|
||||
}
|
||||
})
|
||||
.subscribe());
|
||||
// Dispose previous request to debounce project reconcile requests.
|
||||
if (previousRequest != null) {
|
||||
previousRequest.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected final void unscheduleValidation(IJavaProject project) {
|
||||
URI uri = project.getLocationUri();
|
||||
Disposable request = projectReconcileRequests.remove(uri);
|
||||
if (request != null) {
|
||||
request.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected final void clear(IJavaProject project, boolean async) {
|
||||
/*
|
||||
* TODO: Look at LanguageServerHarness to fix the deadlock that occurs every 2 second time maven build is ran
|
||||
* If #clear(IJavaProject) is synchronous then the locked LanguageServerHarness instance is attempted to call publishDiagnostic()
|
||||
* which is caused by the #clear(...) call. In the LS reality this will never happen as #publishDiagnsotics() is always a future
|
||||
*/
|
||||
if (async) {
|
||||
Mono.fromRunnable(() -> reconciler.clear(project))
|
||||
.publishOn(projectReconcileScheduler)
|
||||
.subscribe();
|
||||
} else {
|
||||
reconciler.clear(project);
|
||||
}
|
||||
}
|
||||
|
||||
public JavaProjectFinder getProjectFinder() {
|
||||
return projectFinder;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -191,8 +191,10 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
|
||||
RewriteCompilationUnitCache orCompilationUnitCache = appContext.getBean(RewriteCompilationUnitCache.class);
|
||||
|
||||
RewriteRecipeRepository recipeRepo = appContext.getBean(RewriteRecipeRepository.class);
|
||||
|
||||
RewriteReconciler rewriteJavaReconciler = new RewriteReconciler(
|
||||
appContext.getBean(RewriteRecipeRepository.class),
|
||||
recipeRepo,
|
||||
orCompilationUnitCache,
|
||||
server.getQuickfixRegistry(),
|
||||
config
|
||||
@@ -201,7 +203,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
reconcileEngine = new BootJavaReconcileEngine(projectFinder, new JavaReconciler[] {
|
||||
jdtReconciler,
|
||||
rewriteJavaReconciler
|
||||
}, documents);
|
||||
}, server, config, projectObserver, recipeRepo);
|
||||
|
||||
codeActionProvider = new BootJavaCodeActionProvider(
|
||||
projectFinder,
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/*******************************************************************************
|
||||
* 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.java.handlers;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.springframework.ide.vscode.boot.app.BootJavaConfig;
|
||||
import org.springframework.ide.vscode.boot.common.IJavaProjectReconcileEngine;
|
||||
import org.springframework.ide.vscode.boot.common.ProjectReconcileScheduler;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRecipeRepository;
|
||||
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.languageserver.java.ProjectObserver;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
|
||||
import org.springframework.ide.vscode.commons.util.FileObserver;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public class BootJavaProjectReconcilerScheduler extends ProjectReconcileScheduler {
|
||||
|
||||
private static final List<String> FILES_TO_WATCH_GLOB = List.of("**/*.java");
|
||||
|
||||
private ProjectObserver projectObserver;
|
||||
private BootJavaConfig config;
|
||||
private RewriteRecipeRepository recipesRepo;
|
||||
private FileObserver fileObserver;
|
||||
private SimpleTextDocumentService documents;
|
||||
|
||||
public BootJavaProjectReconcilerScheduler(IJavaProjectReconcileEngine reconciler, FileObserver fileObserver, ProjectObserver projectObserver,
|
||||
BootJavaConfig config, RewriteRecipeRepository recipesRepo, SimpleTextDocumentService documents, JavaProjectFinder projectFinder) {
|
||||
super(reconciler, projectFinder);
|
||||
this.fileObserver = fileObserver;
|
||||
this.projectObserver = projectObserver;
|
||||
this.config = config;
|
||||
this.recipesRepo = recipesRepo;
|
||||
this.documents = documents;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
if (recipesRepo != null) {
|
||||
recipesRepo.onRecipesLoaded(v -> {
|
||||
// Recipes will start loading only after config has been received. Therefore safe to start listening to config changes now
|
||||
// and launch initial project reconcile since both config and recipes are present
|
||||
startListeningToPerformReconcile();
|
||||
scheduleValidationForAllProjects();
|
||||
});
|
||||
} else {
|
||||
startListeningToPerformReconcile();
|
||||
scheduleValidationForAllProjects();
|
||||
}
|
||||
}
|
||||
|
||||
private void startListeningToPerformReconcile() {
|
||||
config.addListener(evt -> scheduleValidationForAllProjects());
|
||||
|
||||
projectObserver.addListener(new ProjectObserver.Listener() {
|
||||
|
||||
@Override
|
||||
public void deleted(IJavaProject project) {
|
||||
unscheduleValidation(project);
|
||||
clear(project, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void created(IJavaProject project) {
|
||||
scheduleValidation(project);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changed(IJavaProject project) {
|
||||
scheduleValidation(project);
|
||||
}
|
||||
});
|
||||
|
||||
fileObserver.onFilesChanged(FILES_TO_WATCH_GLOB, this::handleFiles);
|
||||
fileObserver.onFilesCreated(FILES_TO_WATCH_GLOB, this::handleFiles);
|
||||
|
||||
// TODO: index update even happens on every file save. Very expensive to blindly reconcile all projects.
|
||||
// Need to figure out a check if spring index has any changes
|
||||
// springIndexer.onUpdate(v -> reconcile());
|
||||
}
|
||||
|
||||
private void handleFiles(String[] files) {
|
||||
for (String f : files) {
|
||||
URI uri = URI.create(f);
|
||||
TextDocumentIdentifier docId = new TextDocumentIdentifier(uri.toASCIIString());
|
||||
TextDocument doc = documents.getLatestSnapshot(docId.getUri());
|
||||
if (doc == null) {
|
||||
getProjectFinder().find(docId).ifPresent(project -> {
|
||||
Path p = Paths.get(uri);
|
||||
if (IClasspathUtil.getSourceFolders(project.getClasspath())
|
||||
.filter(folder -> p.startsWith(folder.toPath())).findFirst().isPresent()) {
|
||||
scheduleValidation(project);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,22 +18,26 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.app.BootJavaConfig;
|
||||
import org.springframework.ide.vscode.boot.common.IJavaProjectReconcileEngine;
|
||||
import org.springframework.ide.vscode.boot.common.ProjectReconcileScheduler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.JavaReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRecipeRepository;
|
||||
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.languageserver.java.ProjectObserver;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.UriUtil;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
import org.springframework.ide.vscode.commons.util.text.LazyTextDocument;
|
||||
@@ -46,14 +50,26 @@ public class BootJavaReconcileEngine implements IReconcileEngine, IJavaProjectRe
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(BootJavaReconcileEngine.class);
|
||||
|
||||
private final SimpleTextDocumentService documents;
|
||||
private final JavaProjectFinder projectFinder;
|
||||
private JavaReconciler[] javaReconcilers;
|
||||
private final JavaReconciler[] javaReconcilers;
|
||||
private final BootJavaProjectReconcilerScheduler projectReconeilerScheduler;
|
||||
private final SimpleLanguageServer server;
|
||||
|
||||
public BootJavaReconcileEngine(JavaProjectFinder projectFinder, JavaReconciler[] javaReconcilers, SimpleTextDocumentService documents) {
|
||||
this.documents = documents;
|
||||
public BootJavaReconcileEngine(JavaProjectFinder projectFinder, JavaReconciler[] javaReconcilers,
|
||||
SimpleLanguageServer server, BootJavaConfig config, ProjectObserver projectObserver,
|
||||
RewriteRecipeRepository recipeRepo) {
|
||||
this.projectFinder = projectFinder;
|
||||
this.javaReconcilers = javaReconcilers;
|
||||
this.server = server;
|
||||
this.projectReconeilerScheduler = new BootJavaProjectReconcilerScheduler(
|
||||
this,
|
||||
server.getWorkspaceService().getFileObserver(),
|
||||
projectObserver,
|
||||
config,
|
||||
recipeRepo,
|
||||
server.getTextDocumentService(),
|
||||
projectFinder
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -113,7 +129,7 @@ public class BootJavaReconcileEngine implements IReconcileEngine, IJavaProjectRe
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconcile(IJavaProject project, Function<TextDocument, IProblemCollector> problemCollectorFactory) {
|
||||
public void reconcile(IJavaProject project) {
|
||||
Stream<Path> files = IClasspathUtil.getProjectJavaSourceFolders(project.getClasspath()).flatMap(folder -> {
|
||||
try {
|
||||
return Files.walk(folder.toPath()).filter(Files::isRegularFile);
|
||||
@@ -126,18 +142,17 @@ public class BootJavaReconcileEngine implements IReconcileEngine, IJavaProjectRe
|
||||
.filter(f -> f.getFileName().toString().endsWith(".java"))
|
||||
.map(f -> new TextDocumentIdentifier(f.toUri().toASCIIString()));
|
||||
|
||||
List<TextDocument> docs = docIds.filter(docId -> documents.getLatestSnapshot(docId.getUri()) == null)
|
||||
List<TextDocument> docs = docIds.filter(docId -> server.getTextDocumentService().getLatestSnapshot(docId.getUri()) == null)
|
||||
.map(docId -> new LazyTextDocument(docId.getUri(), LanguageId.JAVA)).collect(Collectors.toList());
|
||||
|
||||
Map<IDocument, IProblemCollector> problemCollectors = docs.stream()
|
||||
.collect(Collectors.toMap(d -> d, d -> problemCollectorFactory.apply(d)));
|
||||
.collect(Collectors.toMap(d -> d, d -> server.createProblemCollector(d)));
|
||||
|
||||
problemCollectors.values().forEach(c -> c.beginCollecting());
|
||||
|
||||
for (JavaReconciler jr : javaReconcilers) {
|
||||
try {
|
||||
Map<IDocument, Collection<ReconcileProblem>> problems = jr.reconcile(project, docs,
|
||||
problemCollectorFactory);
|
||||
Map<IDocument, Collection<ReconcileProblem>> problems = jr.reconcile(project, docs);
|
||||
problems.entrySet().forEach(e -> {
|
||||
IProblemCollector collector = problemCollectors.get(e.getKey());
|
||||
e.getValue().forEach(p -> collector.accept(p));
|
||||
@@ -161,8 +176,14 @@ public class BootJavaReconcileEngine implements IReconcileEngine, IJavaProjectRe
|
||||
}
|
||||
})
|
||||
.filter(f -> f.getFileName().toString().endsWith(".java"))
|
||||
.forEach(p -> documents.publishDiagnostics(new TextDocumentIdentifier(p.toUri().toASCIIString()), Collections.emptyList()));
|
||||
.filter(f -> server.getTextDocumentService().getLatestSnapshot(UriUtil.toUri(f.toFile()).toASCIIString()) == null)
|
||||
.forEach(p -> server.getTextDocumentService().publishDiagnostics(new TextDocumentIdentifier(p.toUri().toASCIIString()), Collections.emptyList()));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectReconcileScheduler getScheduler() {
|
||||
return projectReconeilerScheduler;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2022 VMware, Inc.
|
||||
* Copyright (c) 2022, 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
|
||||
@@ -13,7 +13,6 @@ package org.springframework.ide.vscode.boot.java.reconcilers;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
@@ -25,7 +24,6 @@ public interface JavaReconciler {
|
||||
|
||||
void reconcile(IJavaProject project, IDocument doc, IProblemCollector problemCollector);
|
||||
|
||||
Map<IDocument, Collection<ReconcileProblem>> reconcile(IJavaProject project, List<TextDocument> docs,
|
||||
Function<TextDocument, IProblemCollector> problemCollectorFactory);
|
||||
Map<IDocument, Collection<ReconcileProblem>> reconcile(IJavaProject project, List<TextDocument> docs);
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
@@ -160,8 +159,7 @@ public class JdtReconciler implements JavaReconciler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<IDocument, Collection<ReconcileProblem>> reconcile(IJavaProject project, List<TextDocument> docs,
|
||||
Function<TextDocument, IProblemCollector> problemCollectorFactory) {
|
||||
public Map<IDocument, Collection<ReconcileProblem>> reconcile(IJavaProject project, List<TextDocument> docs) {
|
||||
|
||||
if (config.isRewriteReconcileEnabled()) {
|
||||
// long start = System.currentTimeMillis();
|
||||
|
||||
@@ -22,7 +22,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.openrewrite.ExecutionContext;
|
||||
@@ -139,8 +138,7 @@ public class RewriteReconciler implements JavaReconciler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<IDocument, Collection<ReconcileProblem>> reconcile(IJavaProject project, List<TextDocument> docs,
|
||||
Function<TextDocument, IProblemCollector> problemCollectorFactory) {
|
||||
public Map<IDocument, Collection<ReconcileProblem>> reconcile(IJavaProject project, List<TextDocument> docs) {
|
||||
|
||||
if (!config.isRewriteReconcileEnabled()) {
|
||||
return Collections.emptyMap();
|
||||
@@ -162,9 +160,9 @@ public class RewriteReconciler implements JavaReconciler {
|
||||
}
|
||||
JavaParser javaParser = ORAstUtils.createJavaParser(project);
|
||||
javaParser.setSourceSet(MavenProjectParser.MAIN);
|
||||
allProblems.putAll(doReconcile(project, mainSources, problemCollectorFactory, javaParser));
|
||||
allProblems.putAll(doReconcile(project, mainSources, javaParser));
|
||||
javaParser.setSourceSet(MavenProjectParser.TEST);
|
||||
allProblems.putAll(doReconcile(project, testSources, problemCollectorFactory, javaParser));
|
||||
allProblems.putAll(doReconcile(project, testSources, javaParser));
|
||||
|
||||
long end = System.currentTimeMillis();
|
||||
log.info("reconciling project (OpenRewrite): " + project.getElementName() + " - " + docs.size() + " done in " + (end - start) + "ms");
|
||||
@@ -244,8 +242,7 @@ public class RewriteReconciler implements JavaReconciler {
|
||||
private static final int BATCH = 50;
|
||||
|
||||
// Parse in batches and share the parser
|
||||
private Map<IDocument, Collection<ReconcileProblem>> doReconcile(IJavaProject project, List<TextDocument> docs,
|
||||
Function<TextDocument, IProblemCollector> problemCollectorFactory, JavaParser javaParser) {
|
||||
private Map<IDocument, Collection<ReconcileProblem>> doReconcile(IJavaProject project, List<TextDocument> docs, JavaParser javaParser) {
|
||||
Map<IDocument, Collection<ReconcileProblem>> allProblems = new HashMap<>();
|
||||
if (javaParser != null && config.isRewriteReconcileEnabled()) {
|
||||
try {
|
||||
|
||||
@@ -99,8 +99,8 @@ public class SpringBootUpgrade {
|
||||
|
||||
if (version.getMajor() == targetVersion.getMajor() && version.getMinor() == targetVersion.getMinor()) {
|
||||
// patch version upgrade - treat as pom versions only upgrade
|
||||
recipe.doNext(new UpgradeDependencyVersion("org.springframework.boot", "*", version.getMajor() + "." + version.getMinor() + ".x", null, null));
|
||||
recipe.doNext(new UpgradeParentVersion("org.springframework.boot", "spring-boot-starter-parent", version.getMajor() + "." + version.getMinor() + ".x", null));
|
||||
recipe.doNext(new UpgradeDependencyVersion("org.springframework.boot", "*", version.getMajor() + "." + version.getMinor() + ".x", null, null, null));
|
||||
recipe.doNext(new UpgradeParentVersion("org.springframework.boot", "spring-boot-starter-parent", version.getMajor() + "." + version.getMinor() + ".x", null, null));
|
||||
} else {
|
||||
createRecipeIdsChain(version.getMajor(), version.getMinor(), targetVersion.getMajor(), targetVersion.getMinor()).stream()
|
||||
.map(recipeRepo::getRecipe)
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
package org.springframework.ide.vscode.boot.validation;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.app.BootJavaConfig;
|
||||
import org.springframework.ide.vscode.boot.common.IJavaProjectReconcileEngine;
|
||||
import org.springframework.ide.vscode.boot.common.ProjectReconcileScheduler;
|
||||
import org.springframework.ide.vscode.boot.validation.generations.ProjectVersionDiagnosticProvider;
|
||||
import org.springframework.ide.vscode.boot.validation.generations.ProjectVersionDiagnosticProvider.DiagnosticResult;
|
||||
import org.springframework.ide.vscode.boot.validation.generations.SpringIoProjectsProvider;
|
||||
@@ -26,9 +26,9 @@ import org.springframework.ide.vscode.boot.validation.generations.SpringProjects
|
||||
import org.springframework.ide.vscode.boot.validation.generations.VersionValidators;
|
||||
import org.springframework.ide.vscode.boot.validation.generations.preferences.VersionValidationPreferences;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
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.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public class BootVersionValidationEngine implements IJavaProjectReconcileEngine {
|
||||
|
||||
@@ -36,13 +36,41 @@ public class BootVersionValidationEngine implements IJavaProjectReconcileEngine
|
||||
|
||||
private SimpleLanguageServer server;
|
||||
private BootJavaConfig config;
|
||||
private ProjectReconcileScheduler projectReconcileScheduler;
|
||||
|
||||
public BootVersionValidationEngine(SimpleLanguageServer server, BootJavaConfig config) {
|
||||
public BootVersionValidationEngine(SimpleLanguageServer server, BootJavaConfig config, ProjectObserver projectObserver, JavaProjectFinder projectFinder) {
|
||||
this.server = server;
|
||||
this.config = config;
|
||||
this.projectReconcileScheduler = new ProjectReconcileScheduler(this, projectFinder) {
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
config.addListener(evt -> scheduleValidationForAllProjects());
|
||||
projectObserver.addListener(new ProjectObserver.Listener() {
|
||||
|
||||
@Override
|
||||
public void deleted(IJavaProject project) {
|
||||
unscheduleValidation(project);
|
||||
clear(project, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void created(IJavaProject project) {
|
||||
scheduleValidation(project);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changed(IJavaProject project) {
|
||||
scheduleValidation(project);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
public void reconcile(IJavaProject project, Function<TextDocument, IProblemCollector> problemCollectorFactory) {
|
||||
public void reconcile(IJavaProject project) {
|
||||
if (config.isBootVersionValidationEnabled()) {
|
||||
log.debug("validating Spring Boot version on project: " + project.getElementName());
|
||||
long start = System.currentTimeMillis();
|
||||
@@ -86,4 +114,9 @@ public class BootVersionValidationEngine implements IJavaProjectReconcileEngine
|
||||
Collections.emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectReconcileScheduler getScheduler() {
|
||||
return projectReconcileScheduler;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ public class ValueSpelExpressionValidationTest {
|
||||
@Autowired private CompilationUnitCache compilationUnitCache;
|
||||
@Autowired private SimpleLanguageServer server;
|
||||
@Autowired private BootJavaConfig config;
|
||||
@Autowired private ProjectObserver projectObserver;
|
||||
|
||||
private File directory;
|
||||
private String docUri;
|
||||
@@ -149,7 +150,7 @@ public class ValueSpelExpressionValidationTest {
|
||||
problemCollector = new TestProblemCollector();
|
||||
reconcileEngine = new BootJavaReconcileEngine(projectFinder, new JavaReconciler[] {
|
||||
new JdtReconciler(compilationUnitCache, config)
|
||||
}, server.getTextDocumentService());
|
||||
}, server, config, projectObserver, null);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
|
||||
Reference in New Issue
Block a user