Re-work project reconcile for optimization purposes

This commit is contained in:
aboyko
2023-01-25 16:10:39 -05:00
parent 72ce3e03b2
commit df5b6c2cf7
12 changed files with 378 additions and 175 deletions

View File

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

View File

@@ -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();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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